IP:網際協議
來源:程序員人生 發布時間:2014-10-20 08:00:00 閱讀次數:3408次
IP是TCP/IP協議族中最為核心的協議,所有的TCP,UDP,ICMP及IGMP數據都是以IP數據報的形式傳輸的

IP是一種不可靠的協議,也就是說,它并不能保證每個IP數據報都能成功地到達目的地,而只是提供最好的傳輸服務。如果發生某種錯誤(例如:某個路由器暫時用完了緩沖區),IP有一個簡單的錯誤處理算法,即丟棄該數據報,然后發送ICMP消息報給發送方。每個數據報的處理是相互獨立的,因此IP數據報可以不按發送順序接收。任何可靠性都必須由上層協議來提供,如TCP
IP數據報的輸入、輸出和轉發,數據報控制信息處理以及對端信息塊的管理涉及以下文件
include/net/ip.h 定義IP層相關的結構、宏和函數原型
include/linux/inetdevice.h 定義IPv4專用的網絡設備相關的結構、宏等
include/linux/errqueue.h 定義差錯處理相關的結構
include/net/inetpeer.h 定義對端信息塊的結構、宏和函數原型
include/net/dst.h 定義目的路由緩存相關結構、宏和函數原型
net/ipv4/ip_output.c IP數據報的輸出
net/ipv4/ip_sockglue.c IP層套接口選項
net/ipv4/ip_input.c IP數據報的輸入
net/ipv4/ip_forward.c IP數據報的轉發
net/ipv4/inetpeer.c 對端信息塊的管理
net/ipv4/af_inet.c 網絡層和傳輸層接口
IP數據報的報文格式
參見tcp/ip協議學習筆記(3)Internet
Protocol(IP)
IP數據報的輸入與輸出
網絡層處于傳輸層和鏈路層之間,同時還需要與路由表以及鄰居子系統打交道。在輸入數據時,提供輸入接口給鏈路層調用,并調用傳輸層的輸入接口將數據傳遞到傳輸層。在數據輸出時,提供輸出接口給傳輸層調用,并調用鏈路層的輸出接口將數據輸出到鏈路層。輸入和輸出過程中,都需要查找路由,通過netfilter處理等

IP的私有信息控制塊
IP層在SKB中有個信息控制塊struct inet_skb_parm結構,存儲在skb_buff結構中的cb成員中。IP層用宏IPCB訪問該結構以增強代碼的可讀性。這個私有的信息控制塊主要存儲IP選項以及在IP處理中需要設置的標志。
在IP層,無論是輸入還是輸出都需要處理IP選項。例如,輸入時,ip_rcv_option()會解析IP首部中的選項保存到inet_skb_parm結構的opt;在輸出時,ip_options_build()會根據inet_skb_parm結構的opt將其組織后在IP首部生成選項,而在轉發時,ip_forward_options()會根據選項做適當處理。
struct inet_skb_parm
{
struct ip_options opt; /* Compiled IP options */
unsigned char flags;
#define IPSKB_FORWARDED 1
#define IPSKB_XFRM_TUNNEL_SIZE 2
#define IPSKB_XFRM_TRANSFORMED 4
#define IPSKB_FRAG_COMPLETE 8
#define IPSKB_REROUTED 16
};
#define IPCB(skb) ((struct inet_skb_parm*)((skb)->cb))
IP層套接口選項
IP層套接口選項入口為ip_setsockopt()。該函數首先判斷選項的級別,如果不是SOL_IP級別,則返回無效的協議,否則調用do_ip_setsockopt()進行具體的選項處理。而在do_ip_setsockopt()中,組播路由相關的選項由ip_mroute_opt()來處理,其他選項則由該函數自己處理。
IP層套接口選項如下:
1)IP_OPTIONS
設置或獲取將由套接口發送的每個數據報IP首部中的IP選項,最長可達40B。該參數是一個指向數據報含選項和選項長度的存儲緩沖區的指針。
2)IP_PKTINFO
控制是否允許通過IP_PKTOPTIONS選項或recvmsg系統調用來獲取與本地地址等信息相關的IP_PKTOPTIONS選項
3)IP_TTL
設置輸出IP數據報的生存時間,有效值為1至255。IP數據報每經過一個路由器,路由器都會判斷TTL的值,并將該值遞減1,一旦發現其為0,即丟棄數據報,以免由于路由循環,造成數據報無休止地在網絡中的”游蕩“
具體選擇如下,不一一列舉了
include/linux/in.h
#define IP_TOS 1
#define IP_TTL 2
#define IP_HDRINCL 3
#define IP_OPTIONS 4
#define IP_ROUTER_ALERT 5
#define IP_RECVOPTS 6
#define IP_RETOPTS 7
#define IP_PKTINFO 8
#define IP_PKTOPTIONS 9
#define IP_MTU_DISCOVER 10
#define IP_RECVERR 11
#define IP_RECVTTL 12
#define IP_RECVTOS 13
#define IP_MTU 14
#define IP_FREEBIND 15
#define IP_IPSEC_POLICY 16
#define IP_XFRM_POLICY 17
#define IP_PASSSEC 18
#define IP_TRANSPARENT 19
/* BSD compatibility */
#define IP_RECVRETOPTS IP_RETOPTS
/* TProxy original addresses */
#define IP_ORIGDSTADDR 20
ipv4_devconf
ipv4_devconf結構是網絡設備接口的IPv4系統配置。在內核中有一個名為ipv4_devconf的系統全局變量,該配置對所有接口有效。另外,每個網絡設備的IP控制塊中也都存在一份配置,但該配置只對所在網絡設備有效。
enum
{
NET_IPV4_CONF_FORWARDING=1,
NET_IPV4_CONF_MC_FORWARDING=2,
NET_IPV4_CONF_PROXY_ARP=3,
NET_IPV4_CONF_ACCEPT_REDIRECTS=4,
NET_IPV4_CONF_SECURE_REDIRECTS=5,
NET_IPV4_CONF_SEND_REDIRECTS=6,
NET_IPV4_CONF_SHARED_MEDIA=7,
NET_IPV4_CONF_RP_FILTER=8,
NET_IPV4_CONF_ACCEPT_SOURCE_ROUTE=9,
NET_IPV4_CONF_BOOTP_RELAY=10,
NET_IPV4_CONF_LOG_MARTIANS=11,
NET_IPV4_CONF_TAG=12,
NET_IPV4_CONF_ARPFILTER=13,
NET_IPV4_CONF_MEDIUM_ID=14,
NET_IPV4_CONF_NOXFRM=15,
NET_IPV4_CONF_NOPOLICY=16,
NET_IPV4_CONF_FORCE_IGMP_VERSION=17,
NET_IPV4_CONF_ARP_ANNOUNCE=18,
NET_IPV4_CONF_ARP_IGNORE=19,
NET_IPV4_CONF_PROMOTE_SECONDARIES=20,
NET_IPV4_CONF_ARP_ACCEPT=21,
NET_IPV4_CONF_ARP_NOTIFY=22,
NET_IPV4_CONF_SRC_VMARK=24,
__NET_IPV4_CONF_MAX
};
struct ipv4_devconf
{
void *sysctl;
int data[__NET_IPV4_CONF_MAX - 1];
DECLARE_BITMAP(state, __NET_IPV4_CONF_MAX - 1);
};
NET_IPV4_CONF_FORWARDING
標識是否啟用IP數據報轉發功能
NET_IPV4_CONF_PROXY_ARP
標識是否啟用ARP代理功能
套接口錯誤隊列
在傳輸控制塊中有一個用于保存錯誤信息的隊列sk_error_queue,當ICMP接收到差錯信息或者UDP套接口和RAW套接口輸出報文出錯時,會產生描述錯誤信息的SKB添加到該隊列上。應用程序為能通過系統調用獲取詳細的錯誤信息,需要設置IP_RECVERR套接口選項,之后可通過參數flags為MSG_ERRQUEUE的recvmsg系統調用來獲取詳細的出錯信息。
錯誤信息的數據流及函數調用關系

在基于連接的套接口上,IP_RECVERR意義則會有所不同。并不保存錯誤信息到錯誤隊列中,而是立即傳遞所有接收到的錯誤信息給用戶進程。這對于基于短連接的TCP應用是很有用的,因為TCP要求快速的錯誤處理。需要注意的是,TCP沒有錯誤隊列,MSG_ERRQUEUE對于基于連接的套接口時無效的。
錯誤信息傳遞給用戶時,并不將錯誤信息作為報文的內容傳遞給用戶進程,而是以錯誤信息塊的形式保存在SKB控制塊中,通過SKB_EXT_ERR來訪問SKB控制塊中的錯誤信息塊
#define SKB_EXT_ERR(skb) ((struct sock_exterr_skb *) ((skb)->cb))
struct sock_exterr_skb
{
union {
struct inet_skb_parm h4;
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
struct inet6_skb_parm h6;
#endif
} header;
struct sock_extended_err ee;
u16 addr_offset;
__be16 port;
};
由于該錯誤也在IP層中處理,為了與IP控制塊兼用,錯誤信息塊的前部由IP控制塊組成,之后才是出錯信息
添加ICMP差錯信息
當接收到ICMP差錯信息時,ICMP模塊會根據出錯的原始數據報的傳輸層協議,調用傳輸層的差錯處理例程,而傳輸層的差錯處理例程會進而調用ip_icmp_error()將出錯信息添加到輸出該錯誤數據報的傳輸控制塊錯誤隊列上。
void ip_icmp_error(struct sock *sk, struct sk_buff *skb, int err,
__be16 port, u32 info, u8 *payload)
{
struct inet_sock *inet = inet_sk(sk);
struct sock_exterr_skb *serr;
if (!inet->recverr)
return;
skb = skb_clone(skb, GFP_ATOMIC);
if (!skb)
return;
serr = SKB_EXT_ERR(skb);
serr->ee.ee_errno = err;
serr->ee.ee_origin = SO_EE_ORIGIN_ICMP;
serr->ee.ee_type = icmp_hdr(skb)->type;
serr->ee.ee_code = icmp_hdr(skb)->code;
serr->ee.ee_pad = 0;
serr->ee.ee_info = info;
serr->ee.ee_data = 0;
serr->addr_offset = (u8 *)&(((struct iphdr *)(icmp_hdr(skb) + 1))->daddr) -
skb_network_header(skb);
serr->port = port;
if (skb_pull(skb, payload - skb->data) != NULL) {
skb_reset_transport_header(skb);
if (sock_queue_err_skb(sk, skb) == 0)
return;
}
kfree_skb(skb);
}
sk,輸出錯誤數據報的傳輸控制塊
skb,從ICMP模塊傳遞到傳輸層的ICMP差錯報文
err,發生錯誤的錯誤碼
port,對于UDP是出錯報文的目的端口,其他情況下都為0
info,錯誤信息的擴展信息
payload,指向產生ICMP差錯的原始數據報中應用層的內容
添加由本地產生的差錯信息
當UDP套接口或RAW套接口發送數據時,如果待發送數據的長度超過IP數據報能負載的長度,會調用ip_local_error()將數據報數據超長的出錯信息添加到輸出該出錯報文的傳輸控制塊錯誤隊列上。ip_local_error()的實現和ip_icmp_error()的實現類似,區別在于出錯信息來源為本地
void ip_local_error(struct sock *sk, int err, __be32 daddr, __be16 port, u32 info)
{
struct inet_sock *inet = inet_sk(sk);
struct sock_exterr_skb *serr;
struct iphdr *iph;
struct sk_buff *skb;
if (!inet->recverr)
return;
skb = alloc_skb(sizeof(struct iphdr), GFP_ATOMIC);
if (!skb)
return;
skb_put(skb, sizeof(struct iphdr));
skb_reset_network_header(skb);
iph = ip_hdr(skb);
iph->daddr = daddr;
serr = SKB_EXT_ERR(skb);
serr->ee.ee_errno = err;
serr->ee.ee_origin = SO_EE_ORIGIN_LOCAL;
serr->ee.ee_type = 0;
serr->ee.ee_code = 0;
serr->ee.ee_pad = 0;
serr->ee.ee_info = info;
serr->ee.ee_data = 0;
serr->addr_offset = (u8 *)&iph->daddr - skb_network_header(skb);
serr->port = port;
__skb_pull(skb, skb_tail_pointer(skb) - skb->data);
skb_reset_transport_header(skb);
if (sock_queue_err_skb(sk, skb))
kfree_skb(skb);
}
讀取錯誤信息
通常,recvmsg()是用來接收遠端發送到到所在套接口的數據的,但也可通過設置flags為MSG_ERRQUEUE來讀取傳輸控制塊錯誤隊列上的錯誤信息。在UDP套接口和RAW套接口的recvmsg()的實現中,先檢測是否存在MSG_ERRQUEUE標志,如果有,則直接調用ip_recv_error()從傳輸控制塊的錯誤隊列中讀取錯誤信息后返回。
/*
* Handle MSG_ERRQUEUE
*/
int ip_recv_error(struct sock *sk, struct msghdr *msg, int len)
{
struct sock_exterr_skb *serr;
struct sk_buff *skb, *skb2;
struct sockaddr_in *sin;
struct {
struct sock_extended_err ee;
struct sockaddr_in offender;
} errhdr;
int err;
int copied;
err = -EAGAIN;
skb = skb_dequeue(&sk->sk_error_queue);
if (skb == NULL)
goto out;
copied = skb->len;
if (copied > len) {
msg->msg_flags |= MSG_TRUNC;
copied = len;
}
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
if (err)
goto out_free_skb;
sock_recv_timestamp(msg, sk, skb);
serr = SKB_EXT_ERR(skb);
sin = (struct sockaddr_in *)msg->msg_name;
if (sin) {
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = *(__be32 *)(skb_network_header(skb) +
serr->addr_offset);
sin->sin_port = serr->port;
memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
}
memcpy(&errhdr.ee, &serr->ee, sizeof(struct sock_extended_err));
sin = &errhdr.offender;
sin->sin_family = AF_UNSPEC;
if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP) {
struct inet_sock *inet = inet_sk(sk);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
sin->sin_port = 0;
memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
if (inet->cmsg_flags)
ip_cmsg_recv(msg, skb);
}
put_cmsg(msg, SOL_IP, IP_RECVERR, sizeof(errhdr), &errhdr);
/* Now we could try to dump offended packet options */
msg->msg_flags |= MSG_ERRQUEUE;
err = copied;
/* Reset and regenerate socket error */
spin_lock_bh(&sk->sk_error_queue.lock);
sk->sk_err = 0;
skb2 = skb_peek(&sk->sk_error_queue);
if (skb2 != NULL) {
sk->sk_err = SKB_EXT_ERR(skb2)->ee.ee_errno;
spin_unlock_bh(&sk->sk_error_queue.lock);
sk->sk_error_report(sk);
} else
spin_unlock_bh(&sk->sk_error_queue.lock);
out_free_skb:
kfree_skb(skb);
out:
return err;
}
對端信息塊
對端信息塊由inet_peer結構描述,用來保存對端的一些信息,包括對端的地址以及傳輸層使用的時間戳等。主要用于在組裝IP數據報時防止分片攻擊,在建立TCP連接時檢測連接請求段是否有效以及其序號是否回繞。
struct inet_peer
{
/* group together avl_left,avl_right,v4daddr to speedup lookups */
struct inet_peer *avl_left, *avl_right;
__be32 v4daddr; /* peer's address */
__u16 avl_height;
__u16 ip_id_count; /* IP ID for the next packet */
struct list_head unused;
__u32 dtime; /* the time of last use of not
* referenced entries */
atomic_t refcnt;
atomic_t rid; /* Frag reception counter */
__u32 tcp_ts;
unsigned long tcp_ts_stamp;
};
對端信息塊的創建和查找
對端信息塊的創建和查找都是通過inet_getpeer()來實現的,由參數create來區分是創建還是查找
/* Called with or without local BH being disabled. */
struct inet_peer *inet_getpeer(__be32 daddr, int create)
{
struct inet_peer *p, *n;
struct inet_peer **stack[PEER_MAXDEPTH], ***stackptr;
/* Look up for the address quickly. */
read_lock_bh(&peer_pool_lock);
p = lookup(daddr, NULL);
if (p != peer_avl_empty)
atomic_inc(&p->refcnt);
read_unlock_bh(&peer_pool_lock);
if (p != peer_avl_empty) {
/* The existing node has been found. */
/* Remove the entry from unused list if it was there. */
unlink_from_unused(p);
return p;
}
if (!create)
return NULL;
/* Allocate the space outside the locked region. */
n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC);
if (n == NULL)
return NULL;
n->v4daddr = daddr;
atomic_set(&n->refcnt, 1);
atomic_set(&n->rid, 0);
n->ip_id_count = secure_ip_id(daddr);
n->tcp_ts_stamp = 0;
write_lock_bh(&peer_pool_lock);
/* Check if an entry has suddenly appeared. */
p = lookup(daddr, stack);
if (p != peer_avl_empty)
goto out_free;
/* Link the node. */
link_to_pool(n);
INIT_LIST_HEAD(&n->unused);
peer_total++;
write_unlock_bh(&peer_pool_lock);
if (peer_total >= inet_peer_threshold)
/* Remove one less-recently-used entry. */
cleanup_once(0);
return n;
out_free:
/* The appropriate node is already in the pool. */
atomic_inc(&p->refcnt);
write_unlock_bh(&peer_pool_lock);
/* Remove the entry from unused list if it was there. */
unlink_from_unused(p);
/* Free preallocated the preallocated node. */
kmem_cache_free(peer_cachep, n);
return p;
}
對端信息塊的刪除
當使用完對端信息塊之后,需要將其刪除并釋放。實際上,inet_putpeer()只是將該對端信息塊添加到unused_peers鏈表中,表示該對端信息塊當前沒有被使用。而真正的刪除和釋放,由垃圾回收機制來處理
void inet_putpeer(struct inet_peer *p)
{
spin_lock_bh(&inet_peer_unused_lock);
if (atomic_dec_and_test(&p->refcnt)) {
list_add_tail(&p->unused, &unused_peers);
p->dtime = (__u32)jiffies;
}
spin_unlock_bh(&inet_peer_unused_lock);
}
垃圾回收
處理垃圾回收的方式有兩種---同步和異步。同步方式通常是在創建對端信息塊時發現當前對端信息塊數達到inet_peer_threshold時觸發,而異步方式則是在定時器超時觸發。
正在使用的對端信息塊是不會過期的,只有閑置的對端信息塊才有可能過期,因為對端信息塊一旦閑置,就會被添加到unused_peers鏈表中,并記錄閑置的時間。在同步或異步清理是,一旦發現閑置時間達到閾值,對端信息塊就會過期。對端信息塊的過期與inet_peer_minttl、inet_peer_maxttl和inet_peer_threshold有關
同步清理
struct inet_peer *inet_getpeer(__be32 daddr, int create)
{
...
if (peer_total >= inet_peer_threshold)
/* Remove one less-recently-used entry. */
cleanup_once(0);
...
}
異步清理
同步回收來及用于處理內存壓力較大的特殊情況,事實上這種情況比較少見,同時也是非常影響性能的。因此為了避免這種特殊情況的出現或降低其出現的概率,使用peer_periodic_timer定時器來進行周期性回收,該定時器處理例程為peer_check_expire()。其時間間隔在inet_initpeers()中設置
/* Called with local BH disabled. */
static void peer_check_expire(unsigned long dummy)
{
unsigned long now = jiffies;
int ttl;
if (peer_total >= inet_peer_threshold)
ttl = inet_peer_minttl;
else
ttl = inet_peer_maxttl
- (inet_peer_maxttl - inet_peer_minttl) / HZ *
peer_total / inet_peer_threshold * HZ;
while (!cleanup_once(ttl)) {
if (jiffies != now)
break;
}
/* Trigger the timer after inet_peer_gc_mintime .. inet_peer_gc_maxtime
* interval depending on the total number of entries (more entries,
* less interval). */
if (peer_total >= inet_peer_threshold)
peer_periodic_timer.expires = jiffies + inet_peer_gc_mintime;
else
peer_periodic_timer.expires = jiffies
+ inet_peer_gc_maxtime
- (inet_peer_gc_maxtime - inet_peer_gc_mintime) / HZ *
peer_total / inet_peer_threshold * HZ;
add_timer(&peer_periodic_timer);
}
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈