套接字選項這個話題在socket編程裏,可能已經屬於中高級話題了,之所以在 一開始就把這個話題提上來講,是因爲我們的一個近階段目標是能夠把 MY_PF_INET域的RAW協議走通,並在上面跑起一個ping程序,所以,按照ping程序的要求,接下來,我們必須實現套接字選項系統調用 setsockopt在MY_PF_INET中RAW協議中的相關實現。
下面是該系統調用函數的原型:
#include <sys/socket.h>
int setsockopt( int socket, int level, int option_name,
const void *option_value, size_t option_len);
第一個參數socket是套接字描述符。第二個參數level是被設置的選項的級別,如果想要在套接字級別上設置選項,就必須把level設置爲 SOL_SOCKET。option_name指定準備設置的選項,option_name可以有哪些取值,這取決於level,以linux 2.6內核爲例(在不同的平臺上,這種關係可能會有不同),在套接字級別上(SOL_SOCKET),option_name可以有以下取值:
SO_DEBUG,打開或關閉調試信息。
當option_value不等於0時,打開調試信息,否則,關閉調試信息。它實際所做的工作是在sock->sk->sk_flag中置SOCK_DBG(第10)位,或清SOCK_DBG位。
SO_REUSEADDR,打開或關閉地址複用功能。
當option_value不等於0時,打開,否則,關閉。它實際所做的工作是置sock->sk->sk_reuse爲1或0。
SO_DONTROUTE,打開或關閉路由查找功能。
當option_value不等於0時,打開,否則,關閉。它實際所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。
SO_BROADCAST,允許或禁止發送廣播數據。
當option_value不等於0時,允許,否則,禁止。它實際所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。
SO_SNDBUF,設置發送緩衝區的大小。
發送緩衝區的大小是有上下限的,其上限爲256 * (sizeof(struct sk_buff) + 256),下限爲2048字節。該操作將sock->sk->sk_sndbuf設置爲val * 2,之所以要乘以2,是防止大數據量的發送,突然導致緩衝區溢出。最後,該操作完成後,因爲對發送緩衝的大小作了改變,要檢查sleep隊列,如果有進程 正在等待寫,將它們喚醒。
SO_RCVBUF,設置接收緩衝區的大小。
接收緩衝區大小的上下限分別是:256 * (sizeof(struct sk_buff) + 256)和256字節。該操作將sock->sk->sk_rcvbuf設置爲val * 2。
SO_KEEPALIVE,套接字保活。
如果協議是TCP,並且當前的套接字狀態不是偵聽(listen)或關閉(close),那麼,當option_value不是零時,啓用TCP保活定時 器,否則關閉保活定時器。對於所有協議,該操作都會根據option_value置或清sock->sk->sk_flag中的 SOCK_KEEPOPEN位。
SO_OOBINLINE,緊急數據放入普通數據流。
該操作根據option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。
SO_NO_CHECK,打開或關閉校驗和。
該操作根據option_value的值,設置sock->sk->sk_no_check。
SO_PRIORITY,設置在套接字發送的所有包的協議定義優先權。Linux通過這一值來排列網絡隊列。
這個值在0到6之間(包括0和6),由option_value指定。賦給sock->sk->sk_priority。
SO_LINGER,如果選擇此選項, close或 shutdown將等到所有套接字裏排隊的消息成功發送或到達延遲時間後>纔會返回. 否則, 調用將立即返回。
該選項的參數(option_value)是一個linger結構:
struct linger {
int l_onoff; /* 延時狀態(打開/關閉) */
int l_linger; /* 延時多長時間 */
};
如果linger.l_onoff值爲0(關閉),則清sock->sk->sk_flag中的SOCK_LINGER位;否則,置該位,並賦sk->sk_lingertime值爲linger.l_linger。
SO_PASSCRED,允許或禁止SCM_CREDENTIALS 控制消息的接收。
該選項根據option_value的值,清或置sock->sk->sk_flag中的SOCK_PASSCRED位。
SO_TIMESTAMP,打開或關閉數據報中的時間戳接收。
該選項根據option_value的值,清或置sock->sk->sk_flag中的SOCK_RCVTSTAMP位,如果打開,則還需 設sock->sk->sk_flag中的SOCK_TIMESTAMP位,同時,將全局變量netstamp_needed加1。
SO_RCVLOWAT,設置接收數據前的緩衝區內的最小字節數。
在Linux中,緩衝區內的最小字節數是固定的,爲1。即將sock->sk->sk_rcvlowat固定賦值爲1。
SO_RCVTIMEO,設置接收超時時間。
該選項最終將接收超時時間賦給sock->sk->sk_rcvtimeo。
SO_SNDTIMEO,設置發送超時時間。
該選項最終將發送超時時間賦給sock->sk->sk_sndtimeo。
SO_BINDTODEVICE,將套接字綁定到一個特定的設備上。
該選項最終將設備賦給sock->sk->sk_bound_dev_if。
SO_ATTACH_FILTER和SO_DETACH_FILTER。
關於數據包過濾,它們最終會影響sk->sk_filter。
以上所介紹的都是在SOL_SOCKET層的一些套接字選項,如果超出這個範圍,給出一些不在這一level的選項作爲參數,最終會得到- ENOPROTOOPT的返回值。但以上的分析僅限於這些選項對sock-sk的值的影響,這些選項真正如何發揮作用,我們的探索道路將漫漫其修遠。
如果不在套接字級別上設置選項,即setsockopt系統調用的參數level不設爲SOL_SOCKET,那麼sys_setsockopt的 實現會直接調用sock->ops->setsockopt。對MY_PF_INET域的RAW協議來講,sock->ops = myinet_sockraw_ops,而myinet_sockraw_ops.setsockopt = sock_common_setsockopt。
而sock_common_setsockopt直接調用sock->sk->sk_prot->setsockopt。對於RAW協議來講,即myraw_setsockopt。
下面關注myraw_setsockopt的實現。對於RAW協議來講,level還可以有兩種取值:SOL_IP和SOL_RAW。 myraw_setsockopt首先檢查level是否爲SOL_IP,如果是,調用myip_setsockopt函數,該函數實現IP級別上的選 項,否則,爲SOL_RAW級別上的選項,SOL_RAW級別上只有一個選項,即ICMP_FILTER,在MY_IPPROTO_ICMP協議下有效。 它激活綁定到MY_IPPROTO_ICMP協議的一個用於myraw socket特殊的過濾器。該值對每種ICMP消息都有一個位(掩碼),可以把那種ICMP消息過濾掉,缺省時是不過濾ICMP消息。
對於ICMP_FILTER選項,myraw_setsockopt調用myraw_seticmpfilter函數,它把option_value賦給 sock->sk->filter,option_value是一個結構體:
struct icmp_filter {
__u32 data;
};
它是一個32位的位掩碼。
關於該位掩碼,我們目前知道的是最低位爲回顯應答的位掩碼,由於目前我們的MY_PF_INET域代碼還沒完善,我們在PF_INET域上進行測試,把下面的代碼添加到一個ping程序中,ping程序就收不到來自服務器的迴應包了:
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <linux/in.h>
#include <linux/icmp.h>
int main()
{
struct icmp_filter filter;
socklen_t size = sizeof( struct icmp_filter );
int fd = socket( PF_INET, SOCK_RAW, IPPROTO_ICMP );
if( fd < 0 )
perror("error: ");
getsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, &size );
printf("the filter: %x/n", filter.data );
filter.data = 1;
int err = setsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, sizeof(struct icmp_filter) );
if( err < 0 )
perror("error: ");
memset( &filter, 0, sizeof( struct icmp_filter ) );
getsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, &size );
printf("new filter: %x/n", filter.data);
close(fd);
return 0;
}
繼續講關於myraw_setsockopt的實現,如果level是SOL_IP,則調用myip_setsockopt函數。 myip_setsockopt的操作對像是struct socket sock的成員struct sock sk。並把sk強制轉化爲struct inet_sock: inet = inet_sk(sk)。
如果option_name在MRT_BASE和MRT_BASE+10之間,則調用myip_mroute_setsockopt函數,關於mroute,後面再給出分析。
IP_OPTIONS:設置將由該套接字發送的每個包的IP選項。
其option_value是一個結構體struct ip_options。該選項首先分配一個這樣的結構體,然後用這個結構體替代inet->opt指向的結構體。如果協議類型是 SOCK_STREAM的話,從struct tcp_sock *tp中,tp->ext_header_len減去舊的inet->opt->optlen, 再加上新的opt->optlen。最後調用tcp_sync_mss進行同步,有關TCP的一些細節,我們在實現TCP協議時再分析。
IP_PKTINFO:傳遞一條包含pktinfo結構(該結構提供一些來訪包的相關信息)的IP_PKTINFO輔助信息。
這個選項只對數據報類的套接字有效。
struct in_pktinfo
{
unsigned int ipi_ifindex; /* 接口索引 */
struct in_addr ipi_spec_dst; /* 路由目的地址 */
struct in_addr ipi_addr; /* 頭標識目的地址 */
};
ipi_ifindex指的是接收包的接口的唯一索引。ipi_spec_dst指的是路由表記錄中的目的地址,而ipi_addr 指的是包頭中的目的地址。如果給 sendmsg傳遞了IP_PKTINFO,那麼外發的包會通過在ipi_ifindex中指定的接口發送出去,同時把ipi_spec_dst設置爲目 的地址。
myip_setsockopt的代碼實現中只是根據option_value是否爲0,置或清inet->cmsg_flags的IP_CMSG_PKTINFO位。
IP_RECVTTL:
該選項根據option_value的值是否爲0,置或清inet->cmsg_flags的IP_CMSG_TTL位,具體用途,留待日後分析。
IP_RECVTOS:
如果打開了這個選項,則IP_TOS輔助信息會與來訪包一起傳遞。它包含一個字節用來指定包頭中的服務/優先>級字段的類型。該字節爲一個布爾整型 標識。該選項根據option_value的值是否爲0,置或清inet->cmsg_flags的IP_CMSG_TOS位。
IP_RECVOPTS:
用一條IP_OPTIONS控制信息傳遞所有來訪的IP選項給用戶。路由頭標識和其它選項已經爲本地主機填好.此選項不支持SOCK_STREAM套接 字。該選項根據option_value的值是否爲0,置或清inet->cmsg_flags的IP_CMSG_RECVOPTS位。
IP_RETOPTS:
等同於IP_RECVOPTS但是返回的是帶有時間戳的未處理的原始選項和在這段路由中未填入的路由記錄項目。該>選項根據 option_value的值是否爲0,置或清inet->cmsg_flags的IP_CMSG_RETOPTS位。
IP_TOS:
設置源於該套接字的每個IP包的Type-Of-Service(TOS 服務類型)字段。它被用來在網絡上區分包的優先級>。TOS是單字節的字段。定義了一些的標準TOS標識:IPTOS_LOWDELAY用來爲交互 式通信最小化延遲時間,IPTOS_THROUGHPUT用來優化吞吐量,IPTOS_RELIABILITY用來作可靠性優化, IPTOS_MINCOST應該被用作“填充數據”,對於這些數據,低速傳輸是無關緊要的。至多隻能聲明這些 TOS 值中的一個,其它的都是無效的,應當被清除。缺省時,Linux首先發送IPTOS_LOWDELAY數據報,但是確切的做法要看配置的排隊規則而定。一
些高優先級的層次可能會要求一個有效的用戶標識0或者CAP_NET_ADMIN能力。優先級也可以以於協議無關的方式通過( SOL_SOCKET, SO_PRIORITY )套接字選項來設置。
該選項的操作置inet->tos = val,sk->sk_priority = rt_tos2priority(val),同時,清sk->sk_dst_cache。
IP_TTL:設置從此套接字發出的包的當前生存時間字段。
該選項置inet->uc_ttl = option_value。
IP_HDRINCL:
該選項只對SOCK_RAW有效,如果提供的話,用戶可在用戶數據前面提供一個ip頭。該選項的操作根據option_value是否爲零,置inet->hdrincl爲1或0。
IP_MTU_DISCOVER:
爲套接字設置Path MTU Discovery setting(路徑MTU發現設置)。該選項的操作置inet->pmtudisc = option_value,option_value只允許取值0,1,2。
IP_SOL層上餘下的選項還有:
IP_RECVERR,IP_MULTICAST_TTL,IP_MULTICAST_LOOP,IP_MULTICAST_IF, IP_ADD_MEMBERSHIP,IP_DROP_MEMBERSHIP,IP_MSFILTER,IP_BLOCK_SOURCE, IP_UNBLOCK_SOURCE,IP_ADD_SOURCE_MEMBERSHIP,IP_DROP_SOURCE_MEMBERSHIP, MCAST_JOIN_GROUP,MCAST_LEAVE_GROUP,MCAST_JOIN_SOURCE_GROUP,
MCAST_LEAVE_SOURCE_GROUP,MCAST_BLOCK_SOURCE,MCAST_UNBLOCK_SOURCE, MCAST_MSFILTER,IP_ROUTER_ALERT,IP_FREEBIND,IP_IPSEC_POLICY, IP_XFRM_POLICY。
在涉及到相關內容時,再進行一一分析。