chiark / gitweb /
e09eacf95c4466015bf393eaf1fa75cdcd634073
[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 "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 }