UDP socket流程(14)——ip_local_out及其调用的函数

作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net

ip_local_out的代码很短
  1. int ip_local_out(struct sk_buff *skb)
  2. {
  3.     int err;
  4.     /* 
  5.     调用netfilter的hook检查该包是否可以发送。
  6.     */
  7.     err = __ip_local_out(skb);
  8.     /*
  9.     当err为1时,表明netfilter的hook函数告诉调用者允许包通过,并且需要调用者明确的调用hook函数的参数中     的回调函数。在此,需要调用dst_output来真正发送数据包 
  10.     */
  11.     if (likely(err == 1))
  12.         err = dst_output(skb);

  13.     return err;
  14. }
刚看到这里时,看到err很自然的想到err是错误值,尤其是linux中大部分的成功返回都是0。
但是在看完了__ip_local_out的代码后,才知道其返回1时,是一个特定的返回值,要求调用者明确调用hook函数的参数中的回调函数,这里时dst_output。


下面看看__ip_local_out的代码,代码也不长
  1. int __ip_local_out(struct sk_buff *skb)
  2. {
  3.     /* 获得IP报文头的地址 */
  4.     struct iphdr *iph = ip_hdr(skb);
  5.     
  6.     /* 到此,数据包的长度不会改变了,可以对IP报文中的TL字段赋值了,并转为网络序 */
  7.     iph->tot_len = htons(skb->len);
  8.     /* 计算IP报文的校验和 */
  9.     ip_send_check(iph);
  10.     /* 调用netfilter的hook函数 */
  11.     return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
  12.          skb_dst(skb)->dev, dst_output);
  13. }
在编译linux内核时,如果没有指定CONFIG_NETFILTER这个宏,则nf_hook的函数体是直接返回1.
若指定了CONFIG_NETFILTER,则nf_hook函数为
  1. static inline int nf_hook(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
  2.              struct net_device *indev, struct net_device *outdev,
  3.              int (*okfn)(struct sk_buff *))
  4. {
  5.     return nf_hook_thresh(pf, hook, skb, indev, outdev, okfn, INT_MIN);
  6. }
又是一个包装函数,对参数进行一下说明吧。
u_int8_t pf:指定netfilter的协议,值的范围如下:
  1. enum {
  2.     NFPROTO_UNSPEC = 0,
  3.     NFPROTO_IPV4 = 2,
  4.     NFPROTO_ARP = 3,
  5.     NFPROTO_BRIDGE = 7,
  6.     NFPROTO_IPV6 = 10,
  7.     NFPROTO_DECNET = 12,
  8.     NFPROTO_NUMPROTO,
  9. };
unsigned int hook:表示hook的类型,值的范围如下:
  1. enum nf_inet_hooks {
  2.     NF_INET_PRE_ROUTING,
  3.     NF_INET_LOCAL_IN,
  4.     NF_INET_FORWARD,
  5.     NF_INET_LOCAL_OUT,
  6.     NF_INET_POST_ROUTING,
  7.     NF_INET_NUMHOOKS
  8. };
看着几个枚举值,熟悉iptable的朋友应该很眼熟吧——因为iptable就是通过netfilter实现的。
struct sk_buff *skb:要输出的数据包
struct net_device *indev:收到数据包的网卡设备;
struct net_device *outdev:发送数据包的网卡设备;
int (*okfn) (struct sk_buff*):netfiler的hook函数判定成功时,会调用的回调函数。

下面看nf_hook_threash,
  1. /* 这里对于返回值1进行了说明 */
  2. /**
  3.  *    nf_hook_thresh - call a netfilter hook
  4.  *    
  5.  *    Returns 1 if the hook has allowed the packet to pass. The function
  6.  *    okfn must be invoked by the caller in this case. Any other return
  7.  *    value indicates the packet has been consumed by the hook.
  8.  */
  9. static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook,
  10.                  struct sk_buff *skb,
  11.                  struct net_device *indev,
  12.                  struct net_device *outdev,
  13.                  int (*okfn)(struct sk_buff *), int thresh)
  14. {
  15. #ifndef CONFIG_NETFILTER_DEBUG
  16.     if (list_empty(&nf_hooks[pf][hook]))
  17.         return 1;
  18. #endif
  19.     return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
  20. }
好,继续追踪nf_hook_slow的代码。
  1. /* Returns 1 if okfn() needs to be executed by the caller,
  2.  * -EPERM for NF_DROP, 0 otherwise. */
  3. int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
  4.          struct net_device *indev,
  5.          struct net_device *outdev,
  6.          int (*okfn)(struct sk_buff *),
  7.          int hook_thresh)
  8. {
  9.     struct list_head *elem;
  10.     unsigned int verdict;
  11.     int ret = 0;

  12.     /* We may already have this, but read-locks nest anyway */
  13.     /* 获得RCU读锁 */
  14.     rcu_read_lock();
     /* 根据pf的协议类型和hook的类型,得到netfilter动作行为的链表头 */
  1.     elem = &nf_hooks[pf][hook];
  2. next_hook:
  3.     /* 
  4.     遍历这个动作行为链表头并得到最终对该包的判定处理。
  5.     这里,如果熟悉iptable的朋友很容易理解,因为判定的行为就是iptable中的行为。
  6.     */
  7.     verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
  8.              outdev, &elem, okfn, hook_thresh);
  9.     if (verdict == NF_ACCEPT || verdict == NF_STOP) {
  10.         /* 
  11.         判定是接受或者停止
  12.         NF_STOP是2.6中新加入的行为,与NF_ACCEPT类似。区别就是一旦一个判定为NF_STOP,就立刻返回,不会         进行后面的判定。而NF_ACCEPT则还会继续后面的判定
  13.         */
  14.         ret = 1;
  15.     } else if (verdict == NF_DROP) {
  16.         /* 该包需要drop */
  17.         kfree_skb(skb);
  18.         ret = -EPERM;
  19.     } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
  20.         /* 该包需要queue入列 */
  21.         if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
  22.              verdict >> NF_VERDICT_BITS))
  23.             goto next_hook;
  24.     }
  25.     rcu_read_unlock();
  26.     return ret;
  27. }
好,今天就先这样了。其实关于netfilter这些函数的处理,看到这儿,已经不妨碍阅读发送IP数据包的代码了。
明天可以还是先把发送数据包的代码看完,然后在把netfilter整理一下。今天进展比较快,因为自己对iptable的了解还可以,所以阅读netfilter的相关代码很容易,因为概念都很清楚。

看来,对应用了解清楚对于阅读内核代码还是有很大的帮助的。


上一篇:阿里云StreamCompute流计算架构


下一篇:编译安装httpd 2.4