| 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 | /* secnet-address does not have to be in local-networks; |
| 663 | however, it should be advertised in the 'sites' file for the |
| 664 | local site. */ |
| 665 | st->secnet_address=string_to_ipaddr( |
| 666 | dict_find_item(dict,"secnet-address", True, "netlink", loc),"netlink"); |
| 667 | st->mtu=dict_read_number(dict, "mtu", False, "netlink", loc, DEFAULT_MTU); |
| 668 | buffer_new(&st->icmp,ICMP_BUFSIZE); |
| 669 | st->n_routes=0; |
| 670 | st->routes=NULL; |
| 671 | |
| 672 | add_hook(PHASE_SETUP,netlink_phase_hook,st); |
| 673 | |
| 674 | return netlink_incoming; |
| 675 | } |
| 676 | |
| 677 | /* No connection to the kernel at all... */ |
| 678 | |
| 679 | struct null { |
| 680 | struct netlink nl; |
| 681 | }; |
| 682 | |
| 683 | static bool_t null_set_route(void *sst, struct netlink_route *route) |
| 684 | { |
| 685 | struct null *st=sst; |
| 686 | string_t t; |
| 687 | |
| 688 | if (route->up!=route->kup) { |
| 689 | t=subnet_to_string(&route->net); |
| 690 | Message(M_INFO,"%s: setting route %s to state %s\n",st->nl.name, |
| 691 | t, route->up?"up":"down"); |
| 692 | free(t); |
| 693 | route->kup=route->up; |
| 694 | return True; |
| 695 | } |
| 696 | return False; |
| 697 | } |
| 698 | |
| 699 | static void null_deliver(void *sst, void *cid, struct buffer_if *buf) |
| 700 | { |
| 701 | return; |
| 702 | } |
| 703 | |
| 704 | static list_t *null_apply(closure_t *self, struct cloc loc, dict_t *context, |
| 705 | list_t *args) |
| 706 | { |
| 707 | struct null *st; |
| 708 | item_t *item; |
| 709 | dict_t *dict; |
| 710 | |
| 711 | st=safe_malloc(sizeof(*st),"null_apply"); |
| 712 | |
| 713 | item=list_elem(args,0); |
| 714 | if (!item || item->type!=t_dict) |
| 715 | cfgfatal(loc,"null-netlink","parameter must be a dictionary\n"); |
| 716 | |
| 717 | dict=item->data.dict; |
| 718 | |
| 719 | netlink_init(&st->nl,st,loc,dict,"null-netlink",null_set_route, |
| 720 | null_deliver); |
| 721 | |
| 722 | return new_closure(&st->nl.cl); |
| 723 | } |
| 724 | |
| 725 | init_module netlink_module; |
| 726 | void netlink_module(dict_t *dict) |
| 727 | { |
| 728 | add_closure(dict,"null-netlink",null_apply); |
| 729 | } |