1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* Copyright (c) 2014 Mahesh Bandewar <maheshb@google.com> |
3 | */ |
4 | |
5 | #include "ipvlan.h" |
6 | |
7 | static unsigned int ipvlan_netid __read_mostly; |
8 | |
9 | struct ipvlan_netns { |
10 | unsigned int ipvl_nf_hook_refcnt; |
11 | }; |
12 | |
13 | static struct ipvl_addr *ipvlan_skb_to_addr(struct sk_buff *skb, |
14 | struct net_device *dev) |
15 | { |
16 | struct ipvl_addr *addr = NULL; |
17 | struct ipvl_port *port; |
18 | int addr_type; |
19 | void *lyr3h; |
20 | |
21 | if (!dev || !netif_is_ipvlan_port(dev)) |
22 | goto out; |
23 | |
24 | port = ipvlan_port_get_rcu(d: dev); |
25 | if (!port || port->mode != IPVLAN_MODE_L3S) |
26 | goto out; |
27 | |
28 | lyr3h = ipvlan_get_L3_hdr(port, skb, type: &addr_type); |
29 | if (!lyr3h) |
30 | goto out; |
31 | |
32 | addr = ipvlan_addr_lookup(port, lyr3h, addr_type, use_dest: true); |
33 | out: |
34 | return addr; |
35 | } |
36 | |
37 | static struct sk_buff *ipvlan_l3_rcv(struct net_device *dev, |
38 | struct sk_buff *skb, u16 proto) |
39 | { |
40 | struct ipvl_addr *addr; |
41 | struct net_device *sdev; |
42 | |
43 | addr = ipvlan_skb_to_addr(skb, dev); |
44 | if (!addr) |
45 | goto out; |
46 | |
47 | sdev = addr->master->dev; |
48 | switch (proto) { |
49 | case AF_INET: |
50 | { |
51 | struct iphdr *ip4h = ip_hdr(skb); |
52 | int err; |
53 | |
54 | err = ip_route_input_noref(skb, dst: ip4h->daddr, src: ip4h->saddr, |
55 | tos: ip4h->tos, devin: sdev); |
56 | if (unlikely(err)) |
57 | goto out; |
58 | break; |
59 | } |
60 | #if IS_ENABLED(CONFIG_IPV6) |
61 | case AF_INET6: |
62 | { |
63 | struct dst_entry *dst; |
64 | struct ipv6hdr *ip6h = ipv6_hdr(skb); |
65 | int flags = RT6_LOOKUP_F_HAS_SADDR; |
66 | struct flowi6 fl6 = { |
67 | .flowi6_iif = sdev->ifindex, |
68 | .daddr = ip6h->daddr, |
69 | .saddr = ip6h->saddr, |
70 | .flowlabel = ip6_flowinfo(hdr: ip6h), |
71 | .flowi6_mark = skb->mark, |
72 | .flowi6_proto = ip6h->nexthdr, |
73 | }; |
74 | |
75 | skb_dst_drop(skb); |
76 | dst = ip6_route_input_lookup(net: dev_net(dev: sdev), dev: sdev, fl6: &fl6, |
77 | skb, flags); |
78 | skb_dst_set(skb, dst); |
79 | break; |
80 | } |
81 | #endif |
82 | default: |
83 | break; |
84 | } |
85 | out: |
86 | return skb; |
87 | } |
88 | |
89 | static const struct l3mdev_ops ipvl_l3mdev_ops = { |
90 | .l3mdev_l3_rcv = ipvlan_l3_rcv, |
91 | }; |
92 | |
93 | static unsigned int ipvlan_nf_input(void *priv, struct sk_buff *skb, |
94 | const struct nf_hook_state *state) |
95 | { |
96 | struct ipvl_addr *addr; |
97 | unsigned int len; |
98 | |
99 | addr = ipvlan_skb_to_addr(skb, dev: skb->dev); |
100 | if (!addr) |
101 | goto out; |
102 | |
103 | skb->dev = addr->master->dev; |
104 | skb->skb_iif = skb->dev->ifindex; |
105 | #if IS_ENABLED(CONFIG_IPV6) |
106 | if (addr->atype == IPVL_IPV6) |
107 | IP6CB(skb)->iif = skb->dev->ifindex; |
108 | #endif |
109 | len = skb->len + ETH_HLEN; |
110 | ipvlan_count_rx(ipvlan: addr->master, len, success: true, mcast: false); |
111 | out: |
112 | return NF_ACCEPT; |
113 | } |
114 | |
115 | static const struct nf_hook_ops ipvl_nfops[] = { |
116 | { |
117 | .hook = ipvlan_nf_input, |
118 | .pf = NFPROTO_IPV4, |
119 | .hooknum = NF_INET_LOCAL_IN, |
120 | .priority = INT_MAX, |
121 | }, |
122 | #if IS_ENABLED(CONFIG_IPV6) |
123 | { |
124 | .hook = ipvlan_nf_input, |
125 | .pf = NFPROTO_IPV6, |
126 | .hooknum = NF_INET_LOCAL_IN, |
127 | .priority = INT_MAX, |
128 | }, |
129 | #endif |
130 | }; |
131 | |
132 | static int ipvlan_register_nf_hook(struct net *net) |
133 | { |
134 | struct ipvlan_netns *vnet = net_generic(net, id: ipvlan_netid); |
135 | int err = 0; |
136 | |
137 | if (!vnet->ipvl_nf_hook_refcnt) { |
138 | err = nf_register_net_hooks(net, reg: ipvl_nfops, |
139 | ARRAY_SIZE(ipvl_nfops)); |
140 | if (!err) |
141 | vnet->ipvl_nf_hook_refcnt = 1; |
142 | } else { |
143 | vnet->ipvl_nf_hook_refcnt++; |
144 | } |
145 | |
146 | return err; |
147 | } |
148 | |
149 | static void ipvlan_unregister_nf_hook(struct net *net) |
150 | { |
151 | struct ipvlan_netns *vnet = net_generic(net, id: ipvlan_netid); |
152 | |
153 | if (WARN_ON(!vnet->ipvl_nf_hook_refcnt)) |
154 | return; |
155 | |
156 | vnet->ipvl_nf_hook_refcnt--; |
157 | if (!vnet->ipvl_nf_hook_refcnt) |
158 | nf_unregister_net_hooks(net, reg: ipvl_nfops, |
159 | ARRAY_SIZE(ipvl_nfops)); |
160 | } |
161 | |
162 | void ipvlan_migrate_l3s_hook(struct net *oldnet, struct net *newnet) |
163 | { |
164 | struct ipvlan_netns *old_vnet; |
165 | |
166 | ASSERT_RTNL(); |
167 | |
168 | old_vnet = net_generic(net: oldnet, id: ipvlan_netid); |
169 | if (!old_vnet->ipvl_nf_hook_refcnt) |
170 | return; |
171 | |
172 | ipvlan_register_nf_hook(net: newnet); |
173 | ipvlan_unregister_nf_hook(net: oldnet); |
174 | } |
175 | |
176 | static void ipvlan_ns_exit(struct net *net) |
177 | { |
178 | struct ipvlan_netns *vnet = net_generic(net, id: ipvlan_netid); |
179 | |
180 | if (WARN_ON_ONCE(vnet->ipvl_nf_hook_refcnt)) { |
181 | vnet->ipvl_nf_hook_refcnt = 0; |
182 | nf_unregister_net_hooks(net, reg: ipvl_nfops, |
183 | ARRAY_SIZE(ipvl_nfops)); |
184 | } |
185 | } |
186 | |
187 | static struct pernet_operations ipvlan_net_ops = { |
188 | .id = &ipvlan_netid, |
189 | .size = sizeof(struct ipvlan_netns), |
190 | .exit = ipvlan_ns_exit, |
191 | }; |
192 | |
193 | int ipvlan_l3s_init(void) |
194 | { |
195 | return register_pernet_subsys(&ipvlan_net_ops); |
196 | } |
197 | |
198 | void ipvlan_l3s_cleanup(void) |
199 | { |
200 | unregister_pernet_subsys(&ipvlan_net_ops); |
201 | } |
202 | |
203 | int ipvlan_l3s_register(struct ipvl_port *port) |
204 | { |
205 | struct net_device *dev = port->dev; |
206 | int ret; |
207 | |
208 | ASSERT_RTNL(); |
209 | |
210 | ret = ipvlan_register_nf_hook(net: read_pnet(pnet: &port->pnet)); |
211 | if (!ret) { |
212 | dev->l3mdev_ops = &ipvl_l3mdev_ops; |
213 | dev->priv_flags |= IFF_L3MDEV_RX_HANDLER; |
214 | } |
215 | |
216 | return ret; |
217 | } |
218 | |
219 | void ipvlan_l3s_unregister(struct ipvl_port *port) |
220 | { |
221 | struct net_device *dev = port->dev; |
222 | |
223 | ASSERT_RTNL(); |
224 | |
225 | dev->priv_flags &= ~IFF_L3MDEV_RX_HANDLER; |
226 | ipvlan_unregister_nf_hook(net: read_pnet(pnet: &port->pnet)); |
227 | dev->l3mdev_ops = NULL; |
228 | } |
229 | |