当前位置:   article > 正文

Linux netfilter hook源码分析(基于内核代码版本4.18.0-80)

Linux netfilter hook源码分析(基于内核代码版本4.18.0-80)

Netfilter在网络层实现的详细分析见下面文章:

linux Netfilter在网络层的实现详细分析(iptables)_yg@hunter的博客-CSDN博客

本文分析的源码版本为4.18.0-80

4.3以下的内核版本是通过nf_register_hook来注册,nf_unregister_hook来注销;

4.3-4.13之间版本,nf_register_hook里面会调用nf_register_net_hook来逐个net命名空间注册,此时可以使用这俩函数中的任一个来注册,注销对应nf_unregister_hook/nf_unregister_net_hook;

4.13及以上版本内核是通过nf_register_net_hook/nf_unregister_net_hook来注册/注销,删掉了nf_register_hook函数。

我画了张图,描述了netfilter hook的整个过程所涉及的数据结构(基于内核代码版本4.18.0-80):

目录

1、钩子函数的注册

2、自定义hook钩子函数的调用链

①内核网络协议栈中安装的钩子

②调用nf_hook函数

③调用nf_hook_slow函数

④调用内联函数nf_hook_entry_hookfn

⑤最终就调用到hook_entry的hook回调函数

⑥钩子函数的返回值

3、钩子函数的注销

⑴nf_unregister_net_hook

⑵__nf_unregister_net_hook

①nf_remove_net_hook

②__nf_hook_entries_try_shrink

③nf_queue_nf_hook_drop

④nf_hook_entries_free


1、钩子函数的注册

nf_register_net_hook函数的源码如下:

net\netfilter\core.c

  1. int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
  2. {
  3. int err;
  4. if (reg->pf == NFPROTO_INET) { // inet协议包含ipv4、ipv6
  5. err = __nf_register_net_hook(net, NFPROTO_IPV4, reg);
  6. if (err < 0)
  7. return err;
  8. err = __nf_register_net_hook(net, NFPROTO_IPV6, reg);
  9. if (err < 0) {
  10. __nf_unregister_net_hook(net, NFPROTO_IPV4, reg);
  11. return err;
  12. }
  13. } else {
  14. err = __nf_register_net_hook(net, reg->pf, reg);
  15. if (err < 0)
  16. return err;
  17. }
  18. return 0;
  19. }
  20. EXPORT_SYMBOL(nf_register_net_hook);

对于注册的hook协议类型为NFPROTO_INET的话,会先后注册ipv4、ipv6的hook,然后会根据注册的协议类型调用__nf_register_net_hook函数:

  1. static int __nf_register_net_hook(struct net *net, int pf,
  2. const struct nf_hook_ops *reg)
  3. {
  4. struct nf_hook_entries *p, *new_hooks;
  5. struct nf_hook_entries __rcu **pp;
  6. //处理netdev层的ingress hook点
  7. if (pf == NFPROTO_NETDEV) {
  8. #ifndef CONFIG_NETFILTER_INGRESS
  9. if (reg->hooknum == NF_NETDEV_INGRESS)
  10. return -EOPNOTSUPP;
  11. #endif
  12. if (reg->hooknum != NF_NETDEV_INGRESS ||
  13. !reg->dev || dev_net(reg->dev) != net)
  14. return -EINVAL;
  15. }
  16. //获取该协议下对应hook点的数组首地址
  17. pp = nf_hook_entry_head(net, pf, reg->hooknum, reg->dev);
  18. if (!pp)
  19. return -EINVAL;
  20. mutex_lock(&nf_hook_mutex);
  21. p = nf_entry_dereference(*pp);
  22. //将新reg插入到该数组,里面重新为该数组申请空间,然后会按priority排序好,重新插入所有的hook
  23. new_hooks = nf_hook_entries_grow(p, reg);
  24. if (!IS_ERR(new_hooks))
  25. rcu_assign_pointer(*pp, new_hooks);
  26. mutex_unlock(&nf_hook_mutex);
  27. if (IS_ERR(new_hooks))
  28. return PTR_ERR(new_hooks);
  29. hooks_validate(new_hooks);
  30. #ifdef CONFIG_NETFILTER_INGRESS
  31. if (pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)
  32. net_inc_ingress_queue();
  33. #endif
  34. #ifdef HAVE_JUMP_LABEL
  35. static_key_slow_inc(&nf_hooks_needed[pf][reg->hooknum]);
  36. #endif
  37. BUG_ON(p == new_hooks);
  38. nf_hook_entries_free(p);
  39. return 0;
  40. }

nf_hook_entries 获取对应协议的对应hook链的首地址:

  1. static struct nf_hook_entries __rcu **
  2. nf_hook_entry_head(struct net *net, int pf, unsigned int hooknum,
  3. struct net_device *dev)
  4. {
  5. switch (pf) {
  6. case NFPROTO_NETDEV:
  7. break;
  8. #ifdef CONFIG_NETFILTER_FAMILY_ARP
  9. case NFPROTO_ARP:
  10. if (WARN_ON_ONCE(ARRAY_SIZE(net->nf.hooks_arp) <= hooknum))
  11. return NULL;
  12. return net->nf.hooks_arp + hooknum;
  13. #endif
  14. #ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
  15. case NFPROTO_BRIDGE:
  16. if (WARN_ON_ONCE(ARRAY_SIZE(net->nf.hooks_bridge) <= hooknum))
  17. return NULL;
  18. return net->nf.hooks_bridge + hooknum;
  19. #endif
  20. case NFPROTO_IPV4:
  21. if (WARN_ON_ONCE(ARRAY_SIZE(net->nf.hooks_ipv4) <= hooknum))
  22. return NULL;
  23. return net->nf.hooks_ipv4 + hooknum;
  24. case NFPROTO_IPV6:
  25. if (WARN_ON_ONCE(ARRAY_SIZE(net->nf.hooks_ipv6) <= hooknum))
  26. return NULL;
  27. return net->nf.hooks_ipv6 + hooknum;
  28. #if IS_ENABLED(CONFIG_DECNET)
  29. case NFPROTO_DECNET:
  30. if (WARN_ON_ONCE(ARRAY_SIZE(net->nf.hooks_decnet) <= hooknum))
  31. return NULL;
  32. return net->nf.hooks_decnet + hooknum;
  33. #endif
  34. default:
  35. WARN_ON_ONCE(1);
  36. return NULL;
  37. }
  38. #ifdef CONFIG_NETFILTER_INGRESS
  39. if (hooknum == NF_NETDEV_INGRESS) {
  40. if (dev && dev_net(dev) == net)
  41. return &dev->nf_hooks_ingress;
  42. }
  43. #endif
  44. WARN_ON_ONCE(1);
  45. return NULL;
  46. }

然后调用nf_hook_entries_grow将要注册的hook按优先级priority插入到该链中:

  1. static struct nf_hook_entries *
  2. nf_hook_entries_grow(const struct nf_hook_entries *old,
  3. const struct nf_hook_ops *reg)
  4. {
  5. unsigned int i, alloc_entries, nhooks, old_entries;
  6. struct nf_hook_ops **orig_ops = NULL;
  7. struct nf_hook_ops **new_ops;
  8. struct nf_hook_entries *new;
  9. bool inserted = false;
  10. alloc_entries = 1;
  11. old_entries = old ? old->num_hook_entries : 0;
  12. if (old) {
  13. orig_ops = nf_hook_entries_get_hook_ops(old);
  14. for (i = 0; i < old_entries; i++) {
  15. if (orig_ops[i] != &dummy_ops)
  16. alloc_entries++;
  17. }
  18. }
  19. if (alloc_entries > MAX_HOOK_COUNT)
  20. return ERR_PTR(-E2BIG);
  21. new = allocate_hook_entries_size(alloc_entries);
  22. if (!new)
  23. return ERR_PTR(-ENOMEM);
  24. new_ops = nf_hook_entries_get_hook_ops(new);
  25. i = 0;
  26. nhooks = 0;
  27. while (i < old_entries) {
  28. if (orig_ops[i] == &dummy_ops) {
  29. ++i;
  30. continue;
  31. }
  32. if (inserted || reg->priority > orig_ops[i]->priority) {
  33. new_ops[nhooks] = (void *)orig_ops[i];
  34. new->hooks[nhooks] = old->hooks[i];
  35. i++;
  36. } else {
  37. new_ops[nhooks] = (void *)reg;
  38. new->hooks[nhooks].hook = reg->hook;
  39. new->hooks[nhooks].priv = reg->priv;
  40. inserted = true;
  41. }
  42. nhooks++;
  43. }
  44. if (!inserted) {
  45. new_ops[nhooks] = (void *)reg;
  46. new->hooks[nhooks].hook = reg->hook;
  47. new->hooks[nhooks].priv = reg->priv;
  48. }
  49. return new;
  50. }

2、自定义hook钩子函数的调用链

  1. 内核网络协议栈的各hook点
  2. ->NF_HOOK/NF_HOOK_COND
  3. ->nf_hook()
  4. -> nf_hook_slow()
  5. -> nf_hook_entry_hookfn()
  6. -> entry->hook()

①内核网络协议栈中安装的钩子

会在相关位置调用NF_HOOK/NF_HOOK_COND宏,触发钩子函数:

include\linux\netfilter.h

  1. static inline int
  2. NF_HOOK_COND(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk,
  3. struct sk_buff *skb, struct net_device *in, struct net_device *out,
  4. int (*okfn)(struct net *, struct sock *, struct sk_buff *),
  5. bool cond)
  6. {
  7. int ret;
  8. if (!cond ||
  9. ((ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn)) == 1))
  10. ret = okfn(net, sk, skb);
  11. return ret;
  12. }
  13. static inline int
  14. NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,
  15. struct net_device *in, struct net_device *out,
  16. int (*okfn)(struct net *, struct sock *, struct sk_buff *))
  17. {
  18. int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
  19. if (ret == 1)
  20. ret = okfn(net, sk, skb);
  21. return ret;
  22. }

比如,net\ipv4\ip_input.c中,进入本地的网络数据包,会调用NF_HOOK触发NF_INET_LOCAL_IN钩子:

②调用nf_hook函数

实际执行时调用nf_hook函数,函数定义如下:

include\linux\netfilter.h

  1. /**
  2. * nf_hook - call a netfilter hook
  3. *
  4. * Returns 1 if the hook has allowed the packet to pass. The function
  5. * okfn must be invoked by the caller in this case. Any other return
  6. * value indicates the packet has been consumed by the hook.
  7. */
  8. static inline int nf_hook(u_int8_t pf, unsigned int hook, struct net *net,
  9. struct sock *sk, struct sk_buff *skb,
  10. struct net_device *indev, struct net_device *outdev,
  11. int (*okfn)(struct net *, struct sock *, struct sk_buff *))
  12. {
  13. struct nf_hook_entries *hook_head = NULL;
  14. int ret = 1;
  15. #ifdef HAVE_JUMP_LABEL
  16. if (__builtin_constant_p(pf) &&
  17. __builtin_constant_p(hook) &&
  18. !static_key_false(&nf_hooks_needed[pf][hook]))
  19. return 1;
  20. #endif
  21. rcu_read_lock();
  22. //根据传入的协议类型,及hook点,获取对应hook链的数组首地址
  23. switch (pf) {
  24. case NFPROTO_IPV4:
  25. hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);
  26. break;
  27. case NFPROTO_IPV6:
  28. hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]);
  29. break;
  30. case NFPROTO_ARP:
  31. #ifdef CONFIG_NETFILTER_FAMILY_ARP
  32. hook_head = rcu_dereference(net->nf.hooks_arp[hook]);
  33. #endif
  34. break;
  35. case NFPROTO_BRIDGE:
  36. #ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
  37. hook_head = rcu_dereference(net->nf.hooks_bridge[hook]);
  38. #endif
  39. break;
  40. #if IS_ENABLED(CONFIG_DECNET)
  41. case NFPROTO_DECNET:
  42. hook_head = rcu_dereference(net->nf.hooks_decnet[hook]);
  43. break;
  44. #endif
  45. default:
  46. WARN_ON_ONCE(1);
  47. break;
  48. }
  49. if (hook_head) {
  50. struct nf_hook_state state;
  51. nf_hook_state_init(&state, hook, pf, indev, outdev,
  52. sk, net, okfn);
  53. //最后进入nf_hook_slow函数流程
  54. ret = nf_hook_slow(skb, &state, hook_head, 0);
  55. }
  56. rcu_read_unlock();
  57. return ret;
  58. }

③调用nf_hook_slow函数

根据传入的协议类型,及hook点,获取对应hook链的数组首地址后,最终调用nf_hook_slow函数:

net\netfilter\core.c

  1. /* Returns 1 if okfn() needs to be executed by the caller,
  2. * -EPERM for NF_DROP, 0 otherwise. Caller must hold rcu_read_lock. */
  3. int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
  4. const struct nf_hook_entries *e, unsigned int s)
  5. {
  6. unsigned int verdict;
  7. int ret;
  8. for (; s < e->num_hook_entries; s++) {
  9. // 调用对应钩子函数
  10. verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
  11. // 判断钩子函数的返回值,决定该数据包的后续处理流程
  12. switch (verdict & NF_VERDICT_MASK) {
  13. case NF_ACCEPT: // 允许数据包继续下一步
  14. break;
  15. case NF_DROP: // 丢弃该数据包,直接返回EPERM
  16. kfree_skb(skb);
  17. ret = NF_DROP_GETERR(verdict);
  18. if (ret == 0)
  19. ret = -EPERM;
  20. return ret;
  21. case NF_QUEUE: // 数据包加入用户队列,给用户程序处理,然后返回
  22. ret = nf_queue(skb, state, e, s, verdict);
  23. if (ret == 1)
  24. continue;
  25. return ret;
  26. default: // NF_STOLEN,让netfilter框架忽略该数据包的处理
  27. /* Implicit handling for NF_STOLEN, as well as any other
  28. * non conventional verdicts.
  29. */
  30. return 0;
  31. }
  32. }
  33. return 1;
  34. }
  35. EXPORT_SYMBOL(nf_hook_slow);

④调用内联函数nf_hook_entry_hookfn

include\linux\netfilter.h

  1. static inline int
  2. nf_hook_entry_hookfn(const struct nf_hook_entry *entry, struct sk_buff *skb,
  3. struct nf_hook_state *state)
  4. {
  5. return entry->hook(entry->priv, skb, state);
  6. }

⑤最终就调用到hook_entry的hook回调函数

此时entry->hook就是我们自定义的nf_hook_ops中的hook函数了。

⑥钩子函数的返回值

它的返回值为以下几种:

include\uapi\linux\netfilter.h

  1. /* Responses from hook functions. */
  2. #define NF_DROP 0 // 丢弃该数据包
  3. #define NF_ACCEPT 1 // 当前hook点,允许该数据包继续在协议栈中流转
  4. #define NF_STOLEN 2 // 让netfilter框架忽略该数据包的处理
  5. #define NF_QUEUE 3 // 该数据包加入到用户队列,供用户程序处理
  6. #define NF_REPEAT 4
  7. #define NF_STOP 5 /* Deprecated, for userspace nf_queue compatibility. */
  8. #define NF_MAX_VERDICT NF_STOP

3、钩子函数的注销

net\netfilter\core.c

⑴nf_unregister_net_hook

  1. void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg)
  2. {
  3. if (reg->pf == NFPROTO_INET) {
  4. __nf_unregister_net_hook(net, NFPROTO_IPV4, reg);
  5. __nf_unregister_net_hook(net, NFPROTO_IPV6, reg);
  6. } else {
  7. __nf_unregister_net_hook(net, reg->pf, reg);
  8. }
  9. }
  10. EXPORT_SYMBOL(nf_unregister_net_hook);

⑵__nf_unregister_net_hook

跟注册类似处理之后,里面会调用__nf_unregister_net_hook函数:

  1. static void __nf_unregister_net_hook(struct net *net, int pf,
  2. const struct nf_hook_ops *reg)
  3. {
  4. struct nf_hook_entries __rcu **pp;
  5. struct nf_hook_entries *p;
  6. pp = nf_hook_entry_head(net, pf, reg->hooknum, reg->dev);
  7. if (!pp)
  8. return;
  9. mutex_lock(&nf_hook_mutex);
  10. p = nf_entry_dereference(*pp);
  11. if (WARN_ON_ONCE(!p)) {
  12. mutex_unlock(&nf_hook_mutex);
  13. return;
  14. }
  15. // 将该hook从对应hook数组中移除
  16. if (nf_remove_net_hook(p, reg)) {
  17. #ifdef CONFIG_NETFILTER_INGRESS
  18. if (pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)
  19. net_dec_ingress_queue();
  20. #endif
  21. #ifdef HAVE_JUMP_LABEL
  22. static_key_slow_dec(&nf_hooks_needed[pf][reg->hooknum]);
  23. #endif
  24. } else {
  25. WARN_ONCE(1, "hook not found, pf %d num %d", pf, reg->hooknum);
  26. }
  27. // 尝试缩容,移除hook为accept_all的hook函数操作
  28. p = __nf_hook_entries_try_shrink(p, pp);
  29. mutex_unlock(&nf_hook_mutex);
  30. if (!p)
  31. return;
  32. nf_queue_nf_hook_drop(net);
  33. nf_hook_entries_free(p);
  34. }

①nf_remove_net_hook

里面会调用nf_remove_net_hook,从对应的hook数组中移除该hook,这里移除并没有删掉,而是将该hook数组对应下标的hook改成了accept_all,nf_hook_ops设置为dummy_ops:

  1. /*
  2. * nf_remove_net_hook - remove a hook from blob
  3. *
  4. * @oldp: current address of hook blob
  5. * @unreg: hook to unregister
  6. *
  7. * This cannot fail, hook unregistration must always succeed.
  8. * Therefore replace the to-be-removed hook with a dummy hook.
  9. */
  10. static bool nf_remove_net_hook(struct nf_hook_entries *old,
  11. const struct nf_hook_ops *unreg)
  12. {
  13. struct nf_hook_ops **orig_ops;
  14. unsigned int i;
  15. orig_ops = nf_hook_entries_get_hook_ops(old);
  16. for (i = 0; i < old->num_hook_entries; i++) {
  17. if (orig_ops[i] != unreg)
  18. continue;
  19. WRITE_ONCE(old->hooks[i].hook, accept_all);
  20. WRITE_ONCE(orig_ops[i], &dummy_ops);
  21. return true;
  22. }
  23. return false;
  24. }

②__nf_hook_entries_try_shrink

然后调用__nf_hook_entries_try_shrink,尝试缩容hook数组,这里是重新申请了个nf_hook_entries,把旧的nf_hook_entries里hook数组中除了元素为dummy_ops的所有元素都按顺序拷贝到新nf_hook_entries中:

  1. /*
  2. * __nf_hook_entries_try_shrink - try to shrink hook array
  3. *
  4. * @old -- current hook blob at @pp
  5. * @pp -- location of hook blob
  6. *
  7. * Hook unregistration must always succeed, so to-be-removed hooks
  8. * are replaced by a dummy one that will just move to next hook.
  9. *
  10. * This counts the current dummy hooks, attempts to allocate new blob,
  11. * copies the live hooks, then replaces and discards old one.
  12. *
  13. * return values:
  14. *
  15. * Returns address to free, or NULL.
  16. */
  17. static void *__nf_hook_entries_try_shrink(struct nf_hook_entries *old,
  18. struct nf_hook_entries __rcu **pp)
  19. {
  20. unsigned int i, j, skip = 0, hook_entries;
  21. struct nf_hook_entries *new = NULL;
  22. struct nf_hook_ops **orig_ops;
  23. struct nf_hook_ops **new_ops;
  24. if (WARN_ON_ONCE(!old))
  25. return NULL;
  26. orig_ops = nf_hook_entries_get_hook_ops(old);
  27. for (i = 0; i < old->num_hook_entries; i++) {
  28. if (orig_ops[i] == &dummy_ops)
  29. skip++;
  30. }
  31. /* if skip == hook_entries all hooks have been removed */
  32. hook_entries = old->num_hook_entries;
  33. if (skip == hook_entries)
  34. goto out_assign;
  35. if (skip == 0)
  36. return NULL;
  37. hook_entries -= skip;
  38. new = allocate_hook_entries_size(hook_entries);
  39. if (!new)
  40. return NULL;
  41. new_ops = nf_hook_entries_get_hook_ops(new);
  42. for (i = 0, j = 0; i < old->num_hook_entries; i++) {
  43. if (orig_ops[i] == &dummy_ops)
  44. continue;
  45. new->hooks[j] = old->hooks[i];
  46. new_ops[j] = (void *)orig_ops[i];
  47. j++;
  48. }
  49. hooks_validate(new);
  50. out_assign:
  51. rcu_assign_pointer(*pp, new);
  52. return old;
  53. }

③nf_queue_nf_hook_drop

在当前net网络命名空间中删除旧的nf_hook_entries。

④nf_hook_entries_free

释放旧nf_hook_entries_free所占空间。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/160462
推荐阅读
相关标签
  

闽ICP备14008679号