Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
c675e06a | 2 | /* Copyright (c) 2014 Mahesh Bandewar <maheshb@google.com> |
c675e06a DB |
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(dev); | |
25 | if (!port || port->mode != IPVLAN_MODE_L3S) | |
26 | goto out; | |
27 | ||
28 | lyr3h = ipvlan_get_L3_hdr(port, skb, &addr_type); | |
29 | if (!lyr3h) | |
30 | goto out; | |
31 | ||
32 | addr = ipvlan_addr_lookup(port, lyr3h, addr_type, 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, ip4h->daddr, ip4h->saddr, | |
55 | ip4h->tos, 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(ip6h), | |
71 | .flowi6_mark = skb->mark, | |
72 | .flowi6_proto = ip6h->nexthdr, | |
73 | }; | |
74 | ||
75 | skb_dst_drop(skb); | |
76 | dst = ip6_route_input_lookup(dev_net(sdev), sdev, &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, skb->dev); | |
100 | if (!addr) | |
101 | goto out; | |
102 | ||
103 | skb->dev = addr->master->dev; | |
59a0b022 | 104 | skb->skb_iif = skb->dev->ifindex; |
c675e06a DB |
105 | len = skb->len + ETH_HLEN; |
106 | ipvlan_count_rx(addr->master, len, true, false); | |
107 | out: | |
108 | return NF_ACCEPT; | |
109 | } | |
110 | ||
111 | static const struct nf_hook_ops ipvl_nfops[] = { | |
112 | { | |
113 | .hook = ipvlan_nf_input, | |
114 | .pf = NFPROTO_IPV4, | |
115 | .hooknum = NF_INET_LOCAL_IN, | |
116 | .priority = INT_MAX, | |
117 | }, | |
118 | #if IS_ENABLED(CONFIG_IPV6) | |
119 | { | |
120 | .hook = ipvlan_nf_input, | |
121 | .pf = NFPROTO_IPV6, | |
122 | .hooknum = NF_INET_LOCAL_IN, | |
123 | .priority = INT_MAX, | |
124 | }, | |
125 | #endif | |
126 | }; | |
127 | ||
128 | static int ipvlan_register_nf_hook(struct net *net) | |
129 | { | |
130 | struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid); | |
131 | int err = 0; | |
132 | ||
133 | if (!vnet->ipvl_nf_hook_refcnt) { | |
134 | err = nf_register_net_hooks(net, ipvl_nfops, | |
135 | ARRAY_SIZE(ipvl_nfops)); | |
136 | if (!err) | |
137 | vnet->ipvl_nf_hook_refcnt = 1; | |
138 | } else { | |
139 | vnet->ipvl_nf_hook_refcnt++; | |
140 | } | |
141 | ||
142 | return err; | |
143 | } | |
144 | ||
145 | static void ipvlan_unregister_nf_hook(struct net *net) | |
146 | { | |
147 | struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid); | |
148 | ||
149 | if (WARN_ON(!vnet->ipvl_nf_hook_refcnt)) | |
150 | return; | |
151 | ||
152 | vnet->ipvl_nf_hook_refcnt--; | |
153 | if (!vnet->ipvl_nf_hook_refcnt) | |
154 | nf_unregister_net_hooks(net, ipvl_nfops, | |
155 | ARRAY_SIZE(ipvl_nfops)); | |
156 | } | |
157 | ||
158 | void ipvlan_migrate_l3s_hook(struct net *oldnet, struct net *newnet) | |
159 | { | |
160 | struct ipvlan_netns *old_vnet; | |
161 | ||
162 | ASSERT_RTNL(); | |
163 | ||
164 | old_vnet = net_generic(oldnet, ipvlan_netid); | |
165 | if (!old_vnet->ipvl_nf_hook_refcnt) | |
166 | return; | |
167 | ||
168 | ipvlan_register_nf_hook(newnet); | |
169 | ipvlan_unregister_nf_hook(oldnet); | |
170 | } | |
171 | ||
172 | static void ipvlan_ns_exit(struct net *net) | |
173 | { | |
174 | struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid); | |
175 | ||
176 | if (WARN_ON_ONCE(vnet->ipvl_nf_hook_refcnt)) { | |
177 | vnet->ipvl_nf_hook_refcnt = 0; | |
178 | nf_unregister_net_hooks(net, ipvl_nfops, | |
179 | ARRAY_SIZE(ipvl_nfops)); | |
180 | } | |
181 | } | |
182 | ||
183 | static struct pernet_operations ipvlan_net_ops = { | |
184 | .id = &ipvlan_netid, | |
185 | .size = sizeof(struct ipvlan_netns), | |
186 | .exit = ipvlan_ns_exit, | |
187 | }; | |
188 | ||
189 | int ipvlan_l3s_init(void) | |
190 | { | |
191 | return register_pernet_subsys(&ipvlan_net_ops); | |
192 | } | |
193 | ||
194 | void ipvlan_l3s_cleanup(void) | |
195 | { | |
196 | unregister_pernet_subsys(&ipvlan_net_ops); | |
197 | } | |
198 | ||
199 | int ipvlan_l3s_register(struct ipvl_port *port) | |
200 | { | |
201 | struct net_device *dev = port->dev; | |
202 | int ret; | |
203 | ||
204 | ASSERT_RTNL(); | |
205 | ||
206 | ret = ipvlan_register_nf_hook(read_pnet(&port->pnet)); | |
207 | if (!ret) { | |
208 | dev->l3mdev_ops = &ipvl_l3mdev_ops; | |
a655fe9f | 209 | dev->priv_flags |= IFF_L3MDEV_RX_HANDLER; |
c675e06a DB |
210 | } |
211 | ||
212 | return ret; | |
213 | } | |
214 | ||
215 | void ipvlan_l3s_unregister(struct ipvl_port *port) | |
216 | { | |
217 | struct net_device *dev = port->dev; | |
218 | ||
219 | ASSERT_RTNL(); | |
220 | ||
a655fe9f | 221 | dev->priv_flags &= ~IFF_L3MDEV_RX_HANDLER; |
c675e06a DB |
222 | ipvlan_unregister_nf_hook(read_pnet(&port->pnet)); |
223 | dev->l3mdev_ops = NULL; | |
224 | } |