xref: /illumos-gate/usr/src/uts/intel/io/viona/viona_hook.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  *
11  * Copyright 2019 Joyent, Inc.
12  */
13 
14 #include <sys/hook.h>
15 #include <sys/hook_event.h>
16 
17 #include "viona_impl.h"
18 
19 
20 /*
21  * Global linked list of viona_neti_ts.  Access is protected by viona_neti_lock
22  */
23 static list_t		viona_neti_list;
24 static kmutex_t		viona_neti_lock;
25 
26 /*
27  * viona_neti is allocated and initialized during attach, and read-only
28  * until detach (where it's also freed)
29  */
30 static net_instance_t	*viona_neti;
31 
32 
33 /*
34  * Generate a hook event for the packet in *mpp headed in the direction
35  * indicated by 'out'.  If the packet is accepted, 0 is returned.  If the
36  * packet is rejected, an error is returned.  The hook function may or may not
37  * alter or even free *mpp.  The caller is expected to deal with either
38  * situation.
39  */
40 int
41 viona_hook(viona_link_t *link, viona_vring_t *ring, mblk_t **mpp, boolean_t out)
42 {
43 	viona_neti_t *nip = link->l_neti;
44 	viona_nethook_t *vnh = &nip->vni_nethook;
45 	hook_pkt_event_t info;
46 	hook_event_t he;
47 	hook_event_token_t het;
48 	int ret;
49 
50 	he = out ? vnh->vnh_event_out : vnh->vnh_event_in;
51 	het = out ? vnh->vnh_token_out : vnh->vnh_token_in;
52 
53 	if (!he.he_interested)
54 		return (0);
55 
56 	info.hpe_protocol = vnh->vnh_neti;
57 	info.hpe_ifp = (phy_if_t)link;
58 	info.hpe_ofp = (phy_if_t)link;
59 	info.hpe_mp = mpp;
60 	info.hpe_flags = 0;
61 
62 	ret = hook_run(vnh->vnh_neti->netd_hooks, het, (hook_data_t)&info);
63 	if (ret == 0)
64 		return (0);
65 
66 	if (out) {
67 		VIONA_PROBE3(tx_hook_drop, viona_vring_t *, ring,
68 		    mblk_t *, *mpp, int, ret);
69 		VIONA_RING_STAT_INCR(ring, tx_hookdrop);
70 	} else {
71 		VIONA_PROBE3(rx_hook_drop, viona_vring_t *, ring,
72 		    mblk_t *, *mpp, int, ret);
73 		VIONA_RING_STAT_INCR(ring, rx_hookdrop);
74 	}
75 	return (ret);
76 }
77 
78 /*
79  * netinfo stubs - required by the nethook framework, but otherwise unused
80  *
81  * Currently, all ipf rules are applied against all interfaces in a given
82  * netstack (e.g. all interfaces in a zone).  In the future if we want to
83  * support being able to apply different rules to different interfaces, I
84  * believe we would need to implement some of these stubs to map an interface
85  * name in a rule (e.g. 'net0', back to an index or viona_link_t);
86  */
87 static int
88 viona_neti_getifname(net_handle_t neti __unused, phy_if_t phy __unused,
89     char *buf __unused, const size_t len __unused)
90 {
91 	return (-1);
92 }
93 
94 static int
95 viona_neti_getmtu(net_handle_t neti __unused, phy_if_t phy __unused,
96     lif_if_t ifdata __unused)
97 {
98 	return (-1);
99 }
100 
101 static int
102 viona_neti_getptmue(net_handle_t neti __unused)
103 {
104 	return (-1);
105 }
106 
107 static int
108 viona_neti_getlifaddr(net_handle_t neti __unused, phy_if_t phy __unused,
109     lif_if_t ifdata __unused, size_t nelem __unused,
110     net_ifaddr_t type[] __unused, void *storage __unused)
111 {
112 	return (-1);
113 }
114 
115 static int
116 viona_neti_getlifzone(net_handle_t neti __unused, phy_if_t phy __unused,
117     lif_if_t ifdata __unused, zoneid_t *zid __unused)
118 {
119 	return (-1);
120 }
121 
122 static int
123 viona_neti_getlifflags(net_handle_t neti __unused, phy_if_t phy __unused,
124     lif_if_t ifdata __unused, uint64_t *flags __unused)
125 {
126 	return (-1);
127 }
128 
129 static phy_if_t
130 viona_neti_phygetnext(net_handle_t neti __unused, phy_if_t phy __unused)
131 {
132 	return ((phy_if_t)-1);
133 }
134 
135 static phy_if_t
136 viona_neti_phylookup(net_handle_t neti __unused, const char *name __unused)
137 {
138 	return ((phy_if_t)-1);
139 }
140 
141 static lif_if_t
142 viona_neti_lifgetnext(net_handle_t neti __unused, phy_if_t phy __unused,
143     lif_if_t ifdata __unused)
144 {
145 	return (-1);
146 }
147 
148 static int
149 viona_neti_inject(net_handle_t neti __unused, inject_t style __unused,
150     net_inject_t *packet __unused)
151 {
152 	return (-1);
153 }
154 
155 static phy_if_t
156 viona_neti_route(net_handle_t neti __unused, struct sockaddr *address __unused,
157     struct sockaddr *next __unused)
158 {
159 	return ((phy_if_t)-1);
160 }
161 
162 static int
163 viona_neti_ispchksum(net_handle_t neti __unused, mblk_t *mp __unused)
164 {
165 	return (-1);
166 }
167 
168 static int
169 viona_neti_isvchksum(net_handle_t neti __unused, mblk_t *mp __unused)
170 {
171 	return (-1);
172 }
173 
174 static net_protocol_t viona_netinfo = {
175 	NETINFO_VERSION,
176 	NHF_VIONA,
177 	viona_neti_getifname,
178 	viona_neti_getmtu,
179 	viona_neti_getptmue,
180 	viona_neti_getlifaddr,
181 	viona_neti_getlifzone,
182 	viona_neti_getlifflags,
183 	viona_neti_phygetnext,
184 	viona_neti_phylookup,
185 	viona_neti_lifgetnext,
186 	viona_neti_inject,
187 	viona_neti_route,
188 	viona_neti_ispchksum,
189 	viona_neti_isvchksum
190 };
191 
192 /*
193  * Create/register our nethooks
194  */
195 static int
196 viona_nethook_init(netid_t nid, viona_nethook_t *vnh, char *nh_name,
197     net_protocol_t *netip)
198 {
199 	int ret;
200 
201 	if ((vnh->vnh_neti = net_protocol_register(nid, netip)) == NULL) {
202 		cmn_err(CE_NOTE, "%s: net_protocol_register failed "
203 		    "(netid=%d name=%s)", __func__, nid, nh_name);
204 		goto fail_init_proto;
205 	}
206 
207 	HOOK_FAMILY_INIT(&vnh->vnh_family, nh_name);
208 	if ((ret = net_family_register(vnh->vnh_neti, &vnh->vnh_family)) != 0) {
209 		cmn_err(CE_NOTE, "%s: net_family_register failed "
210 		    "(netid=%d name=%s err=%d)", __func__,
211 		    nid, nh_name, ret);
212 		goto fail_init_family;
213 	}
214 
215 	HOOK_EVENT_INIT(&vnh->vnh_event_in, NH_PHYSICAL_IN);
216 	if ((vnh->vnh_token_in = net_event_register(vnh->vnh_neti,
217 	    &vnh->vnh_event_in)) == NULL) {
218 		cmn_err(CE_NOTE, "%s: net_event_register %s failed "
219 		    "(netid=%d name=%s)", __func__, NH_PHYSICAL_IN, nid,
220 		    nh_name);
221 		goto fail_init_event_in;
222 	}
223 
224 	HOOK_EVENT_INIT(&vnh->vnh_event_out, NH_PHYSICAL_OUT);
225 	if ((vnh->vnh_token_out = net_event_register(vnh->vnh_neti,
226 	    &vnh->vnh_event_out)) == NULL) {
227 		cmn_err(CE_NOTE, "%s: net_event_register %s failed "
228 		    "(netid=%d name=%s)", __func__, NH_PHYSICAL_OUT, nid,
229 		    nh_name);
230 		goto fail_init_event_out;
231 	}
232 	return (0);
233 
234 	/*
235 	 * On failure, we undo all the steps that succeeded in the
236 	 * reverse order of initialization, starting at the last
237 	 * successful step (the labels denoting the failing step).
238 	 */
239 fail_init_event_out:
240 	VERIFY0(net_event_shutdown(vnh->vnh_neti, &vnh->vnh_event_in));
241 	VERIFY0(net_event_unregister(vnh->vnh_neti, &vnh->vnh_event_in));
242 	vnh->vnh_token_in = NULL;
243 
244 fail_init_event_in:
245 	VERIFY0(net_family_shutdown(vnh->vnh_neti, &vnh->vnh_family));
246 	VERIFY0(net_family_unregister(vnh->vnh_neti, &vnh->vnh_family));
247 
248 fail_init_family:
249 	VERIFY0(net_protocol_unregister(vnh->vnh_neti));
250 	vnh->vnh_neti = NULL;
251 
252 fail_init_proto:
253 	return (1);
254 }
255 
256 /*
257  * Shutdown the nethooks for a protocol family.  This triggers notification
258  * callbacks to anything that has registered interest to allow hook consumers
259  * to unhook prior to the removal of the hooks as well as makes them unavailable
260  * to any future consumers as the first step of removal.
261  */
262 static void
263 viona_nethook_shutdown(viona_nethook_t *vnh)
264 {
265 	VERIFY0(net_event_shutdown(vnh->vnh_neti, &vnh->vnh_event_out));
266 	VERIFY0(net_event_shutdown(vnh->vnh_neti, &vnh->vnh_event_in));
267 	VERIFY0(net_family_shutdown(vnh->vnh_neti, &vnh->vnh_family));
268 }
269 
270 /*
271  * Remove the nethooks for a protocol family.
272  */
273 static void
274 viona_nethook_fini(viona_nethook_t *vnh)
275 {
276 	VERIFY0(net_event_unregister(vnh->vnh_neti, &vnh->vnh_event_out));
277 	VERIFY0(net_event_unregister(vnh->vnh_neti, &vnh->vnh_event_in));
278 	VERIFY0(net_family_unregister(vnh->vnh_neti, &vnh->vnh_family));
279 	VERIFY0(net_protocol_unregister(vnh->vnh_neti));
280 	vnh->vnh_neti = NULL;
281 }
282 
283 /*
284  * Callback invoked by the neti module.  This creates/registers our hooks
285  * {IPv4,IPv6}{in,out} with the nethook framework so they are available to
286  * interested consumers (e.g. ipf).
287  *
288  * During attach, viona_neti_create is called once for every netstack
289  * present on the system at the time of attach.  Thereafter, it is called
290  * during the creation of additional netstack instances (i.e. zone boot).  As a
291  * result, the viona_neti_t that is created during this call always occurs
292  * prior to any viona instances that will use it to send hook events.
293  *
294  * It should never return NULL.  If we cannot register our hooks, we do not
295  * set vnh_hooked of the respective protocol family, which will prevent the
296  * creation of any viona instances on this netstack (see viona_ioc_create).
297  * This can only occur if after a shutdown event (which means destruction is
298  * imminent) we are trying to create a new instance.
299  */
300 static void *
301 viona_neti_create(const netid_t netid)
302 {
303 	viona_neti_t *nip;
304 
305 	VERIFY(netid != -1);
306 
307 	nip = kmem_zalloc(sizeof (*nip), KM_SLEEP);
308 	nip->vni_netid = netid;
309 	nip->vni_zid = net_getzoneidbynetid(netid);
310 	mutex_init(&nip->vni_lock, NULL, MUTEX_DRIVER, NULL);
311 	list_create(&nip->vni_dev_list, sizeof (viona_soft_state_t),
312 	    offsetof(viona_soft_state_t, ss_node));
313 
314 	if (viona_nethook_init(netid, &nip->vni_nethook, Hn_VIONA,
315 	    &viona_netinfo) == 0)
316 		nip->vni_nethook.vnh_hooked = B_TRUE;
317 
318 	mutex_enter(&viona_neti_lock);
319 	list_insert_tail(&viona_neti_list, nip);
320 	mutex_exit(&viona_neti_lock);
321 
322 	return (nip);
323 }
324 
325 /*
326  * Called during netstack teardown by the neti module.  During teardown, all
327  * the shutdown callbacks are invoked, allowing consumers to release any holds
328  * and otherwise quiesce themselves prior to destruction, followed by the
329  * actual destruction callbacks.
330  */
331 static void
332 viona_neti_shutdown(netid_t nid, void *arg)
333 {
334 	viona_neti_t *nip = arg;
335 
336 	ASSERT(nip != NULL);
337 	VERIFY(nid == nip->vni_netid);
338 
339 	mutex_enter(&viona_neti_lock);
340 	list_remove(&viona_neti_list, nip);
341 	mutex_exit(&viona_neti_lock);
342 
343 	if (nip->vni_nethook.vnh_hooked)
344 		viona_nethook_shutdown(&nip->vni_nethook);
345 }
346 
347 /*
348  * Called during netstack teardown by the neti module.  Destroys the viona
349  * netinst data.  This is invoked after all the netstack and neti shutdown
350  * callbacks have been invoked.
351  */
352 static void
353 viona_neti_destroy(netid_t nid, void *arg)
354 {
355 	viona_neti_t *nip = arg;
356 
357 	ASSERT(nip != NULL);
358 	VERIFY(nid == nip->vni_netid);
359 
360 	mutex_enter(&nip->vni_lock);
361 	while (nip->vni_ref != 0)
362 		cv_wait(&nip->vni_ref_change, &nip->vni_lock);
363 	mutex_exit(&nip->vni_lock);
364 
365 	VERIFY(!list_link_active(&nip->vni_node));
366 
367 	if (nip->vni_nethook.vnh_hooked)
368 		viona_nethook_fini(&nip->vni_nethook);
369 
370 	mutex_destroy(&nip->vni_lock);
371 	list_destroy(&nip->vni_dev_list);
372 	kmem_free(nip, sizeof (*nip));
373 }
374 
375 /*
376  * Find the viona netinst data by zone id.  This is only used during
377  * viona instance creation (and thus is only called by a zone that is running).
378  */
379 viona_neti_t *
380 viona_neti_lookup_by_zid(zoneid_t zid)
381 {
382 	viona_neti_t *nip;
383 
384 	mutex_enter(&viona_neti_lock);
385 	for (nip = list_head(&viona_neti_list); nip != NULL;
386 	    nip = list_next(&viona_neti_list, nip)) {
387 		if (nip->vni_zid == zid) {
388 			mutex_enter(&nip->vni_lock);
389 			nip->vni_ref++;
390 			mutex_exit(&nip->vni_lock);
391 			mutex_exit(&viona_neti_lock);
392 			return (nip);
393 		}
394 	}
395 	mutex_exit(&viona_neti_lock);
396 	return (NULL);
397 }
398 
399 void
400 viona_neti_rele(viona_neti_t *nip)
401 {
402 	mutex_enter(&nip->vni_lock);
403 	VERIFY3S(nip->vni_ref, >, 0);
404 	nip->vni_ref--;
405 	mutex_exit(&nip->vni_lock);
406 	cv_broadcast(&nip->vni_ref_change);
407 }
408 
409 void
410 viona_neti_attach(void)
411 {
412 	mutex_init(&viona_neti_lock, NULL, MUTEX_DRIVER, NULL);
413 	list_create(&viona_neti_list, sizeof (viona_neti_t),
414 	    offsetof(viona_neti_t, vni_node));
415 
416 	/* This can only fail if NETINFO_VERSION is wrong */
417 	viona_neti = net_instance_alloc(NETINFO_VERSION);
418 	VERIFY(viona_neti != NULL);
419 
420 	viona_neti->nin_name = "viona";
421 	viona_neti->nin_create = viona_neti_create;
422 	viona_neti->nin_shutdown = viona_neti_shutdown;
423 	viona_neti->nin_destroy = viona_neti_destroy;
424 	/* This can only fail if we've registered ourselves multiple times */
425 	VERIFY3S(net_instance_register(viona_neti), ==, DDI_SUCCESS);
426 }
427 
428 void
429 viona_neti_detach(void)
430 {
431 	/* This can only fail if we've not registered previously */
432 	VERIFY3S(net_instance_unregister(viona_neti), ==, DDI_SUCCESS);
433 	net_instance_free(viona_neti);
434 	viona_neti = NULL;
435 
436 	list_destroy(&viona_neti_list);
437 	mutex_destroy(&viona_neti_lock);
438 }
439