嵌入式Linux内核修改转发skb网络数据包

实际的项目软件开过程中,碰到一个需求,需要修改以太网接收到的数据包,主要有两点需求:

  • L2层特定自定义协议包修改目的MAC地址
  • 针对特定端口的UDP包,修改其目的IP后,再接交给上层(或在Bridge转发之前)处理

方案评估

第一点需求,修改L2层的MAC地址,可采用:

  • 采用ebtable的DNAT修改L2层MAC地址
  • 通过网络驱动传参修改

第二点需求,修改Bridge转发之前的特定端口包目的IP,可采用:

  • 用iptables的INT_NET_PREROUTING Hook,结合br_netfilter模块的bridge-nf-call-iptables功能实现

    /proc/sys/net/bridge/bridge-nf-call-iptables
  • 编写一个netfilter hook内核模块,通过bridge NF_BR_PRE_ROUTING hook点特殊处理

最终采用的方案

上述两点的两个方案,理论上都是第一条比较优雅,但是现实总是残酷的,在嵌入式系统上,芯片资源有限,CPU\RAM\Flash资源受限的情况下,无论是ebtables或是iptables都太重了,特别还要考虑转发执行效率尽量的减少CPU处理,最终都采用了第二种策略。

实现注意点

  • 网络驱动的代码netif_rx(skb);都处于中断的下半区,代码需要高效简短,否则严重影响CPU。
  • 编写netfilter hook模块时,挂载在NF_BR_PRE_ROUTING hook处理节点,处于L2层,收到的skb数据包,无论是网络层、还是传输层指针都是无效的,不能直接引用
  • 挂载点协议选择NFPROTO_BRIDGE

    static struct nf_hook_ops nfho = {
      .hook = hook_func,
      .hooknum = NF_BR_PRE_ROUTING,
      .pf = NFPROTO_BRIDGE,
      .priority = NF_BR_PRI_FIRST,
    };
  • 修改了UDP包的目的IP后,需要重新计算UDP及IP Header的checksum;此外,这里仅修改目的IP,不需要重新全部计算,可以使用inet_proto_csum_replace4只作替代更新,提高效率
  • 目的IP如果修改为组播或广播包,则需要对应的修改eth header的目的MAC地址,否则包不会经过Bridge广播,从L2层上看还是会传给NF_BR_LOCAL_IN

参考代码及资料

  1. 参考代码1

    /*
     * Copyright (c) 2007-2012 Nicira, Inc.
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of version 2 of the GNU General Public
     * License as published by the Free Software Foundation.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
     * 02110-1301, USA
     */
    
    #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
    
    #include <linux/skbuff.h>
    #include <linux/in.h>
    #include <linux/ip.h>
    #include <linux/openvswitch.h>
    #include <linux/tcp.h>
    #include <linux/udp.h>
    #include <linux/in6.h>
    #include <linux/if_arp.h>
    #include <linux/if_vlan.h>
    #include <net/ip.h>
    #include <net/ipv6.h>
    #include <net/checksum.h>
    #include <net/dsfield.h>
    
    #include "checksum.h"
    #include "datapath.h"
    #include "vlan.h"
    #include "vport.h"
    
    static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
                   const struct nlattr *attr, int len,
                   struct ovs_key_ipv4_tunnel *tun_key, bool keep_skb);
    
    static int make_writable(struct sk_buff *skb, int write_len)
    {
     if (!skb_cloned(skb) || skb_clone_writable(skb, write_len))
         return 0;
    
     return pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
    }
    
    /* remove VLAN header from packet and update csum accordingly. */
    static int __pop_vlan_tci(struct sk_buff *skb, __be16 *current_tci)
    {
     struct vlan_hdr *vhdr;
     int err;
    
     err = make_writable(skb, VLAN_ETH_HLEN);
     if (unlikely(err))
         return err;
    
     if (get_ip_summed(skb) == OVS_CSUM_COMPLETE)
         skb->csum = csum_sub(skb->csum, csum_partial(skb->data
                     + ETH_HLEN, VLAN_HLEN, 0));
    
     vhdr = (struct vlan_hdr *)(skb->data + ETH_HLEN);
     *current_tci = vhdr->h_vlan_TCI;
    
     memmove(skb->data + VLAN_HLEN, skb->data, 2 * ETH_ALEN);
     __skb_pull(skb, VLAN_HLEN);
    
     vlan_set_encap_proto(skb, vhdr);
     skb->mac_header += VLAN_HLEN;
     skb_reset_mac_len(skb);
    
     return 0;
    }
    
    static int pop_vlan(struct sk_buff *skb)
    {
     __be16 tci;
     int err;
    
     if (likely(vlan_tx_tag_present(skb))) {
         vlan_set_tci(skb, 0);
     } else {
         if (unlikely(skb->protocol != htons(ETH_P_8021Q) ||
                  skb->len < VLAN_ETH_HLEN))
             return 0;
    
         err = __pop_vlan_tci(skb, &tci);
         if (err)
             return err;
     }
     /* move next vlan tag to hw accel tag */
     if (likely(skb->protocol != htons(ETH_P_8021Q) ||
            skb->len < VLAN_ETH_HLEN))
         return 0;
    
     err = __pop_vlan_tci(skb, &tci);
     if (unlikely(err))
         return err;
    
     __vlan_hwaccel_put_tag(skb, ntohs(tci));
     return 0;
    }
    
    static int push_vlan(struct sk_buff *skb, const struct ovs_action_push_vlan *vlan)
    {
     if (unlikely(vlan_tx_tag_present(skb))) {
         u16 current_tag;
    
         /* push down current VLAN tag */
         current_tag = vlan_tx_tag_get(skb);
    
         if (!__vlan_put_tag(skb, current_tag))
             return -ENOMEM;
    
         if (get_ip_summed(skb) == OVS_CSUM_COMPLETE)
             skb->csum = csum_add(skb->csum, csum_partial(skb->data
                     + ETH_HLEN, VLAN_HLEN, 0));
    
     }
     __vlan_hwaccel_put_tag(skb, ntohs(vlan->vlan_tci) & ~VLAN_TAG_PRESENT);
     return 0;
    }
    
    static int set_eth_addr(struct sk_buff *skb,
             const struct ovs_key_ethernet *eth_key)
    {
     int err;
     err = make_writable(skb, ETH_HLEN);
     if (unlikely(err))
         return err;
    
     memcpy(eth_hdr(skb)->h_source, eth_key->eth_src, ETH_ALEN);
     memcpy(eth_hdr(skb)->h_dest, eth_key->eth_dst, ETH_ALEN);
    
     return 0;
    }
    
    static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh,
                 __be32 *addr, __be32 new_addr)
    {
     int transport_len = skb->len - skb_transport_offset(skb);
    
     if (nh->protocol == IPPROTO_TCP) {
         if (likely(transport_len >= sizeof(struct tcphdr)))
             inet_proto_csum_replace4(&tcp_hdr(skb)->check, skb,
                          *addr, new_addr, 1);
     } else if (nh->protocol == IPPROTO_UDP) {
         if (likely(transport_len >= sizeof(struct udphdr))) {
             struct udphdr *uh = udp_hdr(skb);
    
             if (uh->check ||
                 get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
                 inet_proto_csum_replace4(&uh->check, skb,
                              *addr, new_addr, 1);
                 if (!uh->check)
                     uh->check = CSUM_MANGLED_0;
             }
         }
     }
    
     csum_replace4(&nh->check, *addr, new_addr);
     skb_clear_rxhash(skb);
     *addr = new_addr;
    }
    
    static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto,
                  __be32 addr[4], const __be32 new_addr[4])
    {
     int transport_len = skb->len - skb_transport_offset(skb);
    
     if (l4_proto == IPPROTO_TCP) {
         if (likely(transport_len >= sizeof(struct tcphdr)))
             inet_proto_csum_replace16(&tcp_hdr(skb)->check, skb,
                           addr, new_addr, 1);
     } else if (l4_proto == IPPROTO_UDP) {
         if (likely(transport_len >= sizeof(struct udphdr))) {
             struct udphdr *uh = udp_hdr(skb);
    
             if (uh->check ||
                 get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
                 inet_proto_csum_replace16(&uh->check, skb,
                               addr, new_addr, 1);
                 if (!uh->check)
                     uh->check = CSUM_MANGLED_0;
             }
         }
     }
    }
    
    static void set_ipv6_addr(struct sk_buff *skb, u8 l4_proto,
               __be32 addr[4], const __be32 new_addr[4],
               bool recalculate_csum)
    {
     if (recalculate_csum)
         update_ipv6_checksum(skb, l4_proto, addr, new_addr);
    
     skb_clear_rxhash(skb);
     memcpy(addr, new_addr, sizeof(__be32[4]));
    }
    
    static void set_ipv6_tc(struct ipv6hdr *nh, u8 tc)
    {
     nh->priority = tc >> 4;
     nh->flow_lbl[0] = (nh->flow_lbl[0] & 0x0F) | ((tc & 0x0F) << 4);
    }
    
    static void set_ipv6_fl(struct ipv6hdr *nh, u32 fl)
    {
     nh->flow_lbl[0] = (nh->flow_lbl[0] & 0xF0) | (fl & 0x000F0000) >> 16;
     nh->flow_lbl[1] = (fl & 0x0000FF00) >> 8;
     nh->flow_lbl[2] = fl & 0x000000FF;
    }
    
    static void set_ip_ttl(struct sk_buff *skb, struct iphdr *nh, u8 new_ttl)
    {
     csum_replace2(&nh->check, htons(nh->ttl << 8), htons(new_ttl << 8));
     nh->ttl = new_ttl;
    }
    
    static int set_ipv4(struct sk_buff *skb, const struct ovs_key_ipv4 *ipv4_key)
    {
     struct iphdr *nh;
     int err;
    
     err = make_writable(skb, skb_network_offset(skb) +
                  sizeof(struct iphdr));
     if (unlikely(err))
         return err;
    
     nh = ip_hdr(skb);
    
     if (ipv4_key->ipv4_src != nh->saddr)
         set_ip_addr(skb, nh, &nh->saddr, ipv4_key->ipv4_src);
    
     if (ipv4_key->ipv4_dst != nh->daddr)
         set_ip_addr(skb, nh, &nh->daddr, ipv4_key->ipv4_dst);
    
     if (ipv4_key->ipv4_tos != nh->tos)
         ipv4_change_dsfield(nh, 0, ipv4_key->ipv4_tos);
    
     if (ipv4_key->ipv4_ttl != nh->ttl)
         set_ip_ttl(skb, nh, ipv4_key->ipv4_ttl);
    
     return 0;
    }
    
    static int set_ipv6(struct sk_buff *skb, const struct ovs_key_ipv6 *ipv6_key)
    {
     struct ipv6hdr *nh;
     int err;
     __be32 *saddr;
     __be32 *daddr;
    
     err = make_writable(skb, skb_network_offset(skb) +
                 sizeof(struct ipv6hdr));
     if (unlikely(err))
         return err;
    
     nh = ipv6_hdr(skb);
     saddr = (__be32 *)&nh->saddr;
     daddr = (__be32 *)&nh->daddr;
    
     if (memcmp(ipv6_key->ipv6_src, saddr, sizeof(ipv6_key->ipv6_src)))
         set_ipv6_addr(skb, ipv6_key->ipv6_proto, saddr,
                   ipv6_key->ipv6_src, true);
    
     if (memcmp(ipv6_key->ipv6_dst, daddr, sizeof(ipv6_key->ipv6_dst))) {
         unsigned int offset = 0;
         int flags = OVS_IP6T_FH_F_SKIP_RH;
         bool recalc_csum = true;
    
         if (ipv6_ext_hdr(nh->nexthdr))
             recalc_csum = ipv6_find_hdr(skb, &offset,
                             NEXTHDR_ROUTING, NULL,
                             &flags) != NEXTHDR_ROUTING;
    
         set_ipv6_addr(skb, ipv6_key->ipv6_proto, daddr,
                   ipv6_key->ipv6_dst, recalc_csum);
     }
    
     set_ipv6_tc(nh, ipv6_key->ipv6_tclass);
     set_ipv6_fl(nh, ntohl(ipv6_key->ipv6_label));
     nh->hop_limit = ipv6_key->ipv6_hlimit;
    
     return 0;
    }
    
    /* Must follow make_writable() since that can move the skb data. */
    static void set_tp_port(struct sk_buff *skb, __be16 *port,
              __be16 new_port, __sum16 *check)
    {
     inet_proto_csum_replace2(check, skb, *port, new_port, 0);
     *port = new_port;
     skb_clear_rxhash(skb);
    }
    
    static void set_udp_port(struct sk_buff *skb, __be16 *port, __be16 new_port)
    {
     struct udphdr *uh = udp_hdr(skb);
    
     if (uh->check && get_ip_summed(skb) != OVS_CSUM_PARTIAL) {
         set_tp_port(skb, port, new_port, &uh->check);
    
         if (!uh->check)
             uh->check = CSUM_MANGLED_0;
     } else {
         *port = new_port;
         skb_clear_rxhash(skb);
     }
    }
    
    static int set_udp(struct sk_buff *skb, const struct ovs_key_udp *udp_port_key)
    {
     struct udphdr *uh;
     int err;
    
     err = make_writable(skb, skb_transport_offset(skb) +
                  sizeof(struct udphdr));
     if (unlikely(err))
         return err;
    
     uh = udp_hdr(skb);
     if (udp_port_key->udp_src != uh->source)
         set_udp_port(skb, &uh->source, udp_port_key->udp_src);
    
     if (udp_port_key->udp_dst != uh->dest)
         set_udp_port(skb, &uh->dest, udp_port_key->udp_dst);
    
     return 0;
    }
    
    static int set_tcp(struct sk_buff *skb, const struct ovs_key_tcp *tcp_port_key)
    {
     struct tcphdr *th;
     int err;
    
     err = make_writable(skb, skb_transport_offset(skb) +
                  sizeof(struct tcphdr));
     if (unlikely(err))
         return err;
    
     th = tcp_hdr(skb);
     if (tcp_port_key->tcp_src != th->source)
         set_tp_port(skb, &th->source, tcp_port_key->tcp_src, &th->check);
    
     if (tcp_port_key->tcp_dst != th->dest)
         set_tp_port(skb, &th->dest, tcp_port_key->tcp_dst, &th->check);
    
     return 0;
    }
    
    static int do_output(struct datapath *dp, struct sk_buff *skb, int out_port)
    {
     struct vport *vport;
    
     if (unlikely(!skb))
         return -ENOMEM;
    
     vport = ovs_vport_rcu(dp, out_port);
     if (unlikely(!vport)) {
         kfree_skb(skb);
         return -ENODEV;
     }
    
     ovs_vport_send(vport, skb);
     return 0;
    }
    
    static int output_userspace(struct datapath *dp, struct sk_buff *skb,
                 const struct nlattr *attr)
    {
     struct dp_upcall_info upcall;
     const struct nlattr *a;
     int rem;
    
     upcall.cmd = OVS_PACKET_CMD_ACTION;
     upcall.key = &OVS_CB(skb)->flow->key;
     upcall.userdata = NULL;
     upcall.pid = 0;
    
     for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
          a = nla_next(a, &rem)) {
         switch (nla_type(a)) {
         case OVS_USERSPACE_ATTR_USERDATA:
             upcall.userdata = a;
             break;
    
         case OVS_USERSPACE_ATTR_PID:
             upcall.pid = nla_get_u32(a);
             break;
         }
     }
    
     return ovs_dp_upcall(dp, skb, &upcall);
    }
    
    static int sample(struct datapath *dp, struct sk_buff *skb,
           const struct nlattr *attr,
           struct ovs_key_ipv4_tunnel *tun_key)
    {
     const struct nlattr *acts_list = NULL;
     const struct nlattr *a;
     int rem;
    
     for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
          a = nla_next(a, &rem)) {
         switch (nla_type(a)) {
         case OVS_SAMPLE_ATTR_PROBABILITY:
             if (net_random() >= nla_get_u32(a))
                 return 0;
             break;
    
         case OVS_SAMPLE_ATTR_ACTIONS:
             acts_list = a;
             break;
         }
     }
    
     return do_execute_actions(dp, skb, nla_data(acts_list),
                   nla_len(acts_list), tun_key, true);
    }
    
    static int execute_set_action(struct sk_buff *skb,
                  const struct nlattr *nested_attr,
                  struct ovs_key_ipv4_tunnel *tun_key)
    {
     int err = 0;
    
     switch (nla_type(nested_attr)) {
     case OVS_KEY_ATTR_PRIORITY:
         skb->priority = nla_get_u32(nested_attr);
         break;
    
     case OVS_KEY_ATTR_TUN_ID:
         /* If we're only using the TUN_ID action, store the value in a
          * temporary instance of struct ovs_key_ipv4_tunnel on the stack.
          * If both IPV4_TUNNEL and TUN_ID are being used together we
          * can't write into the IPV4_TUNNEL action, so make a copy and
          * write into that version.
          */
         if (!OVS_CB(skb)->tun_key)
             memset(tun_key, 0, sizeof(*tun_key));
         else if (OVS_CB(skb)->tun_key != tun_key)
             memcpy(tun_key, OVS_CB(skb)->tun_key, sizeof(*tun_key));
         OVS_CB(skb)->tun_key = tun_key;
    
         OVS_CB(skb)->tun_key->tun_id = nla_get_be64(nested_attr);
         break;
    
     case OVS_KEY_ATTR_IPV4_TUNNEL:
         OVS_CB(skb)->tun_key = nla_data(nested_attr);
         break;
    
     case OVS_KEY_ATTR_ETHERNET:
         err = set_eth_addr(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_IPV4:
         err = set_ipv4(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_IPV6:
         err = set_ipv6(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_TCP:
         err = set_tcp(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_UDP:
         err = set_udp(skb, nla_data(nested_attr));
         break;
     }
    
     return err;
    }
    
    /* Execute a list of actions against 'skb'. */
    static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
             const struct nlattr *attr, int len,
             struct ovs_key_ipv4_tunnel *tun_key, bool keep_skb)
    {
     /* Every output action needs a separate clone of 'skb', but the common
      * case is just a single output action, so that doing a clone and
      * then freeing the original skbuff is wasteful.  So the following code
      * is slightly obscure just to avoid that. */
     int prev_port = -1;
     const struct nlattr *a;
     int rem;
    
     for (a = attr, rem = len; rem > 0;
          a = nla_next(a, &rem)) {
         int err = 0;
    
         if (prev_port != -1) {
             do_output(dp, skb_clone(skb, GFP_ATOMIC), prev_port);
             prev_port = -1;
         }
    
         switch (nla_type(a)) {
         case OVS_ACTION_ATTR_OUTPUT:
             prev_port = nla_get_u32(a);
             break;
    
         case OVS_ACTION_ATTR_USERSPACE:
             output_userspace(dp, skb, a);
             break;
    
         case OVS_ACTION_ATTR_PUSH_VLAN:
             err = push_vlan(skb, nla_data(a));
             if (unlikely(err)) /* skb already freed. */
                 return err;
             break;
    
         case OVS_ACTION_ATTR_POP_VLAN:
             err = pop_vlan(skb);
             break;
    
         case OVS_ACTION_ATTR_SET:
             err = execute_set_action(skb, nla_data(a), tun_key);
             break;
    
         case OVS_ACTION_ATTR_SAMPLE:
             err = sample(dp, skb, a, tun_key);
             break;
         }
    
         if (unlikely(err)) {
             kfree_skb(skb);
             return err;
         }
     }
    
     if (prev_port != -1) {
         if (keep_skb)
             skb = skb_clone(skb, GFP_ATOMIC);
    
         do_output(dp, skb, prev_port);
     } else if (!keep_skb)
         consume_skb(skb);
    
     return 0;
    }
    
    /* We limit the number of times that we pass into execute_actions()
     * to avoid blowing out the stack in the event that we have a loop. */
    #define MAX_LOOPS 5
    
    struct loop_counter {
     u8 count;        /* Count. */
     bool looping;        /* Loop detected? */
    };
    
    static DEFINE_PER_CPU(struct loop_counter, loop_counters);
    
    static int loop_suppress(struct datapath *dp, struct sw_flow_actions *actions)
    {
     if (net_ratelimit())
         pr_warn("%s: flow looped %d times, dropping\n",
                 ovs_dp_name(dp), MAX_LOOPS);
     actions->actions_len = 0;
     return -ELOOP;
    }
    
    /* Execute a list of actions against 'skb'. */
    int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb)
    {
     struct sw_flow_actions *acts = rcu_dereference(OVS_CB(skb)->flow->sf_acts);
     struct loop_counter *loop;
     int error;
     struct ovs_key_ipv4_tunnel tun_key;
    
     /* Check whether we've looped too much. */
     loop = &__get_cpu_var(loop_counters);
     if (unlikely(++loop->count > MAX_LOOPS))
         loop->looping = true;
     if (unlikely(loop->looping)) {
         error = loop_suppress(dp, acts);
         kfree_skb(skb);
         goto out_loop;
     }
    
     OVS_CB(skb)->tun_key = NULL;
     error = do_execute_actions(dp, skb, acts->actions,
                      acts->actions_len, &tun_key, false);
    
     /* Check whether sub-actions looped too much. */
     if (unlikely(loop->looping))
         error = loop_suppress(dp, acts);
    
    out_loop:
     /* Decrement loop counter. */
     if (!--loop->count)
         loop->looping = false;
    
     return error;
    }
  2. 独立内核模块示例代码

    #include <linux/module.h>
    #include <linux/netfilter.h>
    #include <linux/netfilter_ipv4.h>
    #include <linux/ip.h>
    #include <linux/udp.h>
    #include <linux/skbuff.h>
    
    #define TARGET_PORT 12345  // 设置特定的目的端口
    #define MULTICAST_IP "224.0.0.1"  // 指定的组播IP地址
    
    // Netfilter钩子函数,用于处理入站的数据包
    static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
    {
     struct iphdr *ip_header;
     struct udphdr *udp_header;
    
     // 检查skb包是否为空
     if (!skb)
         return NF_ACCEPT;
    
     // 检查skb包是否包含IP头部
     if (!skb_network_header(skb))
         return NF_ACCEPT;
    
     // 获取IP头部和UDP头部
     ip_header = ip_hdr(skb);
     udp_header = (struct udphdr *)(skb_transport_header(skb) + ip_hdrlen(skb));
    
     // 检查是否为UDP协议和特定目的端口
     if (ip_header->protocol == IPPROTO_UDP && ntohs(udp_header->dest) == TARGET_PORT) {
         // 修改目的IP地址为指定的组播IP
         ip_header->daddr = in_aton(MULTICAST_IP);
    
         // 重新计算校验值
         udp_header->check = 0;
         udp_header->check = csum_tcpudp_magic(ip_header->saddr, ip_header->daddr,
                                                skb->len - ip_hdrlen(skb), IPPROTO_UDP, csum_partial(udp_header, skb->len - ip_hdrlen(skb), 0));
    
         // 更新校验和
         skb->ip_summed = CHECKSUM_NONE;
         skb->pkt_type = PACKET_MULTICAST;
    
         // 让tcp/ip协议栈处理skb包
         return NF_ACCEPT;
     }
    
     return NF_ACCEPT;
    }
    
    // 初始化和清理模块
    static struct nf_hook_ops nfho = {
     .hook = hook_func,
     .hooknum = NF_BR_PRE_ROUTING,
     .pf = NFPROTO_BRIDGE,
     .priority = NF_BR_PRI_FIRST,
    };
    
    static int __init my_module_init(void)
    {
     return nf_register_hook(&nfho);
    }
    
    static void __exit my_module_exit(void)
    {
     nf_unregister_hook(&nfho);
    }
    
    module_init(my_module_init);
    module_exit(my_module_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Kimson");
  3. linux内核中如何修改skb报文
  4. linux-2-6-network-stack-paper.pdf
  5. skbuff.pdf

标签: none

添加新评论