bna: use list_for_each_entry where appropriate
[cascardo/linux.git] / net / netfilter / nf_tables_netdev.c
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 <net/netfilter/nf_tables.h>
12 #include <linux/ip.h>
13 #include <linux/ipv6.h>
14 #include <net/netfilter/nf_tables_ipv4.h>
15 #include <net/netfilter/nf_tables_ipv6.h>
16
17 static inline void
18 nft_netdev_set_pktinfo_ipv4(struct nft_pktinfo *pkt,
19                             const struct nf_hook_ops *ops, struct sk_buff *skb,
20                             const struct nf_hook_state *state)
21 {
22         struct iphdr *iph, _iph;
23         u32 len, thoff;
24
25         nft_set_pktinfo(pkt, ops, skb, state);
26
27         iph = skb_header_pointer(skb, skb_network_offset(skb), sizeof(*iph),
28                                  &_iph);
29         if (!iph)
30                 return;
31
32         iph = ip_hdr(skb);
33         if (iph->ihl < 5 || iph->version != 4)
34                 return;
35
36         len = ntohs(iph->tot_len);
37         thoff = iph->ihl * 4;
38         if (skb->len < len)
39                 return;
40         else if (len < thoff)
41                 return;
42
43         pkt->tprot = iph->protocol;
44         pkt->xt.thoff = thoff;
45         pkt->xt.fragoff = ntohs(iph->frag_off) & IP_OFFSET;
46 }
47
48 static inline void
49 __nft_netdev_set_pktinfo_ipv6(struct nft_pktinfo *pkt,
50                               const struct nf_hook_ops *ops,
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
83 static inline void nft_netdev_set_pktinfo_ipv6(struct nft_pktinfo *pkt,
84                                                const struct nf_hook_ops *ops,
85                                                struct sk_buff *skb,
86                                                const struct nf_hook_state *state)
87 {
88         nft_set_pktinfo(pkt, ops, skb, state);
89         __nft_netdev_set_pktinfo_ipv6(pkt, ops, skb, state);
90 }
91
92 static unsigned int
93 nft_do_chain_netdev(const struct nf_hook_ops *ops, struct sk_buff *skb,
94                     const struct nf_hook_state *state)
95 {
96         struct nft_pktinfo pkt;
97
98         switch (eth_hdr(skb)->h_proto) {
99         case htons(ETH_P_IP):
100                 nft_netdev_set_pktinfo_ipv4(&pkt, ops, skb, state);
101                 break;
102         case htons(ETH_P_IPV6):
103                 nft_netdev_set_pktinfo_ipv6(&pkt, ops, skb, state);
104                 break;
105         default:
106                 nft_set_pktinfo(&pkt, ops, skb, state);
107                 break;
108         }
109
110         return nft_do_chain(&pkt, ops);
111 }
112
113 static struct nft_af_info nft_af_netdev __read_mostly = {
114         .family         = NFPROTO_NETDEV,
115         .nhooks         = NF_NETDEV_NUMHOOKS,
116         .owner          = THIS_MODULE,
117         .flags          = NFT_AF_NEEDS_DEV,
118         .nops           = 1,
119         .hooks          = {
120                 [NF_NETDEV_INGRESS]     = nft_do_chain_netdev,
121         },
122 };
123
124 static int nf_tables_netdev_init_net(struct net *net)
125 {
126         net->nft.netdev = kmalloc(sizeof(struct nft_af_info), GFP_KERNEL);
127         if (net->nft.netdev == NULL)
128                 return -ENOMEM;
129
130         memcpy(net->nft.netdev, &nft_af_netdev, sizeof(nft_af_netdev));
131
132         if (nft_register_afinfo(net, net->nft.netdev) < 0)
133                 goto err;
134
135         return 0;
136 err:
137         kfree(net->nft.netdev);
138         return -ENOMEM;
139 }
140
141 static void nf_tables_netdev_exit_net(struct net *net)
142 {
143         nft_unregister_afinfo(net->nft.netdev);
144         kfree(net->nft.netdev);
145 }
146
147 static struct pernet_operations nf_tables_netdev_net_ops = {
148         .init   = nf_tables_netdev_init_net,
149         .exit   = nf_tables_netdev_exit_net,
150 };
151
152 static const struct nf_chain_type nft_filter_chain_netdev = {
153         .name           = "filter",
154         .type           = NFT_CHAIN_T_DEFAULT,
155         .family         = NFPROTO_NETDEV,
156         .owner          = THIS_MODULE,
157         .hook_mask      = (1 << NF_NETDEV_INGRESS),
158 };
159
160 static int __init nf_tables_netdev_init(void)
161 {
162         int ret;
163
164         nft_register_chain_type(&nft_filter_chain_netdev);
165         ret = register_pernet_subsys(&nf_tables_netdev_net_ops);
166         if (ret < 0)
167                 nft_unregister_chain_type(&nft_filter_chain_netdev);
168
169         return ret;
170 }
171
172 static void __exit nf_tables_netdev_exit(void)
173 {
174         unregister_pernet_subsys(&nf_tables_netdev_net_ops);
175         nft_unregister_chain_type(&nft_filter_chain_netdev);
176 }
177
178 module_init(nf_tables_netdev_init);
179 module_exit(nf_tables_netdev_exit);
180
181 MODULE_LICENSE("GPL");
182 MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
183 MODULE_ALIAS_NFT_FAMILY(5); /* NFPROTO_NETDEV */