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