Import release 0.1.6
[secnet] / netlink.c
1 /* User-kernel network link */
2
3 /* Each netlink device is actually a router, with its own IP address.
4 We do things like decreasing the TTL and recalculating the header
5 checksum, generating ICMP, responding to pings, etc. */
6
7 /* This is where we have the anti-spoofing paranoia - before sending a
8 packet to the kernel we check that the tunnel it came over could
9 reasonably have produced it. */
10
11 #include "secnet.h"
12 #include "util.h"
13 #include "ipaddr.h"
14 #include "netlink.h"
15
16 /* Generic IP checksum routine */
17 static inline uint16_t ip_csum(uint8_t *iph,uint32_t count)
18 {
19 register uint32_t sum=0;
20
21 while (count>1) {
22 sum+=ntohs(*(uint16_t *)iph);
23 iph+=2;
24 count-=2;
25 }
26 if(count>0)
27 sum+=*(uint8_t *)iph;
28 while (sum>>16)
29 sum=(sum&0xffff)+(sum>>16);
30 return htons(~sum);
31 }
32
33 #ifdef i386
34 /*
35 * This is a version of ip_compute_csum() optimized for IP headers,
36 * which always checksum on 4 octet boundaries.
37 *
38 * By Jorge Cwik <jorge@laser.satlink.net>, adapted for linux by
39 * Arnt Gulbrandsen.
40 */
41 static inline uint16_t ip_fast_csum(uint8_t *iph, uint32_t ihl) {
42 uint32_t sum;
43
44 __asm__ __volatile__("
45 movl (%1), %0
46 subl $4, %2
47 jbe 2f
48 addl 4(%1), %0
49 adcl 8(%1), %0
50 adcl 12(%1), %0
51 1: adcl 16(%1), %0
52 lea 4(%1), %1
53 decl %2
54 jne 1b
55 adcl $0, %0
56 movl %0, %2
57 shrl $16, %0
58 addw %w2, %w0
59 adcl $0, %0
60 notl %0
61 2:
62 "
63 /* Since the input registers which are loaded with iph and ipl
64 are modified, we must also specify them as outputs, or gcc
65 will assume they contain their original values. */
66 : "=r" (sum), "=r" (iph), "=r" (ihl)
67 : "1" (iph), "2" (ihl));
68 return sum;
69 }
70 #else
71 static inline uint16_t ip_fast_csum(uint8_t *iph, uint32_t ihl)
72 {
73 return ip_csum(iph,ihl*4);
74 }
75 #endif
76
77 struct iphdr {
78 #if defined (WORDS_BIGENDIAN)
79 uint8_t version:4,
80 ihl:4;
81 #else
82 uint8_t ihl:4,
83 version:4;
84 #endif
85 uint8_t tos;
86 uint16_t tot_len;
87 uint16_t id;
88 uint16_t frag_off;
89 uint8_t ttl;
90 uint8_t protocol;
91 uint16_t check;
92 uint32_t saddr;
93 uint32_t daddr;
94 /* The options start here. */
95 };
96
97 struct icmphdr {
98 struct iphdr iph;
99 uint8_t type;
100 uint8_t code;
101 uint16_t check;
102 union {
103 uint32_t unused;
104 struct {
105 uint8_t pointer;
106 uint8_t unused1;
107 uint16_t unused2;
108 } pprob;
109 uint32_t gwaddr;
110 struct {
111 uint16_t id;
112 uint16_t seq;
113 } echo;
114 } d;
115 };
116
117 static void netlink_packet_deliver(struct netlink *st,
118 struct netlink_client *client,
119 struct buffer_if *buf);
120
121 static struct icmphdr *netlink_icmp_tmpl(struct netlink *st,
122 uint32_t dest,uint16_t len)
123 {
124 struct icmphdr *h;
125
126 BUF_ALLOC(&st->icmp,"netlink_icmp_tmpl");
127 buffer_init(&st->icmp,st->max_start_pad);
128 h=buf_append(&st->icmp,sizeof(*h));
129
130 h->iph.version=4;
131 h->iph.ihl=5;
132 h->iph.tos=0;
133 h->iph.tot_len=htons(len+(h->iph.ihl*4)+8);
134 h->iph.id=0;
135 h->iph.frag_off=0;
136 h->iph.ttl=255;
137 h->iph.protocol=1;
138 h->iph.saddr=htonl(st->secnet_address);
139 h->iph.daddr=htonl(dest);
140 h->iph.check=0;
141 h->iph.check=ip_fast_csum((uint8_t *)&h->iph,h->iph.ihl);
142 h->check=0;
143 h->d.unused=0;
144
145 return h;
146 }
147
148 /* Fill in the ICMP checksum field correctly */
149 static void netlink_icmp_csum(struct icmphdr *h)
150 {
151 uint32_t len;
152
153 len=ntohs(h->iph.tot_len)-(4*h->iph.ihl);
154 h->check=0;
155 h->check=ip_csum(&h->type,len);
156 }
157
158 /* RFC1122:
159 * An ICMP error message MUST NOT be sent as the result of
160 * receiving:
161 *
162 * * an ICMP error message, or
163 *
164 * * a datagram destined to an IP broadcast or IP multicast
165 * address, or
166 *
167 * * a datagram sent as a link-layer broadcast, or
168 *
169 * * a non-initial fragment, or
170 *
171 * * a datagram whose source address does not define a single
172 * host -- e.g., a zero address, a loopback address, a
173 * broadcast address, a multicast address, or a Class E
174 * address.
175 */
176 static bool_t netlink_icmp_may_reply(struct buffer_if *buf)
177 {
178 struct iphdr *iph;
179 uint32_t source;
180
181 iph=(struct iphdr *)buf->start;
182 if (iph->protocol==1) return False; /* Overly-broad; we may reply to
183 eg. icmp echo-request */
184 /* How do we spot broadcast destination addresses? */
185 if (ntohs(iph->frag_off)&0x1fff) return False; /* Non-initial fragment */
186 source=ntohl(iph->saddr);
187 if (source==0) return False;
188 if ((source&0xff000000)==0x7f000000) return False;
189 /* How do we spot broadcast source addresses? */
190 if ((source&0xf0000000)==0xe0000000) return False; /* Multicast */
191 if ((source&0xf0000000)==0xf0000000) return False; /* Class E */
192 return True;
193 }
194
195 /* How much of the original IP packet do we include in its ICMP
196 response? The header plus up to 64 bits. */
197 static uint16_t netlink_icmp_reply_len(struct buffer_if *buf)
198 {
199 struct iphdr *iph=(struct iphdr *)buf->start;
200 uint16_t hlen,plen;
201
202 hlen=iph->ihl*4;
203 /* We include the first 8 bytes of the packet data, provided they exist */
204 hlen+=8;
205 plen=ntohs(iph->tot_len);
206 return (hlen>plen?plen:hlen);
207 }
208
209 /* client indicates where the packet we're constructing a response to
210 comes from. NULL indicates the host. */
211 static void netlink_icmp_simple(struct netlink *st, struct buffer_if *buf,
212 struct netlink_client *client,
213 uint8_t type, uint8_t code)
214 {
215 struct iphdr *iph=(struct iphdr *)buf->start;
216 struct icmphdr *h;
217 uint16_t len;
218
219 if (netlink_icmp_may_reply(buf)) {
220 len=netlink_icmp_reply_len(buf);
221 h=netlink_icmp_tmpl(st,ntohl(iph->saddr),len);
222 h->type=type; h->code=code;
223 memcpy(buf_append(&st->icmp,len),buf->start,len);
224 netlink_icmp_csum(h);
225 netlink_packet_deliver(st,NULL,&st->icmp);
226 BUF_ASSERT_FREE(&st->icmp);
227 }
228 }
229
230 /*
231 * RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the
232 * checksum.
233 *
234 * Is the datagram acceptable?
235 *
236 * 1. Length at least the size of an ip header
237 * 2. Version of 4
238 * 3. Checksums correctly.
239 * 4. Doesn't have a bogus length
240 */
241 static bool_t netlink_check(struct netlink *st, struct buffer_if *buf)
242 {
243 struct iphdr *iph=(struct iphdr *)buf->start;
244 uint32_t len;
245
246 if (iph->ihl < 5 || iph->version != 4) return False;
247 if (buf->size < iph->ihl*4) return False;
248 if (ip_fast_csum((uint8_t *)iph, iph->ihl)!=0) return False;
249 len=ntohs(iph->tot_len);
250 /* There should be no padding */
251 if (buf->size!=len || len<(iph->ihl<<2)) return False;
252 /* XXX check that there's no source route specified */
253 return True;
254 }
255
256 /* Deliver a packet. "client" points to the _origin_ of the packet, not
257 its destination. (May be used when sending ICMP response - avoid
258 asymmetric routing.) */
259 static void netlink_packet_deliver(struct netlink *st,
260 struct netlink_client *client,
261 struct buffer_if *buf)
262 {
263 struct iphdr *iph=(struct iphdr *)buf->start;
264 uint32_t dest=ntohl(iph->daddr);
265 uint32_t source=ntohl(iph->saddr);
266 uint32_t best_quality;
267 int best_match;
268 int i;
269
270 BUF_ASSERT_USED(buf);
271
272 if (dest==st->secnet_address) {
273 Message(M_ERROR,"%s: trying to deliver a packet to myself!\n");
274 BUF_FREE(buf);
275 return;
276 }
277
278 /* XXX we're going to need an extra value 'allow_route' for the
279 source of the packet. It's always True for packets from the
280 host. For packets from tunnels, we consult the client
281 options. If !allow_route and the destination is a tunnel that
282 also doesn't allow routing, we must reject the packet with an
283 'administratively prohibited' or something similar ICMP. */
284 if (!client) {
285 /* Origin of packet is host or secnet. Might be for a tunnel. */
286 best_quality=0;
287 best_match=-1;
288 for (i=0; i<st->n_routes; i++) {
289 if (st->routes[i].up && subnet_match(&st->routes[i].net,dest)) {
290 if (st->routes[i].c->link_quality>best_quality
291 || best_quality==0) {
292 best_quality=st->routes[i].c->link_quality;
293 best_match=i;
294 /* If quality isn't perfect we may wish to
295 consider kicking the tunnel with a 0-length
296 packet to prompt it to perform a key setup.
297 Then it'll eventually decide it's up or
298 down. */
299 /* If quality is perfect we don't need to search
300 any more. */
301 if (best_quality>=MAXIMUM_LINK_QUALITY) break;
302 }
303 }
304 }
305 if (best_match==-1) {
306 /* Not going down a tunnel. Might be for the host.
307 XXX think about this - only situation should be if we're
308 sending ICMP. */
309 if (source!=st->secnet_address) {
310 Message(M_ERROR,"netlink_packet_deliver: outgoing packet "
311 "from host that won't fit down any of our tunnels!\n");
312 /* XXX I think this could also occur if a soft tunnel just
313 went down, but still had packets queued in the kernel. */
314 BUF_FREE(buf);
315 } else {
316 st->deliver_to_host(st->dst,NULL,buf);
317 BUF_ASSERT_FREE(buf);
318 }
319 } else {
320 if (best_quality>0) {
321 st->routes[best_match].c->deliver(
322 st->routes[best_match].c->dst,
323 st->routes[best_match].c, buf);
324 BUF_ASSERT_FREE(buf);
325 } else {
326 /* Generate ICMP destination unreachable */
327 netlink_icmp_simple(st,buf,client,3,0); /* client==NULL */
328 BUF_FREE(buf);
329 }
330 }
331 } else { /* client is set */
332 /* We know the origin is a tunnel - packet must be for the host */
333 /* XXX THIS IS NOT NECESSARILY TRUE, AND NEEDS FIXING */
334 /* THIS FUNCTION MUST JUST DELIVER THE PACKET: IT MUST ASSUME
335 THE PACKET HAS ALREADY BEEN CHECKED */
336 if (subnet_matches_list(&st->networks,dest)) {
337 st->deliver_to_host(st->dst,NULL,buf);
338 BUF_ASSERT_FREE(buf);
339 } else {
340 Message(M_ERROR,"%s: packet from tunnel %s can't be delivered "
341 "to the host\n",st->name,client->name);
342 netlink_icmp_simple(st,buf,client,3,0);
343 BUF_FREE(buf);
344 }
345 }
346 BUF_ASSERT_FREE(buf);
347 }
348
349 static void netlink_packet_forward(struct netlink *st,
350 struct netlink_client *client,
351 struct buffer_if *buf)
352 {
353 struct iphdr *iph=(struct iphdr *)buf->start;
354
355 BUF_ASSERT_USED(buf);
356
357 /* Packet has already been checked */
358 if (iph->ttl<=1) {
359 /* Generate ICMP time exceeded */
360 netlink_icmp_simple(st,buf,client,11,0);
361 BUF_FREE(buf);
362 return;
363 }
364 iph->ttl--;
365 iph->check=0;
366 iph->check=ip_fast_csum((uint8_t *)iph,iph->ihl);
367
368 netlink_packet_deliver(st,client,buf);
369 BUF_ASSERT_FREE(buf);
370 }
371
372 /* Deal with packets addressed explicitly to us */
373 static void netlink_packet_local(struct netlink *st,
374 struct netlink_client *client,
375 struct buffer_if *buf)
376 {
377 struct icmphdr *h;
378
379 h=(struct icmphdr *)buf->start;
380
381 if ((ntohs(h->iph.frag_off)&0xbfff)!=0) {
382 Message(M_WARNING,"%s: fragmented packet addressed to secnet; "
383 "ignoring it\n",st->name);
384 BUF_FREE(buf);
385 return;
386 }
387
388 if (h->iph.protocol==1) {
389 /* It's ICMP */
390 if (h->type==8 && h->code==0) {
391 /* ICMP echo-request. Special case: we re-use the buffer
392 to construct the reply. */
393 h->type=0;
394 h->iph.daddr=h->iph.saddr;
395 h->iph.saddr=htonl(st->secnet_address);
396 h->iph.ttl=255; /* Be nice and bump it up again... */
397 h->iph.check=0;
398 h->iph.check=ip_fast_csum((uint8_t *)h,h->iph.ihl);
399 netlink_icmp_csum(h);
400 netlink_packet_deliver(st,NULL,buf);
401 return;
402 }
403 Message(M_WARNING,"%s: unknown incoming ICMP\n",st->name);
404 } else {
405 /* Send ICMP protocol unreachable */
406 netlink_icmp_simple(st,buf,client,3,2);
407 BUF_FREE(buf);
408 return;
409 }
410
411 BUF_FREE(buf);
412 }
413
414 /* If cid==NULL packet is from host, otherwise cid specifies which tunnel
415 it came from. */
416 static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
417 {
418 struct netlink *st=sst;
419 struct netlink_client *client=cid;
420 uint32_t source,dest;
421 struct iphdr *iph;
422
423 BUF_ASSERT_USED(buf);
424 if (!netlink_check(st,buf)) {
425 Message(M_WARNING,"%s: bad IP packet from %s\n",
426 st->name,client?client->name:"host");
427 BUF_FREE(buf);
428 return;
429 }
430 iph=(struct iphdr *)buf->start;
431
432 source=ntohl(iph->saddr);
433 dest=ntohl(iph->daddr);
434
435 /* Check source */
436 if (client) {
437 /* Check that the packet source is in 'nets' and its destination is
438 in st->networks */
439 if (!subnet_matches_list(client->networks,source)) {
440 string_t s,d;
441 s=ipaddr_to_string(source);
442 d=ipaddr_to_string(dest);
443 Message(M_WARNING,"%s: packet from tunnel %s with bad "
444 "source address (s=%s,d=%s)\n",st->name,client->name,s,d);
445 free(s); free(d);
446 BUF_FREE(buf);
447 return;
448 }
449 } else {
450 if (!subnet_matches_list(&st->networks,source)) {
451 string_t s,d;
452 s=ipaddr_to_string(source);
453 d=ipaddr_to_string(dest);
454 Message(M_WARNING,"%s: outgoing packet with bad source address "
455 "(s=%s,d=%s)\n",st->name,s,d);
456 free(s); free(d);
457 BUF_FREE(buf);
458 return;
459 }
460 }
461 /* (st->secnet_address needs checking before matching destination
462 addresses) */
463 if (dest==st->secnet_address) {
464 netlink_packet_local(st,client,buf);
465 BUF_ASSERT_FREE(buf);
466 return;
467 }
468 if (client) {
469 /* Check for free routing */
470 if (!subnet_matches_list(&st->networks,dest)) {
471 string_t s,d;
472 s=ipaddr_to_string(source);
473 d=ipaddr_to_string(dest);
474 Message(M_WARNING,"%s: incoming packet from tunnel %s "
475 "with bad destination address "
476 "(s=%s,d=%s)\n",st->name,client->name,s,d);
477 free(s); free(d);
478 BUF_FREE(buf);
479 return;
480 }
481 }
482 netlink_packet_forward(st,client,buf);
483 BUF_ASSERT_FREE(buf);
484 }
485
486 static void netlink_set_softlinks(struct netlink *st, struct netlink_client *c,
487 bool_t up)
488 {
489 uint32_t i;
490
491 if (!st->routes) return; /* Table has not yet been created */
492 for (i=0; i<st->n_routes; i++) {
493 if (!st->routes[i].hard && st->routes[i].c==c) {
494 st->routes[i].up=up;
495 st->set_route(st->dst,&st->routes[i]);
496 }
497 }
498 }
499
500 static void netlink_set_quality(void *sst, void *cid, uint32_t quality)
501 {
502 struct netlink *st=sst;
503 struct netlink_client *c=cid;
504
505 c->link_quality=quality;
506 if (c->link_quality==LINK_QUALITY_DOWN) {
507 netlink_set_softlinks(st,c,False);
508 } else {
509 netlink_set_softlinks(st,c,True);
510 }
511 }
512
513 static void *netlink_regnets(void *sst, struct subnet_list *nets,
514 netlink_deliver_fn *deliver, void *dst,
515 uint32_t max_start_pad, uint32_t max_end_pad,
516 uint32_t options, string_t client_name)
517 {
518 struct netlink *st=sst;
519 struct netlink_client *c;
520
521 Message(M_DEBUG_CONFIG,"netlink_regnets: request for %d networks, "
522 "max_start_pad=%d, max_end_pad=%d\n",
523 nets->entries,max_start_pad,max_end_pad);
524
525 if ((options&NETLINK_OPTION_SOFTROUTE) && !st->set_route) {
526 Message(M_ERROR,"%s: this netlink device does not support "
527 "soft routes.\n");
528 return NULL;
529 }
530
531 if (options&NETLINK_OPTION_SOFTROUTE) {
532 /* XXX for now we assume that soft routes require root privilege;
533 this may not always be true. The device driver can tell us. */
534 require_root_privileges=True;
535 require_root_privileges_explanation="netlink: soft routes";
536 }
537
538 /* Check that nets do not intersect st->exclude_remote_networks;
539 refuse to register if they do. */
540 if (subnet_lists_intersect(&st->exclude_remote_networks,nets)) {
541 Message(M_ERROR,"%s: site %s specifies networks that "
542 "intersect with the explicitly excluded remote networks\n",
543 st->name,client_name);
544 return False;
545 }
546
547 c=safe_malloc(sizeof(*c),"netlink_regnets");
548 c->networks=nets;
549 c->deliver=deliver;
550 c->dst=dst;
551 c->name=client_name; /* XXX copy it? */
552 c->options=options;
553 c->link_quality=LINK_QUALITY_DOWN;
554 c->next=st->clients;
555 st->clients=c;
556 if (max_start_pad > st->max_start_pad) st->max_start_pad=max_start_pad;
557 if (max_end_pad > st->max_end_pad) st->max_end_pad=max_end_pad;
558 st->n_routes+=nets->entries;
559
560 return c;
561 }
562
563 static void netlink_dump_routes(struct netlink *st)
564 {
565 int i;
566 string_t net;
567
568 Message(M_INFO,"%s: routing table:\n",st->name);
569 for (i=0; i<st->n_routes; i++) {
570 net=subnet_to_string(&st->routes[i].net);
571 Message(M_INFO,"%s -> tunnel %s (%s,%s route,%s)\n",net,
572 st->routes[i].c->name,
573 st->routes[i].hard?"hard":"soft",
574 st->routes[i].allow_route?"free":"restricted",
575 st->routes[i].up?"up":"down");
576 free(net);
577 }
578 Message(M_INFO,"%s/32 -> netlink \"%s\"\n",
579 ipaddr_to_string(st->secnet_address),st->name);
580 for (i=0; i<st->networks.entries; i++) {
581 net=subnet_to_string(&st->networks.list[i]);
582 Message(M_INFO,"%s -> host\n",net);
583 free(net);
584 }
585 }
586
587 static int netlink_compare_route_specificity(const void *ap, const void *bp)
588 {
589 const struct netlink_route *a=ap;
590 const struct netlink_route *b=bp;
591
592 if (a->net.len==b->net.len) return 0;
593 if (a->net.len<b->net.len) return 1;
594 return -1;
595 }
596
597 static void netlink_phase_hook(void *sst, uint32_t new_phase)
598 {
599 struct netlink *st=sst;
600 struct netlink_client *c;
601 uint32_t i,j;
602
603 /* All the networks serviced by the various tunnels should now
604 * have been registered. We build a routing table by sorting the
605 * routes into most-specific-first order. */
606 st->routes=safe_malloc(st->n_routes*sizeof(*st->routes),
607 "netlink_phase_hook");
608 /* Fill the table */
609 i=0;
610 for (c=st->clients; c; c=c->next) {
611 for (j=0; j<c->networks->entries; j++) {
612 st->routes[i].net=c->networks->list[j];
613 st->routes[i].c=c;
614 /* Hard routes are always up;
615 soft routes default to down */
616 st->routes[i].up=c->options&NETLINK_OPTION_SOFTROUTE?False:True;
617 st->routes[i].kup=False;
618 st->routes[i].hard=c->options&NETLINK_OPTION_SOFTROUTE?False:True;
619 st->routes[i].allow_route=c->options&NETLINK_OPTION_ALLOW_ROUTE?
620 True:False;
621 i++;
622 }
623 }
624 /* ASSERT i==st->n_routes */
625 if (i!=st->n_routes) {
626 fatal("netlink: route count error: expected %d got %d\n",
627 st->n_routes,i);
628 }
629 /* Sort the table in descending order of specificity */
630 qsort(st->routes,st->n_routes,sizeof(*st->routes),
631 netlink_compare_route_specificity);
632
633 netlink_dump_routes(st);
634 }
635
636 netlink_deliver_fn *netlink_init(struct netlink *st,
637 void *dst, struct cloc loc,
638 dict_t *dict, string_t description,
639 netlink_route_fn *set_route,
640 netlink_deliver_fn *to_host)
641 {
642 st->dst=dst;
643 st->cl.description=description;
644 st->cl.type=CL_NETLINK;
645 st->cl.apply=NULL;
646 st->cl.interface=&st->ops;
647 st->ops.st=st;
648 st->ops.regnets=netlink_regnets;
649 st->ops.deliver=netlink_incoming;
650 st->ops.set_quality=netlink_set_quality;
651 st->max_start_pad=0;
652 st->max_end_pad=0;
653 st->clients=NULL;
654 st->set_route=set_route;
655 st->deliver_to_host=to_host;
656
657 st->name=dict_read_string(dict,"name",False,"netlink",loc);
658 if (!st->name) st->name=description;
659 dict_read_subnet_list(dict, "networks", True, "netlink", loc,
660 &st->networks);
661 dict_read_subnet_list(dict, "exclude-remote-networks", False, "netlink",
662 loc, &st->exclude_remote_networks);
663 /* secnet-address does not have to be in local-networks;
664 however, it should be advertised in the 'sites' file for the
665 local site. */
666 st->secnet_address=string_to_ipaddr(
667 dict_find_item(dict,"secnet-address", True, "netlink", loc),"netlink");
668 st->mtu=dict_read_number(dict, "mtu", False, "netlink", loc, DEFAULT_MTU);
669 buffer_new(&st->icmp,ICMP_BUFSIZE);
670 st->n_routes=0;
671 st->routes=NULL;
672
673 add_hook(PHASE_SETUP,netlink_phase_hook,st);
674
675 return netlink_incoming;
676 }
677
678 /* No connection to the kernel at all... */
679
680 struct null {
681 struct netlink nl;
682 };
683
684 static bool_t null_set_route(void *sst, struct netlink_route *route)
685 {
686 struct null *st=sst;
687 string_t t;
688
689 if (route->up!=route->kup) {
690 t=subnet_to_string(&route->net);
691 Message(M_INFO,"%s: setting route %s to state %s\n",st->nl.name,
692 t, route->up?"up":"down");
693 free(t);
694 route->kup=route->up;
695 return True;
696 }
697 return False;
698 }
699
700 static void null_deliver(void *sst, void *cid, struct buffer_if *buf)
701 {
702 return;
703 }
704
705 static list_t *null_apply(closure_t *self, struct cloc loc, dict_t *context,
706 list_t *args)
707 {
708 struct null *st;
709 item_t *item;
710 dict_t *dict;
711
712 st=safe_malloc(sizeof(*st),"null_apply");
713
714 item=list_elem(args,0);
715 if (!item || item->type!=t_dict)
716 cfgfatal(loc,"null-netlink","parameter must be a dictionary\n");
717
718 dict=item->data.dict;
719
720 netlink_init(&st->nl,st,loc,dict,"null-netlink",null_set_route,
721 null_deliver);
722
723 return new_closure(&st->nl.cl);
724 }
725
726 init_module netlink_module;
727 void netlink_module(dict_t *dict)
728 {
729 add_closure(dict,"null-netlink",null_apply);
730 }