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