chiark / gitweb /
Import release 0.1.2
[secnet.git] / 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     if (!client) {
278         /* Origin of packet is host or secnet. Might be for a tunnel. */
279         best_quality=0;
280         best_match=-1;
281         for (i=0; i<st->n_routes; i++) {
282             if (st->routes[i].up && subnet_match(&st->routes[i].net,dest)) {
283                 if (st->routes[i].c->link_quality>best_quality
284                     || best_quality==0) {
285                     best_quality=st->routes[i].c->link_quality;
286                     best_match=i;
287                     /* If quality isn't perfect we may wish to
288                        consider kicking the tunnel with a 0-length
289                        packet to prompt it to perform a key setup.
290                        Then it'll eventually decide it's up or
291                        down. */
292                     /* If quality is perfect we don't need to search
293                        any more. */
294                     if (best_quality>=MAXIMUM_LINK_QUALITY) break;
295                 }
296             }
297         }
298         if (best_match==-1) {
299             /* Not going down a tunnel. Might be for the host. 
300                XXX think about this - only situation should be if we're
301                sending ICMP. */
302             if (source!=st->secnet_address) {
303                 Message(M_ERROR,"netlink_packet_deliver: outgoing packet "
304                         "from host that won't fit down any of our tunnels!\n");
305                 /* XXX I think this could also occur if a soft tunnel just
306                    went down, but still had packets queued in the kernel. */
307                 BUF_FREE(buf);
308             } else {
309                 st->deliver_to_host(st->dst,NULL,buf);
310                 BUF_ASSERT_FREE(buf);
311             }
312         } else {
313             if (best_quality>0) {
314                 st->routes[best_match].c->deliver(
315                     st->routes[best_match].c->dst,
316                     st->routes[best_match].c, buf);
317                 BUF_ASSERT_FREE(buf);
318             } else {
319                 /* Generate ICMP destination unreachable */
320                 netlink_icmp_simple(st,buf,client,3,0); /* client==NULL */
321                 BUF_FREE(buf);
322             }
323         }
324     } else { /* client is set */
325         /* We know the origin is a tunnel - packet must be for the host */
326         if (subnet_matches_list(&st->networks,dest)) {
327             st->deliver_to_host(st->dst,NULL,buf);
328             BUF_ASSERT_FREE(buf);
329         } else {
330             Message(M_ERROR,"%s: packet from tunnel %s can't be delivered "
331                     "to the host\n",st->name,client->name);
332             netlink_icmp_simple(st,buf,client,3,0);
333             BUF_FREE(buf);
334         }
335     }
336     BUF_ASSERT_FREE(buf);
337 }
338
339 static void netlink_packet_forward(struct netlink *st, 
340                                    struct netlink_client *client,
341                                    struct buffer_if *buf)
342 {
343     struct iphdr *iph=(struct iphdr *)buf->start;
344     
345     BUF_ASSERT_USED(buf);
346
347     /* Packet has already been checked */
348     if (iph->ttl<=1) {
349         /* Generate ICMP time exceeded */
350         netlink_icmp_simple(st,buf,client,11,0);
351         BUF_FREE(buf);
352         return;
353     }
354     iph->ttl--;
355     iph->check=0;
356     iph->check=ip_fast_csum((uint8_t *)iph,iph->ihl);
357
358     netlink_packet_deliver(st,client,buf);
359     BUF_ASSERT_FREE(buf);
360 }
361
362 /* Deal with packets addressed explicitly to us */
363 static void netlink_packet_local(struct netlink *st,
364                                  struct netlink_client *client,
365                                  struct buffer_if *buf)
366 {
367     struct icmphdr *h;
368
369     h=(struct icmphdr *)buf->start;
370
371     if ((ntohs(h->iph.frag_off)&0xbfff)!=0) {
372         Message(M_WARNING,"%s: fragmented packet addressed to secnet; "
373                 "ignoring it\n",st->name);
374         BUF_FREE(buf);
375         return;
376     }
377
378     if (h->iph.protocol==1) {
379         /* It's ICMP */
380         if (h->type==8 && h->code==0) {
381             /* ICMP echo-request. Special case: we re-use the buffer
382                to construct the reply. */
383             h->type=0;
384             h->iph.daddr=h->iph.saddr;
385             h->iph.saddr=htonl(st->secnet_address);
386             h->iph.ttl=255; /* Be nice and bump it up again... */
387             h->iph.check=0;
388             h->iph.check=ip_fast_csum((uint8_t *)h,h->iph.ihl);
389             netlink_icmp_csum(h);
390             netlink_packet_deliver(st,NULL,buf);
391             return;
392         }
393         Message(M_WARNING,"%s: unknown incoming ICMP\n",st->name);
394     } else {
395         /* Send ICMP protocol unreachable */
396         netlink_icmp_simple(st,buf,client,3,2);
397         BUF_FREE(buf);
398         return;
399     }
400
401     BUF_FREE(buf);
402 }
403
404 /* If cid==NULL packet is from host, otherwise cid specifies which tunnel 
405    it came from. */
406 static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
407 {
408     struct netlink *st=sst;
409     struct netlink_client *client=cid;
410     uint32_t source,dest;
411     struct iphdr *iph;
412
413     BUF_ASSERT_USED(buf);
414     if (!netlink_check(st,buf)) {
415         Message(M_WARNING,"%s: bad IP packet from %s\n",
416                 st->name,client?client->name:"host");
417         BUF_FREE(buf);
418         return;
419     }
420     iph=(struct iphdr *)buf->start;
421
422     source=ntohl(iph->saddr);
423     dest=ntohl(iph->daddr);
424
425     /* Check source */
426     if (client) {
427         /* Check that the packet source is in 'nets' and its destination is
428            in st->networks */
429         if (!subnet_matches_list(client->networks,source)) {
430             string_t s,d;
431             s=ipaddr_to_string(source);
432             d=ipaddr_to_string(dest);
433             Message(M_WARNING,"%s: packet from tunnel %s with bad "
434                     "source address (s=%s,d=%s)\n",st->name,client->name,s,d);
435             free(s); free(d);
436             BUF_FREE(buf);
437             return;
438         }
439     } else {
440         if (!subnet_matches_list(&st->networks,source)) {
441             string_t s,d;
442             s=ipaddr_to_string(source);
443             d=ipaddr_to_string(dest);
444             Message(M_WARNING,"%s: outgoing packet with bad source address "
445                     "(s=%s,d=%s)\n",st->name,s,d);
446             free(s); free(d);
447             BUF_FREE(buf);
448             return;
449         }
450     }
451     /* (st->secnet_address needs checking before matching destination
452        addresses) */
453     if (dest==st->secnet_address) {
454         netlink_packet_local(st,client,buf);
455         BUF_ASSERT_FREE(buf);
456         return;
457     }
458     if (client) {
459         if (!subnet_matches_list(&st->networks,dest)) {
460             string_t s,d;
461             s=ipaddr_to_string(source);
462             d=ipaddr_to_string(dest);
463             Message(M_WARNING,"%s: incoming packet from tunnel %s "
464                     "with bad destination address "
465                     "(s=%s,d=%s)\n",st->name,client->name,s,d);
466             free(s); free(d);
467             BUF_FREE(buf);
468             return;
469         }
470     }
471     netlink_packet_forward(st,client,buf);
472     BUF_ASSERT_FREE(buf);
473 }
474
475 static void netlink_set_softlinks(struct netlink *st, struct netlink_client *c,
476                                   bool_t up)
477 {
478     uint32_t i;
479
480     if (!st->routes) return; /* Table has not yet been created */
481     for (i=0; i<st->n_routes; i++) {
482         if (!st->routes[i].hard && st->routes[i].c==c) {
483             st->routes[i].up=up;
484             st->set_route(st->dst,&st->routes[i]);
485         }
486     }
487 }
488
489 static void netlink_set_quality(void *sst, void *cid, uint32_t quality)
490 {
491     struct netlink *st=sst;
492     struct netlink_client *c=cid;
493
494     c->link_quality=quality;
495     if (c->link_quality==LINK_QUALITY_DOWN) {
496         netlink_set_softlinks(st,c,False);
497     } else {
498         netlink_set_softlinks(st,c,True);
499     }
500 }
501
502 static void *netlink_regnets(void *sst, struct subnet_list *nets,
503                              netlink_deliver_fn *deliver, void *dst,
504                              uint32_t max_start_pad, uint32_t max_end_pad,
505                              bool_t hard_routes, string_t client_name)
506 {
507     struct netlink *st=sst;
508     struct netlink_client *c;
509
510     Message(M_DEBUG_CONFIG,"netlink_regnets: request for %d networks, "
511             "max_start_pad=%d, max_end_pad=%d\n",
512             nets->entries,max_start_pad,max_end_pad);
513
514     if (!hard_routes && !st->set_route) {
515         Message(M_ERROR,"%s: this netlink device does not support "
516                 "soft routes.\n");
517         return NULL;
518     }
519
520     if (!hard_routes) {
521         /* XXX for now we assume that soft routes require root privilege;
522            this may not always be true. */
523         require_root_privileges=True;
524         require_root_privileges_explanation="netlink: soft routes";
525     }
526
527 #if 0
528     /* XXX POLICY: do we check nets against local networks?  If we do,
529        that prevents things like laptop tunnels working.  Perhaps we
530        can have a configuration option for this.  Or, if the admin
531        really doesn't want remote sites to be able to claim local
532        addresses, he can list them in exclude-remote-networks. */
533     if (subnet_lists_intersect(&st->networks,nets)) {
534         Message(M_ERROR,"%s: site %s specifies networks that "
535                 "intersect with our local networks\n",st->name,client_name);
536         return False;
537     }
538 #endif /* 0 */
539     /* Check that nets do not intersect st->exclude_remote_networks;
540        refuse to register if they do. */
541     if (subnet_lists_intersect(&st->exclude_remote_networks,nets)) {
542         Message(M_ERROR,"%s: site %s specifies networks that "
543                 "intersect with the explicitly excluded remote networks\n",
544                 st->name,client_name);
545         return False;
546     }
547
548     c=safe_malloc(sizeof(*c),"netlink_regnets");
549     c->networks=nets;
550     c->deliver=deliver;
551     c->dst=dst;
552     c->name=client_name; /* XXX copy it? */
553     c->hard_routes=hard_routes;
554     c->link_quality=LINK_QUALITY_DOWN;
555     c->next=st->clients;
556     st->clients=c;
557     if (max_start_pad > st->max_start_pad) st->max_start_pad=max_start_pad;
558     if (max_end_pad > st->max_end_pad) st->max_end_pad=max_end_pad;
559     st->n_routes+=nets->entries;
560
561     return c;
562 }
563
564 static void netlink_dump_routes(struct netlink *st)
565 {
566     int i;
567     string_t net;
568
569     Message(M_INFO,"%s: routing table:\n",st->name);
570     for (i=0; i<st->n_routes; i++) {
571         net=subnet_to_string(&st->routes[i].net);
572         Message(M_INFO,"%s -> tunnel %s (%s,%s)\n",net,st->routes[i].c->name,
573                 st->routes[i].hard?"hard":"soft",
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             st->routes[i].up=c->hard_routes; /* Hard routes are always up;
614                                                 soft routes default to down */
615             st->routes[i].kup=False;
616             st->routes[i].hard=c->hard_routes;
617             i++;
618         }
619     }
620     /* ASSERT i==st->n_routes */
621     if (i!=st->n_routes) {
622         fatal("netlink: route count error: expected %d got %d\n",
623               st->n_routes,i);
624     }
625     /* Sort the table in descending order of specificity */
626     qsort(st->routes,st->n_routes,sizeof(*st->routes),
627           netlink_compare_route_specificity);
628
629     netlink_dump_routes(st);
630 }
631
632 netlink_deliver_fn *netlink_init(struct netlink *st,
633                                  void *dst, struct cloc loc,
634                                  dict_t *dict, string_t description,
635                                  netlink_route_fn *set_route,
636                                  netlink_deliver_fn *to_host)
637 {
638     st->dst=dst;
639     st->cl.description=description;
640     st->cl.type=CL_NETLINK;
641     st->cl.apply=NULL;
642     st->cl.interface=&st->ops;
643     st->ops.st=st;
644     st->ops.regnets=netlink_regnets;
645     st->ops.deliver=netlink_incoming;
646     st->ops.set_quality=netlink_set_quality;
647     st->max_start_pad=0;
648     st->max_end_pad=0;
649     st->clients=NULL;
650     st->set_route=set_route;
651     st->deliver_to_host=to_host;
652
653     st->name=dict_read_string(dict,"name",False,"netlink",loc);
654     if (!st->name) st->name=description;
655     dict_read_subnet_list(dict, "networks", True, "netlink", loc,
656                           &st->networks);
657     dict_read_subnet_list(dict, "exclude-remote-networks", False, "netlink",
658                           loc, &st->exclude_remote_networks);
659     /* local-address and secnet-address do not have to be in local-networks;
660        however, they should be advertised in the 'sites' file for the
661        local site. */
662     st->local_address=string_to_ipaddr(
663         dict_find_item(dict,"local-address", True, "netlink", loc),"netlink");
664     st->secnet_address=string_to_ipaddr(
665         dict_find_item(dict,"secnet-address", True, "netlink", loc),"netlink");
666     st->mtu=dict_read_number(dict, "mtu", False, "netlink", loc, DEFAULT_MTU);
667     buffer_new(&st->icmp,ICMP_BUFSIZE);
668     st->n_routes=0;
669     st->routes=NULL;
670
671     add_hook(PHASE_SETUP,netlink_phase_hook,st);
672
673     return netlink_incoming;
674 }
675
676 /* No connection to the kernel at all... */
677
678 struct null {
679     struct netlink nl;
680 };
681
682 static bool_t null_set_route(void *sst, struct netlink_route *route)
683 {
684     struct null *st=sst;
685     string_t t;
686
687     if (route->up!=route->kup) {
688         t=subnet_to_string(&route->net);
689         Message(M_INFO,"%s: setting route %s to state %s\n",st->nl.name,
690                 t, route->up?"up":"down");
691         free(t);
692         route->kup=route->up;
693         return True;
694     }
695     return False;
696 }
697             
698 static void null_deliver(void *sst, void *cid, struct buffer_if *buf)
699 {
700     return;
701 }
702
703 static list_t *null_apply(closure_t *self, struct cloc loc, dict_t *context,
704                           list_t *args)
705 {
706     struct null *st;
707     item_t *item;
708     dict_t *dict;
709
710     st=safe_malloc(sizeof(*st),"null_apply");
711
712     item=list_elem(args,0);
713     if (!item || item->type!=t_dict)
714         cfgfatal(loc,"null-netlink","parameter must be a dictionary\n");
715     
716     dict=item->data.dict;
717
718     netlink_init(&st->nl,st,loc,dict,"null-netlink",null_set_route,
719                  null_deliver);
720
721     return new_closure(&st->nl.cl);
722 }
723
724 init_module netlink_module;
725 void netlink_module(dict_t *dict)
726 {
727     add_closure(dict,"null-netlink",null_apply);
728 }