IP层

使用的数据结构:

iphdr:

   1:  struct iphdr {
   2:  #if defined(__LITTLE_ENDIAN_BITFIELD)
   3:      __u8    ihl:4,
   4:          version:4;
   5:  #elif defined (__BIG_ENDIAN_BITFIELD)
   6:      __u8    version:4,
   7:            ihl:4;
   8:  #else
   9:  #error    "Please fix <asm/byteorder.h>"
  10:  #endif
  11:      __u8    tos;
  12:      __be16    tot_len;
  13:      __be16    id;
  14:      __be16    frag_off;
  15:      __u8    ttl;
  16:      __u8    protocol;
  17:      __sum16    check;
  18:      __be32    saddr;
  19:      __be32    daddr;
  20:      /*The options start here. */
  21:  };

version:协议的版本

ihl:报头的长度,以32位为单位

tos:这个8位字段由3个子字段组成

tot_len:封包长度(包括报头),以字节为单位

id:封包标识符

df,mf,片段偏移量:这三个字段以及识别字段会由ip协议的分段/重组功能使用。

TTL:此字段假定是代表自IP封包传输后多少秒数就被丢弃(如果还没抵达最终目的地)。然而,由于路由器会将其递减,而不管其花了多少时间转发该IP包,因此此字段实际上代表的是简单的跳点计数。

protocol:此字段代表较高层的协议标识符。/etc/protocol包含了一部分列表

check:确保IP报头在传输之后依然准确

saddr,daddr:源(发送者)和目的地(接收者)的IP地址

options:可选信息。

ip_options:

   1:  struct ip_options {
   2:      __u32        faddr;            /* Saved first hop address */
   3:      unsigned char    optlen;
   4:      unsigned char srr;
   5:      unsigned char rr;
   6:      unsigned char ts;
   7:      unsigned char is_setbyuser:1,    /* Set by setsockopt?              */
   8:                is_data:1,    /* Options in __data, rather than skb */
   9:                is_strictroute:1, /* Strict source route              */
  10:                srr_is_hit:1,    /* Packet destination addr was our one*/
  11:                is_changed:1,    /* IP checksum more not valid          */
  12:                rr_needaddr:1,    /* Need to record addr of outgoing dev*/
  13:                ts_needtime:1,    /* Need to record timestamp          */
  14:                ts_needaddr:1;    /* Need to record addr of outgoing dev*/
  15:      unsigned char router_alert;
  16:      unsigned char __pad1;
  17:      unsigned char __pad2;
  18:      unsigned char __data[0];
  19:  };

此结构代表的是必须被传输或转发的封包的选项。选项存储在此结构中是因为比起从IP报头中相应部分读取而言,读取此结构比较容易一些。

optlen:这组选项的长度

faddr:只对已传输封包(也就是那些本地产生的封包)及使用来源地路由的封包有意义。faddr的值会设成提供来源地路由使用的ip地址列表中的第一个地址。

srr:包含有报头中“source route”选项的偏移量。如果没有此选项,则其值为零。如果封包使用源路由,而进行接收的接口ip地址是来源地路由表中的地址之一,则srr_is_hit为真、

rr:当rr为非零时,record route就是IP选项之一,而该字段的值就代表该选项在IP报头中起始的偏移量

rr_needaddr:当rr_needaddr为真时,record route是ip选项之一,而且报头中还有空间可容纳另一条路径。因此,当前节点应该把外出接口的ip地址拷贝到rr在ip报头中所指的偏移量。

ts:当ts为非零时,timestamp是ip选项之一,而该字段的值就代表该选项在ip报头中起始的偏移量。此字段和ts_needaddr及ts_needtime一起合用。

is_changed:如果ip报头已被修改,就会设定。知道这件事非常有用处,因为如果封包要被转发,该字段就指出ip校验和必须重新计算。

is_setbyuser:此字段只对传输封包有意义,当选项从用户空间以setsockopt系统调用传递时,就会设定。

is_data,_data.这些字段用在两种情况:当本地节点传输一个本地产生的封包时,以及当本地节点回复一条ICMP回应请求时。就这些情况而言,is_data为真,而_data会指向一个区域,此区域包含要附加到ip报头的选项。[0]的定义方式是很常见的惯例,用于替指针保留空间。转发封包时,选项会放在匹配的skb缓冲区内。

ts_needtime:当此选项为真时,timestamp就是ip选项之一,而且报头中依然有空间可容纳另一个时间戳,因此,当前节点应该把传输时间加入ip报头,而位置就是ts所指定的偏移量

ts_needaddr:与ts和ts_needtime一起使用,以指出出口设备的ip地址也应该拷贝到ip头。

router_alert:当此选项为真时,router alert就是ip选项之一。

ipcm_cookie:

   1:  struct ipcm_cookie {
   2:      __be32            addr;
   3:      int            oif;
   4:      struct ip_options_rcu    *opt;
   5:      __u8            tx_flags;
   6:  };

该结构结合了各种传输封包所需的信息,目的地ip是addr,出口设备是oif(如果有的话),而ip选项是ip_options结构。注意,addr是唯一一定会设定的字段。如果没有对使用哪个设备做限制,oif就是0。

ipq:

   1:  struct ipq {
   2:      struct inet_frag_queue q;
   3:   
   4:      u32        user;
   5:      __be32        saddr;
   6:      __be32        daddr;
   7:      __be16        id;
   8:      u8        protocol;
   9:      u8        ecn; /* RFC3168 support */
  10:      int             iif;
  11:      unsigned int    rid;
  12:      struct inet_peer *peer;
  13:  };
   1:  struct inet_frag_queue {
   2:      struct hlist_node    list;
   3:      struct netns_frags    *net;
   4:      struct list_head    lru_list;   /* lru list member */
   5:      spinlock_t        lock;
   6:      atomic_t        refcnt;
   7:      struct timer_list    timer;      /* when will this queue expire? */
   8:      struct sk_buff        *fragments; /* list of received fragments */
   9:      struct sk_buff        *fragments_tail;
  10:      ktime_t            stamp;
  11:      int            len;        /* total length of orig datagram */
  12:      int            meat;
  13:      __u8            last_in;    /* first/last segment arrived? */
  14:   
  15:  #define INET_FRAG_COMPLETE    4
  16:  #define INET_FRAG_FIRST_IN    2
  17:  #define INET_FRAG_LAST_IN    1
  18:  };

user,这是ip封包为什么重组的原因,等于间接说出是哪个内核子系统要求重组。IP_DEFRAG_XXX容许值列表在include/net/ip.h中。最常见的是IP_DEFRAG_LOCAL_DELIVER,当重组的入口封包要传递给本地时,就会使用。

saddr,daddr,id,protocol,这些参数代表源地ip地址,目的地ip地址,ip封包id,以及l4协议标识符。这四个参数会指出片段所属的原有IP封包。因此,hash函数也会使用这些参数能够在hash表中做最佳的分布。

last_in,存储三个标志,可能的值是:

COMPLETE,所有片段都已接收,因此,可以连接起来获取原有ip封包。该标志也可用于标识那些被选中要删除的ipq结构。

FIRST_IN,片段中的第一个片段(offset=0的片段)已接收到了。第一个片段就是唯一一个携带原有ip封包中所有选项的片段。

LAST_IN,片段中最后一个片段(MF=0的片段)已经接收到了。最后一个片段很重要,因为这个片段会告知我们原有ip封包的尺寸。

fragments,是一个链表,内容是目前已接收的片段

len,偏移量最大的片段的结尾处的偏移量。当最后一个片段(MF=0的片段)已接收时,len会指出原有ip封包的尺寸。

meat,代表我们到目前为止接收了原有封包多少字节。当前值和len相同时,表示该封包已完全接收。

stamp,最后一个片段接收的时间

iif,传送最后一个片段的设备的id。当一个片段链表到期时,该字段可用于决定由哪一个设备传输FRAGMENTATION REASSEMBLY TIMEOUT ICMP消息。

timer:ip片段不能永久存在内存中,而且当重组不可能时,一段时间后,就应该删除。该字段就是处理此事的定时器。

refcnt,计数器用于记录对该封包的外部引用。它的用途的例子如:timer定时器递增refcnt,来确保当定时器依然处于工作中是,没有人可以释放ipq结构。否则该定时器可能会在过期时存取已不存在的数据结构。

lock,保护此结构以避免处于竞争条件下。例如,不用ip片段由不同nic同时接收,而且由不同cpu处理,就会发生竞争。

list_head lru_list,所有的ipq结构都会放在一个全局链表ipq_lru_list中,而排序的条件是根据最近最少使用原则。执行垃圾收集时,这个链表就有用。该字段可用于把ipq结构连接至这样的一个链表。

inet_peer:

   1:  struct inet_peer {
   2:      /* group together avl_left,avl_right,v4daddr to speedup lookups */
   3:      struct inet_peer __rcu    *avl_left, *avl_right;
   4:      struct inetpeer_addr    daddr;
   5:      __u32            avl_height;
   6:   
   7:      u32            metrics[RTAX_MAX];
   8:      u32            rate_tokens;    /* rate limiting for ICMP */
   9:      int            redirect_genid;
  10:      unsigned long        rate_last;
  11:      unsigned long        pmtu_expires;
  12:      u32            pmtu_orig;
  13:      u32            pmtu_learned;
  14:      struct inetpeer_addr_base redirect_learned;
  15:      /*
  16:       * Once inet_peer is queued for deletion (refcnt == -1), following fields
  17:       * are not available: rid, ip_id_count, tcp_ts, tcp_ts_stamp
  18:       * We can share memory with rcu_head to help keep inet_peer small.
  19:       */
  20:      union {
  21:          struct {
  22:              atomic_t            rid;        /* Frag reception counter */
  23:              atomic_t            ip_id_count;    /* IP ID for the next packet */
  24:              __u32                tcp_ts;
  25:              __u32                tcp_ts_stamp;
  26:          };
  27:          struct rcu_head         rcu;
  28:          struct inet_peer    *gc_next;
  29:      };
  30:   
  31:      /* following fields might be frequently dirtied */
  32:      __u32            dtime;    /* the time of last use of not referenced entries */
  33:      atomic_t        refcnt;
  34:  };

内核会为最近联系过的每台远程主机都维护一个此结构的实例。所有的inet_peer实例都放在一棵avl树中,而该树是针对频繁查询做了最优化的结构。

avl_left,avl_right,他们是指向两个子树的左右指针。

avl_height,avl树的高度

gc_next,用于垃圾收集

refcnt,该元素的引用计数值,使用此结构者有路由子系统及tcp层

ipstats_mib:

   1:  struct ipstats_mib {
   2:      /* mibs[] must be first field of struct ipstats_mib */
   3:      u64        mibs[IPSTATS_MIB_MAX];
   4:      struct u64_stats_sync syncp;
   5:  };

收集有关资料的统计数据

in_device:

   1:  struct in_device {
   2:      struct net_device    *dev;
   3:      atomic_t        refcnt;
   4:      int            dead;
   5:      struct in_ifaddr    *ifa_list;    /* IP ifaddr chain        */
   6:      struct ip_mc_list __rcu    *mc_list;    /* IP multicast filter chain    */
   7:      int            mc_count;    /* Number of installed mcasts    */
   8:      spinlock_t        mc_tomb_lock;
   9:      struct ip_mc_list    *mc_tomb;
  10:      unsigned long        mr_v1_seen;
  11:      unsigned long        mr_v2_seen;
  12:      unsigned long        mr_maxdelay;
  13:      unsigned char        mr_qrv;
  14:      unsigned char        mr_gq_running;
  15:      unsigned char        mr_ifc_count;
  16:      struct timer_list    mr_gq_timer;    /* general query timer */
  17:      struct timer_list    mr_ifc_timer;    /* interface change timer */
  18:   
  19:      struct neigh_parms    *arp_parms;
  20:      struct ipv4_devconf    cnf;
  21:      struct rcu_head        rcu_head;
  22:  };

in_device结构是为网络设备存储所有ipv4相关配置。例如,用户用ifconfig或ip命令所做的变更。此结构会通过net_device->ip_ptr连接到net_device结构,而且可以用in_dev_get和__in_dev_get取出。这两个函数的差别在于前者会处理所有必要的上锁问题,而后者会假定调用者已处理上锁问题。因为in_dev_get成功时,其内部会对in_dev结构的引用计数递增,而其增加者用完该结构后,就应该以in_dev_put递减引用计数值。

dev,指回相配的net_device结构的指针

refcnt,引用计数值。只用其为0时,该结构才可被释放

dead,此字段设定时就是把设备标识成已死。这可用于检查一些情况。例如:项目无法被销毁,因其引用计数值不为零。但是,销毁动作早就启动了。触发in_device结构的两个最常见事件是:

  1. 设备的注销
  2. 把设备上最后一个配置的ip地址删除掉

ifa_list,设备上所配置的ipv4地址列表。in_ifaddr实例会按范围排序(范围大者排前面),而相同范围的元素则按地址类型排序(主要地址排前面)。

rcu_head,由rcu机制使用时限互斥。其完成工作如同锁一般

剩余的字段由多播程序使用。

in_ifaddr:

   1:  struct in_ifaddr {
   2:      struct hlist_node    hash;
   3:      struct in_ifaddr    *ifa_next;
   4:      struct in_device    *ifa_dev;
   5:      struct rcu_head        rcu_head;
   6:      __be32            ifa_local;
   7:      __be32            ifa_address;
   8:      __be32            ifa_mask;
   9:      __be32            ifa_broadcast;
  10:      unsigned char        ifa_scope;
  11:      unsigned char        ifa_flags;
  12:      unsigned char        ifa_prefixlen;
  13:      char            ifa_label[IFNAMSIZ];
  14:  };

ifa_next,指向链表中下一个元素的指针。此链表中包含了设备上所配置的所有地址。

ifa_dev,指回相配的in_device结构的指针

ifa_local,ifa_address,这两个字段的值取决于该地址是否被指派给一个隧道接口。如果是,ifa_local和ifa_address就是隧道的本地和远程地址。如果不是,则两者所含都是本地接口的地址。

ifa_mask,ifa_prefixlen,ifa_mask就是和该地址相配的网络掩码。ifa_prefixlen是构成此网络掩码的1的数目。

ifa_broadcast,广播地址

ifa_scope,地址的范围。默认是rt_scope_universe,而此字段通常设成ifconfig/ip所设定值,不过,也可以选不同的值。主要例外是位域范围127.x.x.x里的地址,其范围为RT_SCOPE_HOST

ifa_flags,IFA_F_SECONDARY,当一个新地址加至一台设备时,如果该设备已有另一个地址具有相同子网络,则该地址就会被视为次要地址。

rcu_head,由rcu使用实现互斥。

cork:

   1:  struct inet_cork {
   2:      unsigned int        flags;
   3:      __be32            addr;
   4:      struct ip_options    *opt;
   5:      unsigned int        fragsize;
   6:      struct dst_entry    *dst;
   7:      int            length; /* Total length of all frames */
   8:      struct page        *page;
   9:      u32            off;
  10:      u8            tx_flags;
  11:  };

flags,目前ipv4只有一个标识可以设定,IPCORK_OPT。当此标识设定时,意味着opt中选项。

fragsize,产生的数据片段尺寸。此尺寸包括有效负荷和l3报头,而且通常就是pmtu。

opt,要用的ip选项

dst,用于传输ip封包的路由表缓存项目

length,ip封包的尺寸

skb_frag_t:

   1:  struct skb_frag_struct {
   2:      struct {
   3:          struct page *p;
   4:      } page;
   5:  #if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
   6:      __u32 page_offset;
   7:      __u32 size;
   8:  #else
   9:      __u16 page_offset;
  10:      __u16 size;
  11:  #endif
  12:  };

page,指向内存页面的指针

page_offset,偏移量,相对于页面的开端,也就是此片段起始处

size,此片段的尺寸。

封包的一般性处理:

ip协议的初始化:

ipv4协议是由ip_init初始化的,由于ipv4功能无法从内核中删除,也就没有所谓的ip_uninit函数。

   1:  void __init ip_init(void)
   2:  {
   3:      ip_rt_init();
   4:      inet_initpeers();
   5:   
   6:  #if defined(CONFIG_IP_MULTICAST) && defined(CONFIG_PROC_FS)
   7:      igmp_mc_proc_init();
   8:  #endif
   9:  }

其完成的主要任务:

  • 初始化路由子系统,包括与协议无关的缓存
  • 初始化用于管理IP端点的基础架构

开机期间,ip_init会有inet_init调用,而inet_init会处理所有与ipv4有关的子系统的初始化,包括l4协议在内。

和netfilter互动:

基本上,防火墙在网络堆栈程序中的某些地方都有钩子函数,当封包或内核吻合某些条件时,封包总是会通过那些钩子函数。在这些地方,防火墙允许网络管理员操作流量的内容或配置。内核中的这些位置包括:

  • 封包接收
  • 封包转发(路由决策前)
  • 封包转发(路由决策后)
  • 封包传输

就刚才所列的每种情况而言,负责其运算的函数会分成两部分,通常称为do_something和do_something_finish。do_something值包含一些健康检查,也许还有一些处理工作。做实事的程序位于do_something_finish或do_something2之内。do_something最后会调用netfilter函数NF_HOOK,而且把调用来自于哪个点位传进去,然后用户以iptables命令配置的过滤规则决定丢弃或拒绝。如果没有规则可用,则函数do_something_finish就会被执行。通常的调用形式如下所示:

NF_HOOK(PROTOCOL, HOOK_POSITION_IN_THE_STACK,SKB_BUFFER, IN_DEVICE, OUT_DEVICE, do_something_finish)

NF_HOOK的输出值可以是下列值之一:

  • 当do_something_finish稍后被执行时,就是其输出值
  • 如果因为某个过滤器的缘故使得SKB_BUFFER被丢弃,就返回-eperm
  • 如果没有足够的内存执行过滤运算,就返回-enomem

与路由子系统的交互:

ip层在好几个地方都必须和路由表交互,下面是几个常用的函数:

ip_route_input,决定输入封包的命运。

ip_route_output_flow,传输封包前使用

dst_pmtu,给定一个路由表缓存项目,就可返回相关的pmtu

ip_route_xxx函数会查询路由表,并根据一组字段做出决策:目的ip地址,源ip地址,服务类型,进行接收的设备,可进行传输的设备列表,影响这些函数返回结果的因素中较为复杂的两个,是路由策略的存在以及防火墙的存在。

这些函数会把路由表查询的结果存储在skb->dst中。此结构包含好几个字段,包括完成封包的接收或传输所需调用的输入和输出函数指针。如果查询失败,ip_route_xxx函数会返回负值。

处理输入ip包:

ip_rcv是一个经典的两阶段处理的典型案例。其工作就是对封包做健康检查,然后调用netfilter钩子。大多数处理将在ip_rcv_finish里发生。

   1:  int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
   2:  {
   3:      const struct iphdr *iph;
   4:      u32 len;
   5:   
   6:      /* When the interface is in promisc. mode, drop all the crap
   7:       * that it receives, do not try to analyse it.
   8:       */
   9:      if (skb->pkt_type == PACKET_OTHERHOST)
  10:          goto drop;
  11:   
  12:   
  13:      IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
  14:   
  15:      if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
  16:          IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
  17:          goto out;
  18:      }
  19:   
  20:      if (!pskb_may_pull(skb, sizeof(struct iphdr)))
  21:          goto inhdr_error;
  22:   
  23:      iph = ip_hdr(skb);
  24:   
  25:      /*
  26:       *    RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
  27:       *
  28:       *    Is the datagram acceptable?
  29:       *
  30:       *    1.    Length at least the size of an ip header
  31:       *    2.    Version of 4
  32:       *    3.    Checksums correctly. [Speed optimisation for later, skip loopback checksums]
  33:       *    4.    Doesn't have a bogus length
  34:       */
  35:   
  36:      if (iph->ihl < 5 || iph->version != 4)
  37:          goto inhdr_error;
  38:   
  39:      if (!pskb_may_pull(skb, iph->ihl*4))
  40:          goto inhdr_error;
  41:   
  42:      iph = ip_hdr(skb);
  43:   
  44:      if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
  45:          goto inhdr_error;
  46:   
  47:      len = ntohs(iph->tot_len);
  48:      if (skb->len < len) {
  49:          IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
  50:          goto drop;
  51:      } else if (len < (iph->ihl*4))
  52:          goto inhdr_error;
  53:   
  54:      /* Our transport medium may have padded the buffer out. Now we know it
  55:       * is IP we can trim to the true length of the frame.
  56:       * Note this now means skb->len holds ntohs(iph->tot_len).
  57:       */
  58:      if (pskb_trim_rcsum(skb, len)) {
  59:          IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
  60:          goto drop;
  61:      }
  62:   
  63:      /* Remove any debris in the socket control block */
  64:      memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
  65:   
  66:      /* Must drop socket now because of tproxy. */
  67:      skb_orphan(skb);
  68:   
  69:      return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
  70:                 ip_rcv_finish);
  71:   
  72:  inhdr_error:
  73:      IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
  74:  drop:
  75:      kfree_skb(skb);
  76:  out:
  77:      return NET_RX_DROP;
  78:  }

计算完校验和之后,还有另外两个健康检查:

  • 确保缓冲区(即已接收的封包)长度大于或等于ip报头中记录的长度
  • 确保封包的尺寸至少和ip报头的尺寸一样大

这里我们必须说明为什么需要者两项检查。第一项是源自于l2协议会填充有效负荷,所以,在ip有效负载之后可能有多余的字节(例如,当数据帧的l2尺寸小于协议所要求的最小值时就会发生此事。ethernet数据帧的最小数据帧长度为64字节)。在这种情况下,封包看起来会比ip报头中记录的长度大一点。第二项检查源于ip报头不能分段的事实,因此,每个ip分段必须至少包含一个ip报头。只有在很罕见的情况下,这项检查才会失败。也就是说,用损坏的封包计算而得的校验和碰巧产生与最初的封包相同的校验和。我们说过,l2协议可能会补满封包以达到特定最小长度。函数pskb_trim_rcsum会检查是否有此类事情发生,然后,如果真的有,就会用__pskb_trim把封包剪成正确尺寸,在让l4校验和失效,以免进行接收的nic已计算出此值。最后经过netfilter,其基本含义为,skb是从设备dev所接收的包,请检查该包是否可以继续其旅程,或者它是否需要改变。别忘了我们是从网络堆栈的NF_IP_PRE_ROUTING位置向你要求这件事情的,也就是说封包已接收但是没有做路由决策。如果你不丢弃该封包,就执行ip_rcv_finish.

   1:  static int ip_rcv_finish(struct sk_buff *skb)
   2:  {
   3:      const struct iphdr *iph = ip_hdr(skb);
   4:      struct rtable *rt;
   5:   
   6:      /*
   7:       *    Initialise the virtual path cache for the packet. It describes
   8:       *    how the packet travels inside Linux networking.
   9:       */
  10:      if (skb_dst(skb) == NULL) {
  11:          int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
  12:                             iph->tos, skb->dev);
  13:          if (unlikely(err)) {
  14:              if (err == -EHOSTUNREACH)
  15:                  IP_INC_STATS_BH(dev_net(skb->dev),
  16:                          IPSTATS_MIB_INADDRERRORS);
  17:              else if (err == -ENETUNREACH)
  18:                  IP_INC_STATS_BH(dev_net(skb->dev),
  19:                          IPSTATS_MIB_INNOROUTES);
  20:              else if (err == -EXDEV)
  21:                  NET_INC_STATS_BH(dev_net(skb->dev),
  22:                           LINUX_MIB_IPRPFILTER);
  23:              goto drop;
  24:          }
  25:      }
  26:   
  27:  #ifdef CONFIG_IP_ROUTE_CLASSID
  28:      if (unlikely(skb_dst(skb)->tclassid)) {
  29:          struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
  30:          u32 idx = skb_dst(skb)->tclassid;
  31:          st[idx&0xFF].o_packets++;
  32:          st[idx&0xFF].o_bytes += skb->len;
  33:          st[(idx>>16)&0xFF].i_packets++;
  34:          st[(idx>>16)&0xFF].i_bytes += skb->len;
  35:      }
  36:  #endif
  37:   
  38:      if (iph->ihl > 5 && ip_rcv_options(skb))
  39:          goto drop;
  40:   
  41:      rt = skb_rtable(skb);
  42:      if (rt->rt_type == RTN_MULTICAST) {
  43:          IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,
  44:                  skb->len);
  45:      } else if (rt->rt_type == RTN_BROADCAST)
  46:          IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,
  47:                  skb->len);
  48:   
  49:      return dst_input(skb);
  50:   
  51:  drop:
  52:      kfree_skb(skb);
  53:      return NET_RX_DROP;
  54:  }

ip_rcv所做的仅是封包的基本健康检查。所以,当ip_rcv_finish被调用时,就会处理主要的工作,例如:

  • 决定封包是否必须本地传递或者转发。就第二种情况而言,则必须找到出口设备和下个跳点
  • 分析和处理一些ip选项。然而,并非所有ip选项都在此处理,这说明转发情况时就会知道。

skb->dst可能包含封包通往其目的地的路由信息。如果尚未得知该信息,此函数会询问路由子系统该把封包传送到哪儿,而且,如果路由子系统说目的地无法抵达,则该封包就会丢弃。接着,此函数会更新一些traffic control所用的统计信息。如果ip报头的长度大于20字节,表示有一些选项需要处理。则,处理选项。函数最后会调用dst_input,而dst_input实际上会调用存储于skb缓冲区的dst字段的函数。skb->dst的初始化不是在ip_rcv_finish的开端,就是在ip_options_rcv_srr的尾端。skb->dst->input会设置成ip_local_deliver或ip_forward,这取决于封包的目的地地址。因此,调用dst_input时,就可以完成封包处理。

IP选项:

   1:  static inline int ip_rcv_options(struct sk_buff *skb)
   2:  {
   3:      struct ip_options *opt;
   4:      const struct iphdr *iph;
   5:      struct net_device *dev = skb->dev;
   6:   
   7:      /* It looks as overkill, because not all
   8:         IP options require packet mangling.
   9:         But it is the easiest for now, especially taking
  10:         into account that combination of IP options
  11:         and running sniffer is extremely rare condition.
  12:                            --ANK (980813)
  13:      */
  14:      if (skb_cow(skb, skb_headroom(skb))) {
  15:          IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
  16:          goto drop;
  17:      }
  18:   
  19:      iph = ip_hdr(skb);
  20:      opt = &(IPCB(skb)->opt);
  21:      opt->optlen = iph->ihl*4 - sizeof(struct iphdr);
  22:   
  23:      if (ip_options_compile(dev_net(dev), opt, skb)) {
  24:          IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
  25:          goto drop;
  26:      }
  27:   
  28:      if (unlikely(opt->srr)) {
  29:          struct in_device *in_dev = __in_dev_get_rcu(dev);
  30:   
  31:          if (in_dev) {
  32:              if (!IN_DEV_SOURCE_ROUTE(in_dev)) {
  33:                  if (IN_DEV_LOG_MARTIANS(in_dev) &&
  34:                      net_ratelimit())
  35:                      printk(KERN_INFO "source route option %pI4 -> %pI4\n",
  36:                             &iph->saddr, &iph->daddr);
  37:                  goto drop;
  38:              }
  39:          }
  40:   
  41:          if (ip_options_rcv_srr(skb))
  42:              goto drop;
  43:      }
  44:   
  45:      return 0;
  46:  drop:
  47:      return -1;
  48:  }

当ip报头大于20字节,表示有一些选项要处理。此时,skb_cow(copy on write)会被调用,如果缓冲区与别人共享,就会做出缓冲区的副本。对缓冲区具有排他拥有权是必要的,因为我们要处理那些选项,而且可能必须修改ip报头。

ip_option_compile用于解读报头中所携带的ip选项。该函数的结构存储在一个struct inet_skb_parm类型的数据结构中,而且可以由宏IPCB存取。注意,ip_options_compile只会检查那些选项是否正确,然后将那些选项存储在skb->cb所指的私有数据域字段ip_option结构内。此函数不会处理任何选项,相反,接下来的程序片段会处理部分工作。当设备允许ip源路由时,程序就会调用ip_options_rcv_srr来设定skb->dst,然后决定要如何处理封包,也就是说,要决定使用哪个设备把该封包转发至来源地路由列表中的下一个跳点。

ip选项管理的主要api,定义在ip_options.C中。

ip_options_compile,分析ip报头中的一群选项,然后相应地对一个ip_options结构的实例做初始化。此结构稍后会用于处理这些选项;还包括一些标识和指针,用于告知路由子系统中负责处理转发的部分,ip报头选项空间中该写入什么以及写入何处。

ip_options_build,对ip报头中专属那些选项的部分做初始化(根据输入的ip_options结构)。传输本地产生的封包时,就会用到此函数。根据输入参数,该函数可以区分出那些片段并进行相应的处理:把那些不用拷贝到每个片段的选项从该片段的报头中删除,然后改以空选项覆盖。此外,也会清掉ip_options结构的标志,用于通知必须新增一个时间戳或一个地址至那些选项中。

ip_options_fragment,因为第一个片段是唯一继承原有封包的所有选项的片段,所以,其报头尺寸应该大于或等于后续片段的尺寸。然后,linux简化了此条规则。linux让所有片段都保持相同的报头尺寸,让分段流程更为简单有效。其做法是拷贝原有报头及其所有选项,然后,除了第一个片段外,对其他所有片段都已空选项覆写那些不需要重复的选项,然后清掉和其相关的ip_options结构的标识。最后一项运行正式ip_options_fragment的目的。当第一ip片段被传送出去后,内核会调用ip_options_fragment去修改ip报头,然后为后续所有片段重复利用新调整过后的报头。

ip_forward_options,转发一个封包时,有些选项可能必须被处理。ip_options_compile会解析一些选项,然后对用于存储解析结果ip_options结构的一组标志做初始化,稍后,ip_forward就会处理这些选项。

ip_options_get,此函数会接收一群选项,用ip_options_compile解析,然后把结果存储在其分配的一个ip_options中。此函数也可以从内核空间或用户空间哪儿接收输入选项。

选项的处理过程:

image

选项的解析:

在此,解析是指把ip选项从存储在ip封包报头里的格式中抽取出来,然后将其存储在一个名为ip_options的结构中,使得程序代码更方便处理。将那些选项存储在一个专用的数据结构中是有帮助的,因为不同选项是有ip程序中的不同部分处理的。ip_options_compile只解析那些选项,并不处理那些选项。

   1:  /*
   2:   *    Options "fragmenting", just fill options not
   3:   *    allowed in fragments with NOOPs.
   4:   *    Simple and stupid 8), but the most efficient way.
   5:   */
   6:   
   7:  void ip_options_fragment(struct sk_buff * skb)
   8:  {
   9:      unsigned char *optptr = skb_network_header(skb) + sizeof(struct iphdr);
  10:      struct ip_options * opt = &(IPCB(skb)->opt);
  11:      int  l = opt->optlen;
  12:      int  optlen;
  13:   
  14:      while (l > 0) {
  15:          switch (*optptr) {
  16:          case IPOPT_END:
  17:              return;
  18:          case IPOPT_NOOP:
  19:              l--;
  20:              optptr++;
  21:              continue;
  22:          }
  23:          optlen = optptr[1];
  24:          if (optlen<2 || optlen>l)
  25:            return;
  26:          if (!IPOPT_COPIED(*optptr))
  27:              memset(optptr, IPOPT_NOOP, optlen);
  28:          l -= optlen;
  29:          optptr += optlen;
  30:      }
  31:      opt->ts = 0;
  32:      opt->rr = 0;
  33:      opt->rr_needaddr = 0;
  34:      opt->ts_needaddr = 0;
  35:      opt->ts_needtime = 0;
  36:  }
  37:   
  38:  /*
  39:   * Verify options and fill pointers in struct options.
  40:   * Caller should clear *opt, and set opt->data.
  41:   * If opt == NULL, then skb->data should point to IP header.
  42:   */
  43:   
  44:  int ip_options_compile(struct net *net,
  45:                 struct ip_options * opt, struct sk_buff * skb)
  46:  {
  47:      int l;
  48:      unsigned char * iph;
  49:      unsigned char * optptr;
  50:      int optlen;
  51:      unsigned char * pp_ptr = NULL;
  52:      struct rtable *rt = NULL;
  53:   
  54:      if (skb != NULL) {
  55:          rt = skb_rtable(skb);
  56:          optptr = (unsigned char *)&(ip_hdr(skb)[1]);
  57:      } else
  58:          optptr = opt->__data;
  59:      iph = optptr - sizeof(struct iphdr);
  60:   
  61:      for (l = opt->optlen; l > 0; ) {
  62:          switch (*optptr) {
  63:                case IPOPT_END:
  64:              for (optptr++, l--; l>0; optptr++, l--) {
  65:                  if (*optptr != IPOPT_END) {
  66:                      *optptr = IPOPT_END;
  67:                      opt->is_changed = 1;
  68:                  }
  69:              }
  70:              goto eol;
  71:                case IPOPT_NOOP:
  72:              l--;
  73:              optptr++;
  74:              continue;
  75:          }
  76:          optlen = optptr[1];
  77:          if (optlen<2 || optlen>l) {
  78:              pp_ptr = optptr;
  79:              goto error;
  80:          }
  81:          switch (*optptr) {
  82:                case IPOPT_SSRR:
  83:                case IPOPT_LSRR:
  84:              if (optlen < 3) {
  85:                  pp_ptr = optptr + 1;
  86:                  goto error;
  87:              }
  88:              if (optptr[2] < 4) {
  89:                  pp_ptr = optptr + 2;
  90:                  goto error;
  91:              }
  92:              /* NB: cf RFC-1812 5.2.4.1 */
  93:              if (opt->srr) {
  94:                  pp_ptr = optptr;
  95:                  goto error;
  96:              }
  97:              if (!skb) {
  98:                  if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {
  99:                      pp_ptr = optptr + 1;
 100:                      goto error;
 101:                  }
 102:                  memcpy(&opt->faddr, &optptr[3], 4);
 103:                  if (optlen > 7)
 104:                      memmove(&optptr[3], &optptr[7], optlen-7);
 105:              }
 106:              opt->is_strictroute = (optptr[0] == IPOPT_SSRR);
 107:              opt->srr = optptr - iph;
 108:              break;
 109:                case IPOPT_RR:
 110:              if (opt->rr) {
 111:                  pp_ptr = optptr;
 112:                  goto error;
 113:              }
 114:              if (optlen < 3) {
 115:                  pp_ptr = optptr + 1;
 116:                  goto error;
 117:              }
 118:              if (optptr[2] < 4) {
 119:                  pp_ptr = optptr + 2;
 120:                  goto error;
 121:              }
 122:              if (optptr[2] <= optlen) {
 123:                  if (optptr[2]+3 > optlen) {
 124:                      pp_ptr = optptr + 2;
 125:                      goto error;
 126:                  }
 127:                  if (rt) {
 128:                      memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);
 129:                      opt->is_changed = 1;
 130:                  }
 131:                  optptr[2] += 4;
 132:                  opt->rr_needaddr = 1;
 133:              }
 134:              opt->rr = optptr - iph;
 135:              break;
 136:                case IPOPT_TIMESTAMP:
 137:              if (opt->ts) {
 138:                  pp_ptr = optptr;
 139:                  goto error;
 140:              }
 141:              if (optlen < 4) {
 142:                  pp_ptr = optptr + 1;
 143:                  goto error;
 144:              }
 145:              if (optptr[2] < 5) {
 146:                  pp_ptr = optptr + 2;
 147:                  goto error;
 148:              }
 149:              if (optptr[2] <= optlen) {
 150:                  unsigned char *timeptr = NULL;
 151:                  if (optptr[2]+3 > optptr[1]) {
 152:                      pp_ptr = optptr + 2;
 153:                      goto error;
 154:                  }
 155:                  switch (optptr[3]&0xF) {
 156:                        case IPOPT_TS_TSONLY:
 157:                      opt->ts = optptr - iph;
 158:                      if (skb)
 159:                          timeptr = &optptr[optptr[2]-1];
 160:                      opt->ts_needtime = 1;
 161:                      optptr[2] += 4;
 162:                      break;
 163:                        case IPOPT_TS_TSANDADDR:
 164:                      if (optptr[2]+7 > optptr[1]) {
 165:                          pp_ptr = optptr + 2;
 166:                          goto error;
 167:                      }
 168:                      opt->ts = optptr - iph;
 169:                      if (rt)  {
 170:                          memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);
 171:                          timeptr = &optptr[optptr[2]+3];
 172:                      }
 173:                      opt->ts_needaddr = 1;
 174:                      opt->ts_needtime = 1;
 175:                      optptr[2] += 8;
 176:                      break;
 177:                        case IPOPT_TS_PRESPEC:
 178:                      if (optptr[2]+7 > optptr[1]) {
 179:                          pp_ptr = optptr + 2;
 180:                          goto error;
 181:                      }
 182:                      opt->ts = optptr - iph;
 183:                      {
 184:                          __be32 addr;
 185:                          memcpy(&addr, &optptr[optptr[2]-1], 4);
 186:                          if (inet_addr_type(net, addr) == RTN_UNICAST)
 187:                              break;
 188:                          if (skb)
 189:                              timeptr = &optptr[optptr[2]+3];
 190:                      }
 191:                      opt->ts_needtime = 1;
 192:                      optptr[2] += 8;
 193:                      break;
 194:                        default:
 195:                      if (!skb && !capable(CAP_NET_RAW)) {
 196:                          pp_ptr = optptr + 3;
 197:                          goto error;
 198:                      }
 199:                      break;
 200:                  }
 201:                  if (timeptr) {
 202:                      struct timespec tv;
 203:                      u32  midtime;
 204:                      getnstimeofday(&tv);
 205:                      midtime = (tv.tv_sec % 86400) * MSEC_PER_SEC + tv.tv_nsec / NSEC_PER_MSEC;
 206:                      put_unaligned_be32(midtime, timeptr);
 207:                      opt->is_changed = 1;
 208:                  }
 209:              } else {
 210:                  unsigned overflow = optptr[3]>>4;
 211:                  if (overflow == 15) {
 212:                      pp_ptr = optptr + 3;
 213:                      goto error;
 214:                  }
 215:                  opt->ts = optptr - iph;
 216:                  if (skb) {
 217:                      optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);
 218:                      opt->is_changed = 1;
 219:                  }
 220:              }
 221:              break;
 222:                case IPOPT_RA:
 223:              if (optlen < 4) {
 224:                  pp_ptr = optptr + 1;
 225:                  goto error;
 226:              }
 227:              if (optptr[2] == 0 && optptr[3] == 0)
 228:                  opt->router_alert = optptr - iph;
 229:              break;
 230:                case IPOPT_CIPSO:
 231:              if ((!skb && !capable(CAP_NET_RAW)) || opt->cipso) {
 232:                  pp_ptr = optptr;
 233:                  goto error;
 234:              }
 235:              opt->cipso = optptr - iph;
 236:              if (cipso_v4_validate(skb, &optptr)) {
 237:                  pp_ptr = optptr;
 238:                  goto error;
 239:              }
 240:              break;
 241:                case IPOPT_SEC:
 242:                case IPOPT_SID:
 243:                default:
 244:              if (!skb && !capable(CAP_NET_RAW)) {
 245:                  pp_ptr = optptr;
 246:                  goto error;
 247:              }
 248:              break;
 249:          }
 250:          l -= optlen;
 251:          optptr += optlen;
 252:      }
 253:   
 254:  eol:
 255:      if (!pp_ptr)
 256:          return 0;
 257:   
 258:  error:
 259:      if (skb) {
 260:          icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((pp_ptr-iph)<<24));
 261:      }
 262:      return -EINVAL;
 263:  }

在传输一个本地产生的封包时,opt->data包含一个指向ip报头(先前调用者产生一部分)的指针。如果此函数正在处理一个入口封包,则报头包含在skb输入缓冲区中。

以下是一些可能导致解析失败的原因:

  • 单一选项不能出现在报头中一次以上。唯一例外是空选项IPOPT_NOOP。空选项可出现任意次数,而且通常是用于施加某种对其,不是对个别选项,就是对选项之后的有效载荷。
  • 一个报头字段的值为无效值,或者其值为当前用户不准使用的值。这种情况适用于本地产生的流量。只有超级用户可以产生带有内核不懂的选项或子选项封包。

目前,只有两个单字选项:

选项结尾(IPOPT_END)和空选项(IPOPT_NOOP)。

主要for循环就是绕行一个一个的选项,然后把解析结果存储在输出的ip_options结构opt中。循环内的代码看起来可能很复杂,但实际容易阅读,只要考虑以下几点:

  • l代表尚未解析的选项区块的大小
  • optptr指向正被分析的选项区块的当前位置。optptr[1]是选项的长度,而optptr[2]是选项指针。
  • optlen的初值会设定当前选项的长度。
  • 标志is_changed用于记录报头合适已修改

IPOPT_END选项之后不能有其他选项。因此一旦找到一个,无论后面是什么,都会以更多的IPOPT_END选项覆盖。

多字节选项的基本健康检查包括:

  • 该选项必须至少4个字节长。因为选项的报头是3字节长,字段pointer就不能小于4.
  • 在报头中保留空间的那些选项(因为假定他们应该由后面几个跳点或者目的地主机填写)必须尊重该选项所需的尺寸。

IP转发和本地传递:

在ip_rcv_finish函数的尾端,如果目的地址和本地接口不同,内核就必须把封包转发至适当的主机;如果目的地址为本地地址,内核就必须把封包准备好,以便较高层使用。

转发:

转发分割成两个函数:ip_forward和ip_forward_finish。此时,sk_buff缓冲区内包含了转发封包所需的所有信息。转发由下列步骤组成:

  1. 处理ip选项。这可能会涉及本地ip地址以及时间戳
  2. 确定封包可以转发
  3. 递减ip报头的ttl字段,然后,如果ttl字段变为零,就丢弃该封包
  4. 根据路径相关mtu,必要时处理分段工作
  5. 把封包传送至外出设备

如果封包因为某种原因而无法转发,源主机就必须受到ICMP消息通知,来说明碰到的问题。即使封包会转发,但是封包绕送时使用的是次佳路径,因而触发了重导向事件时,ICMP消息也会送出,来作为通知。

ip_forward:

你可能感兴趣的:(IP层)