linux/net/netfilter/nf_tables_netdev.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2015 Pablo Neira Ayuso <pablo@netfilter.org>
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License version 2 as
   6 * published by the Free Software Foundation.
   7 */
   8
   9#include <linux/init.h>
  10#include <linux/module.h>
  11#include <linux/netdevice.h>
  12#include <net/netfilter/nf_tables.h>
  13#include <linux/ip.h>
  14#include <linux/ipv6.h>
  15#include <net/netfilter/nf_tables_ipv4.h>
  16#include <net/netfilter/nf_tables_ipv6.h>
  17
  18static inline void
  19nft_netdev_set_pktinfo_ipv4(struct nft_pktinfo *pkt,
  20                            struct sk_buff *skb,
  21                            const struct nf_hook_state *state)
  22{
  23        struct iphdr *iph, _iph;
  24        u32 len, thoff;
  25
  26        nft_set_pktinfo(pkt, skb, state);
  27
  28        iph = skb_header_pointer(skb, skb_network_offset(skb), sizeof(*iph),
  29                                 &_iph);
  30        if (!iph)
  31                return;
  32
  33        iph = ip_hdr(skb);
  34        if (iph->ihl < 5 || iph->version != 4)
  35                return;
  36
  37        len = ntohs(iph->tot_len);
  38        thoff = iph->ihl * 4;
  39        if (skb->len < len)
  40                return;
  41        else if (len < thoff)
  42                return;
  43
  44        pkt->tprot = iph->protocol;
  45        pkt->xt.thoff = thoff;
  46        pkt->xt.fragoff = ntohs(iph->frag_off) & IP_OFFSET;
  47}
  48
  49static inline void
  50__nft_netdev_set_pktinfo_ipv6(struct nft_pktinfo *pkt,
  51                              struct sk_buff *skb,
  52                              const struct nf_hook_state *state)
  53{
  54#if IS_ENABLED(CONFIG_IPV6)
  55        struct ipv6hdr *ip6h, _ip6h;
  56        unsigned int thoff = 0;
  57        unsigned short frag_off;
  58        int protohdr;
  59        u32 pkt_len;
  60
  61        ip6h = skb_header_pointer(skb, skb_network_offset(skb), sizeof(*ip6h),
  62                                  &_ip6h);
  63        if (!ip6h)
  64                return;
  65
  66        if (ip6h->version != 6)
  67                return;
  68
  69        pkt_len = ntohs(ip6h->payload_len);
  70        if (pkt_len + sizeof(*ip6h) > skb->len)
  71                return;
  72
  73        protohdr = ipv6_find_hdr(pkt->skb, &thoff, -1, &frag_off, NULL);
  74        if (protohdr < 0)
  75                return;
  76
  77        pkt->tprot = protohdr;
  78        pkt->xt.thoff = thoff;
  79        pkt->xt.fragoff = frag_off;
  80#endif
  81}
  82
  83static inline void nft_netdev_set_pktinfo_ipv6(struct nft_pktinfo *pkt,
  84                                               struct sk_buff *skb,
  85                                               const struct nf_hook_state *state)
  86{
  87        nft_set_pktinfo(pkt, skb, state);
  88        __nft_netdev_set_pktinfo_ipv6(pkt, skb, state);
  89}
  90
  91static unsigned int
  92nft_do_chain_netdev(void *priv, struct sk_buff *skb,
  93                    const struct nf_hook_state *state)
  94{
  95        struct nft_pktinfo pkt;
  96
  97        switch (skb->protocol) {
  98        case htons(ETH_P_IP):
  99                nft_netdev_set_pktinfo_ipv4(&pkt, skb, state);
 100                break;
 101        case htons(ETH_P_IPV6):
 102                nft_netdev_set_pktinfo_ipv6(&pkt, skb, state);
 103                break;
 104        default:
 105                nft_set_pktinfo(&pkt, skb, state);
 106                break;
 107        }
 108
 109        return nft_do_chain(&pkt, priv);
 110}
 111
 112static struct nft_af_info nft_af_netdev __read_mostly = {
 113        .family         = NFPROTO_NETDEV,
 114        .nhooks         = NF_NETDEV_NUMHOOKS,
 115        .owner          = THIS_MODULE,
 116        .flags          = NFT_AF_NEEDS_DEV,
 117        .nops           = 1,
 118        .hooks          = {
 119                [NF_NETDEV_INGRESS]     = nft_do_chain_netdev,
 120        },
 121};
 122
 123static int nf_tables_netdev_init_net(struct net *net)
 124{
 125        net->nft.netdev = kmalloc(sizeof(struct nft_af_info), GFP_KERNEL);
 126        if (net->nft.netdev == NULL)
 127                return -ENOMEM;
 128
 129        memcpy(net->nft.netdev, &nft_af_netdev, sizeof(nft_af_netdev));
 130
 131        if (nft_register_afinfo(net, net->nft.netdev) < 0)
 132                goto err;
 133
 134        return 0;
 135err:
 136        kfree(net->nft.netdev);
 137        return -ENOMEM;
 138}
 139
 140static void nf_tables_netdev_exit_net(struct net *net)
 141{
 142        nft_unregister_afinfo(net->nft.netdev);
 143        kfree(net->nft.netdev);
 144}
 145
 146static struct pernet_operations nf_tables_netdev_net_ops = {
 147        .init   = nf_tables_netdev_init_net,
 148        .exit   = nf_tables_netdev_exit_net,
 149};
 150
 151static const struct nf_chain_type nft_filter_chain_netdev = {
 152        .name           = "filter",
 153        .type           = NFT_CHAIN_T_DEFAULT,
 154        .family         = NFPROTO_NETDEV,
 155        .owner          = THIS_MODULE,
 156        .hook_mask      = (1 << NF_NETDEV_INGRESS),
 157};
 158
 159static void nft_netdev_event(unsigned long event, struct nft_af_info *afi,
 160                             struct net_device *dev, struct nft_table *table,
 161                             struct nft_base_chain *basechain)
 162{
 163        switch (event) {
 164        case NETDEV_REGISTER:
 165                if (strcmp(basechain->dev_name, dev->name) != 0)
 166                        return;
 167
 168                BUG_ON(!(basechain->flags & NFT_BASECHAIN_DISABLED));
 169
 170                dev_hold(dev);
 171                basechain->ops[0].dev = dev;
 172                basechain->flags &= ~NFT_BASECHAIN_DISABLED;
 173                if (!(table->flags & NFT_TABLE_F_DORMANT))
 174                        nft_register_basechain(basechain, afi->nops);
 175                break;
 176        case NETDEV_UNREGISTER:
 177                if (strcmp(basechain->dev_name, dev->name) != 0)
 178                        return;
 179
 180                BUG_ON(basechain->flags & NFT_BASECHAIN_DISABLED);
 181
 182                if (!(table->flags & NFT_TABLE_F_DORMANT))
 183                        nft_unregister_basechain(basechain, afi->nops);
 184
 185                dev_put(basechain->ops[0].dev);
 186                basechain->ops[0].dev = NULL;
 187                basechain->flags |= NFT_BASECHAIN_DISABLED;
 188                break;
 189        case NETDEV_CHANGENAME:
 190                if (dev->ifindex != basechain->ops[0].dev->ifindex)
 191                        return;
 192
 193                strncpy(basechain->dev_name, dev->name, IFNAMSIZ);
 194                break;
 195        }
 196}
 197
 198static int nf_tables_netdev_event(struct notifier_block *this,
 199                                  unsigned long event, void *ptr)
 200{
 201        struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 202        struct nft_af_info *afi;
 203        struct nft_table *table;
 204        struct nft_chain *chain;
 205
 206        nfnl_lock(NFNL_SUBSYS_NFTABLES);
 207        list_for_each_entry(afi, &dev_net(dev)->nft.af_info, list) {
 208                if (afi->family != NFPROTO_NETDEV)
 209                        continue;
 210
 211                list_for_each_entry(table, &afi->tables, list) {
 212                        list_for_each_entry(chain, &table->chains, list) {
 213                                if (!(chain->flags & NFT_BASE_CHAIN))
 214                                        continue;
 215
 216                                nft_netdev_event(event, afi, dev, table,
 217                                                 nft_base_chain(chain));
 218                        }
 219                }
 220        }
 221        nfnl_unlock(NFNL_SUBSYS_NFTABLES);
 222
 223        return NOTIFY_DONE;
 224}
 225
 226static struct notifier_block nf_tables_netdev_notifier = {
 227        .notifier_call  = nf_tables_netdev_event,
 228};
 229
 230static int __init nf_tables_netdev_init(void)
 231{
 232        int ret;
 233
 234        nft_register_chain_type(&nft_filter_chain_netdev);
 235        ret = register_pernet_subsys(&nf_tables_netdev_net_ops);
 236        if (ret < 0)
 237                nft_unregister_chain_type(&nft_filter_chain_netdev);
 238
 239        register_netdevice_notifier(&nf_tables_netdev_notifier);
 240
 241        return ret;
 242}
 243
 244static void __exit nf_tables_netdev_exit(void)
 245{
 246        unregister_netdevice_notifier(&nf_tables_netdev_notifier);
 247        unregister_pernet_subsys(&nf_tables_netdev_net_ops);
 248        nft_unregister_chain_type(&nft_filter_chain_netdev);
 249}
 250
 251module_init(nf_tables_netdev_init);
 252module_exit(nf_tables_netdev_exit);
 253
 254MODULE_LICENSE("GPL");
 255MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
 256MODULE_ALIAS_NFT_FAMILY(5); /* NFPROTO_NETDEV */
 257