From 70dc107b6edee55270619b895f17acb66065f7ea Mon Sep 17 00:00:00 2001 From: Stephen Early Date: Thu, 27 Sep 2001 19:10:00 +0100 Subject: [PATCH] Import release 0.1.1 --- Makefile.in | 2 +- TODO | 1 + conffile.c | 1 + dh.c | 12 +++- netlink.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++------------ rsa.c | 30 ++++++---- secnet.h | 22 +++++-- site.c | 28 +++++---- util.c | 14 ++++- 9 files changed, 234 insertions(+), 72 deletions(-) diff --git a/Makefile.in b/Makefile.in index a06fcae..881a726 100644 --- a/Makefile.in +++ b/Makefile.in @@ -18,7 +18,7 @@ .PHONY: all clean realclean dist install PACKAGE:=secnet -VERSION:=0.1.0 +VERSION:=0.1.1 @SET_MAKE@ diff --git a/TODO b/TODO index 071a716..811ee1a 100644 --- a/TODO +++ b/TODO @@ -8,6 +8,7 @@ dh.c: change format to binary from decimal string (without introducing endianness problems) netlink.c: done. jdamery reports tun-old code works on Linux-2.2 +Unresolved problem with ioctl(TUNSETIFF) sometimes return EINVAL. random.c: test diff --git a/conffile.c b/conffile.c index 45889ba..c9796fc 100644 --- a/conffile.c +++ b/conffile.c @@ -681,6 +681,7 @@ static struct subnet string_to_subnet(item_t *i, string_t desc) } s.prefix=(a<<24)|(b<<16)|(c<<8)|(d); s.mask=(~0UL << (32-n)); + s.len=n; if (s.prefix & ~s.mask) { cfgfatal(i->loc,desc,"\"%s\": prefix not fully contained " "in mask\n",i->data.string); diff --git a/dh.c b/dh.c index eb9ae21..67ebd50 100644 --- a/dh.c +++ b/dh.c @@ -135,9 +135,15 @@ static list_t *dh_apply(closure_t *self, struct cloc loc, dict_t *context, cfgfatal(loc,"diffie-hellman","you must provide a generator\n"); } - /* Test that the modulus is really prime */ - if (mpz_probab_prime_p(&st->p,5)==0) { - cfgfatal(loc,"diffie-hellman","modulus must be a prime\n"); + i=list_elem(args,2); + if (i && i->type==t_bool && i->data.bool==False) { + Message(M_INFO,"diffie-hellman (%s:%d): skipping modulus " + "primality check\n",loc.file,loc.line); + } else { + /* Test that the modulus is really prime */ + if (mpz_probab_prime_p(&st->p,5)==0) { + cfgfatal(loc,"diffie-hellman","modulus must be a prime\n"); + } } st->ops.len=mpz_sizeinbase(&st->p,2)/8; diff --git a/netlink.c b/netlink.c index 0cbb27d..dea05f2 100644 --- a/netlink.c +++ b/netlink.c @@ -48,10 +48,15 @@ struct netlink_client { netlink_deliver_fn *deliver; void *dst; string_t name; - bool_t can_deliver; + uint32_t link_quality; struct netlink_client *next; }; +struct netlink_route { + struct subnet net; + struct netlink_client *c; +}; + /* Netlink provides one function to the device driver, to call to deliver a packet from the device. The device driver provides one function to netlink, for it to call to deliver a packet to the device. */ @@ -71,6 +76,8 @@ struct netlink { struct netlink_client *clients; netlink_deliver_fn *deliver_to_host; /* Provided by driver */ struct buffer_if icmp; /* Buffer for assembly of outgoing ICMP */ + uint32_t n_routes; /* How many routes do we know about? */ + struct netlink_route *routes; }; /* Generic IP checksum routine */ @@ -174,7 +181,9 @@ struct icmphdr { } d; }; -static void netlink_packet_deliver(struct netlink *st, struct buffer_if *buf); +static void netlink_packet_deliver(struct netlink *st, + struct netlink_client *client, + struct buffer_if *buf); static struct icmphdr *netlink_icmp_tmpl(struct netlink *st, uint32_t dest,uint16_t len) @@ -264,7 +273,10 @@ static uint16_t netlink_icmp_reply_len(struct buffer_if *buf) return (hlen>plen?plen:hlen); } +/* client indicates where the packet we're constructing a response to + comes from. NULL indicates the host. */ static void netlink_icmp_simple(struct netlink *st, struct buffer_if *buf, + struct netlink_client *client, uint8_t type, uint8_t code) { struct iphdr *iph=(struct iphdr *)buf->start; @@ -277,7 +289,7 @@ static void netlink_icmp_simple(struct netlink *st, struct buffer_if *buf, h->type=type; h->code=code; memcpy(buf_append(&st->icmp,len),buf->start,len); netlink_icmp_csum(h); - netlink_packet_deliver(st,&st->icmp); + netlink_packet_deliver(st,NULL,&st->icmp); BUF_ASSERT_FREE(&st->icmp); } } @@ -321,11 +333,19 @@ static bool_t netlink_check(struct netlink *st, struct buffer_if *buf) return True; } -static void netlink_packet_deliver(struct netlink *st, struct buffer_if *buf) +/* Deliver a packet. "client" points to the _origin_ of the packet, not + its destination. (May be used when sending ICMP response - avoid + asymmetric routing.) */ +static void netlink_packet_deliver(struct netlink *st, + struct netlink_client *client, + struct buffer_if *buf) { struct iphdr *iph=(struct iphdr *)buf->start; uint32_t dest=ntohl(iph->daddr); - struct netlink_client *c; + uint32_t source=ntohl(iph->saddr); + uint32_t best_quality; + int best_match; + int i; BUF_ASSERT_USED(buf); @@ -335,30 +355,69 @@ static void netlink_packet_deliver(struct netlink *st, struct buffer_if *buf) return; } - for (c=st->clients; c; c=c->next) { - if (subnet_match(c->networks,dest)) { - if (c->can_deliver) { - c->deliver(c->dst,c,buf); + if (!client) { + /* Origin of packet is host or secnet. Might be for a tunnel. */ + best_quality=0; + best_match=-1; + for (i=0; in_routes; i++) { + if (subnet_match(&st->routes[i].net,dest)) { + if (st->routes[i].c->link_quality>best_quality + || best_quality==0) { + best_quality=st->routes[i].c->link_quality; + best_match=i; + /* If quality isn't perfect we may wish to + consider kicking the tunnel with a 0-length + packet to prompt it to perform a key setup. + Then it'll eventually decide it's up or + down. */ + /* If quality is perfect we don't need to search + any more. */ + if (best_quality>=MAXIMUM_LINK_QUALITY) break; + } + } + } + if (best_match==-1) { + /* Not going down a tunnel. Might be for the host. + XXX think about this - only situation should be if we're + sending ICMP. */ + if (source!=st->secnet_address) { + Message(M_ERROR,"netlink_packet_deliver: outgoing packet " + "from host that won't fit down any of our tunnels!\n"); + BUF_FREE(buf); + } else { + st->deliver_to_host(st->dst,NULL,buf); + BUF_ASSERT_FREE(buf); + } + } else { + if (best_quality>0) { + st->routes[best_match].c->deliver( + st->routes[best_match].c->dst, + st->routes[best_match].c, buf); BUF_ASSERT_FREE(buf); } else { /* Generate ICMP destination unreachable */ - netlink_icmp_simple(st,buf,3,0); + netlink_icmp_simple(st,buf,client,3,0); /* client==NULL */ BUF_FREE(buf); } - return; + } + } else { /* client is set */ + /* We know the origin is a tunnel - packet must be for the host */ + if (subnet_matches_list(&st->networks,dest)) { + st->deliver_to_host(st->dst,NULL,buf); + BUF_ASSERT_FREE(buf); + } else { + Message(M_ERROR,"%s: packet from tunnel %s can't be delivered " + "to the host\n",st->name,client->name); + netlink_icmp_simple(st,buf,client,3,0); + BUF_FREE(buf); } } - if (subnet_match(&st->networks,dest)) { - st->deliver_to_host(st->dst,NULL,buf); - BUF_ASSERT_FREE(buf); - return; - } - Message(M_ERROR,"%s: failed to deliver a packet (bad destination address)" - "\nXXX make this message clearer\n"); - BUF_FREE(buf); + BUF_ASSERT_FREE(buf); } -static void netlink_packet_forward(struct netlink *st, struct buffer_if *buf) +static void netlink_packet_forward(struct netlink *st, + struct netlink_client *client, + struct buffer_if *buf) { struct iphdr *iph=(struct iphdr *)buf->start; @@ -367,7 +426,7 @@ static void netlink_packet_forward(struct netlink *st, struct buffer_if *buf) /* Packet has already been checked */ if (iph->ttl<=1) { /* Generate ICMP time exceeded */ - netlink_icmp_simple(st,buf,11,0); + netlink_icmp_simple(st,buf,client,11,0); BUF_FREE(buf); return; } @@ -375,13 +434,15 @@ static void netlink_packet_forward(struct netlink *st, struct buffer_if *buf) iph->check=0; iph->check=ip_fast_csum((uint8_t *)iph,iph->ihl); - netlink_packet_deliver(st,buf); + netlink_packet_deliver(st,client,buf); BUF_ASSERT_FREE(buf); } /* Someone has been foolish enough to address a packet to us. I suppose we should reply to it, just to be polite. */ -static void netlink_packet_local(struct netlink *st, struct buffer_if *buf) +static void netlink_packet_local(struct netlink *st, + struct netlink_client *client, + struct buffer_if *buf) { struct icmphdr *h; @@ -405,13 +466,13 @@ static void netlink_packet_local(struct netlink *st, struct buffer_if *buf) h->iph.check=0; h->iph.check=ip_fast_csum((uint8_t *)h,h->iph.ihl); netlink_icmp_csum(h); - netlink_packet_deliver(st,buf); + netlink_packet_deliver(st,NULL,buf); return; } Message(M_WARNING,"%s: unknown incoming ICMP\n",st->name); } else { /* Send ICMP protocol unreachable */ - netlink_icmp_simple(st,buf,3,2); + netlink_icmp_simple(st,buf,client,3,2); BUF_FREE(buf); return; } @@ -441,8 +502,8 @@ static void netlink_from_tunnel(void *sst, void *cst, struct buffer_if *buf) dest=ntohl(iph->daddr); /* Check that the packet source is in 'nets' and its destination is - in client->networks */ - if (!subnet_match(client->networks,source)) { + in st->networks */ + if (!subnet_matches_list(client->networks,source)) { string_t s,d; s=ipaddr_to_string(source); d=ipaddr_to_string(dest); @@ -456,11 +517,11 @@ static void netlink_from_tunnel(void *sst, void *cst, struct buffer_if *buf) st->networks because secnet's IP address may not be in the range the host is willing to deal with) */ if (dest==st->secnet_address) { - netlink_packet_local(st,buf); + netlink_packet_local(st,client,buf); BUF_ASSERT_FREE(buf); return; } - if (!subnet_match(&st->networks,dest)) { + if (!subnet_matches_list(&st->networks,dest)) { string_t s,d; s=ipaddr_to_string(source); d=ipaddr_to_string(dest); @@ -472,7 +533,7 @@ static void netlink_from_tunnel(void *sst, void *cst, struct buffer_if *buf) return; } - netlink_packet_forward(st,buf); + netlink_packet_forward(st,client,buf); BUF_ASSERT_FREE(buf); } @@ -498,7 +559,7 @@ static void netlink_from_host(void *sst, void *cid, struct buffer_if *buf) source=ntohl(iph->saddr); dest=ntohl(iph->daddr); - if (!subnet_match(&st->networks,source)) { + if (!subnet_matches_list(&st->networks,source)) { string_t s,d; s=ipaddr_to_string(source); d=ipaddr_to_string(dest); @@ -509,19 +570,19 @@ static void netlink_from_host(void *sst, void *cid, struct buffer_if *buf) return; } if (dest==st->secnet_address) { - netlink_packet_local(st,buf); + netlink_packet_local(st,NULL,buf); BUF_ASSERT_FREE(buf); return; } - netlink_packet_forward(st,buf); + netlink_packet_forward(st,NULL,buf); BUF_ASSERT_FREE(buf); } -static void netlink_set_delivery(void *sst, void *cid, bool_t can_deliver) +static void netlink_set_quality(void *sst, void *cid, uint32_t quality) { struct netlink_client *c=cid; - c->can_deliver=can_deliver; + c->link_quality=quality; } static void *netlink_regnets(void *sst, struct subnet_list *nets, @@ -555,15 +616,71 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets, c->deliver=deliver; c->dst=dst; c->name=client_name; /* XXX copy it? */ - c->can_deliver=False; + c->link_quality=LINK_QUALITY_DOWN; c->next=st->clients; st->clients=c; if (max_start_pad > st->max_start_pad) st->max_start_pad=max_start_pad; if (max_end_pad > st->max_end_pad) st->max_end_pad=max_end_pad; + st->n_routes+=nets->entries; return c; } +static int netlink_compare_route_specificity(const void *ap, const void *bp) +{ + const struct netlink_route *a=ap; + const struct netlink_route *b=bp; + + if (a->net.len==b->net.len) return 0; + if (a->net.lennet.len) return 1; + return -1; +} + +static void netlink_phase_hook(void *sst, uint32_t new_phase) +{ + struct netlink *st=sst; + struct netlink_client *c; + uint32_t i,j; + + /* All the networks serviced by the various tunnels should now + * have been registered. We build a routing table by sorting the + * routes into most-specific-first order. */ + st->routes=safe_malloc(st->n_routes*sizeof(*st->routes), + "netlink_phase_hook"); + /* Fill the table */ + i=0; + for (c=st->clients; c; c=c->next) { + for (j=0; jnetworks->entries; j++) { + st->routes[i].net=c->networks->list[j]; + st->routes[i].c=c; + i++; + } + } + /* ASSERT i==st->n_routes */ + if (i!=st->n_routes) { + fatal("netlink: route count error: expected %d got %d\n", + st->n_routes,i); + } + /* Sort the table in descending order of specificity */ + qsort(st->routes,st->n_routes,sizeof(*st->routes), + netlink_compare_route_specificity); + Message(M_INFO,"%s: routing table:\n",st->name); + for (i=0; in_routes; i++) { + string_t net; + net=subnet_to_string(&st->routes[i].net); + Message(M_INFO,"%s -> tunnel %s\n",net,st->routes[i].c->name); + free(net); + } + Message(M_INFO,"%s/32 -> netlink \"%s\"\n", + ipaddr_to_string(st->secnet_address),st->name); + for (i=0; inetworks.entries; i++) { + string_t net; + net=subnet_to_string(&st->networks.list[i]); + Message(M_INFO,"%s -> host\n",net); + free(net); + } +} + static netlink_deliver_fn *netlink_init(struct netlink *st, void *dst, struct cloc loc, dict_t *dict, string_t description, @@ -577,7 +694,7 @@ static netlink_deliver_fn *netlink_init(struct netlink *st, st->ops.st=st; st->ops.regnets=netlink_regnets; st->ops.deliver=netlink_from_tunnel; - st->ops.set_delivery=netlink_set_delivery; + st->ops.set_quality=netlink_set_quality; st->max_start_pad=0; st->max_end_pad=0; st->clients=NULL; @@ -598,6 +715,10 @@ static netlink_deliver_fn *netlink_init(struct netlink *st, dict_find_item(dict,"secnet-address", True, "netlink", loc),"netlink"); st->mtu=dict_read_number(dict, "mtu", False, "netlink", loc, DEFAULT_MTU); buffer_new(&st->icmp,ICMP_BUFSIZE); + st->n_routes=0; + st->routes=NULL; + + add_hook(PHASE_SETUP,netlink_phase_hook,st); return netlink_from_host; } @@ -972,6 +1093,7 @@ static void tun_phase_hook(void *sst, uint32_t newphase) no extra headers */ if (st->interface_name) strncpy(ifr.ifr_name,st->interface_name,IFNAMSIZ); + Message(M_INFO,"%s: about to ioctl(TUNSETIFF)...\n",st->nl.name); if (ioctl(st->fd,TUNSETIFF,&ifr)<0) { fatal_perror("%s: ioctl(TUNSETIFF)",st->nl.name); } diff --git a/rsa.c b/rsa.c index 0b6f147..03318ba 100644 --- a/rsa.c +++ b/rsa.c @@ -317,19 +317,25 @@ static list_t *rsapriv_apply(closure_t *self, struct cloc loc, dict_t *context, /* Now do trial signature/check to make sure it's a real keypair: sign the comment string! */ - mpz_init(&sig); - mpz_init(&plain); - mpz_init(&check); - read_mpbin(&plain,c,strlen(c)); - mpz_powm(&sig, &plain, &st->d, &st->n); - mpz_powm(&check, &sig, &e, &st->n); - if (mpz_cmp(&plain,&check)!=0) { - cfgfatal(loc,"rsa-private","file \"%s\" does not contain a " - "valid RSA key!\n",filename); + i=list_elem(args,1); + if (i && i->type==t_bool && i->data.bool==False) { + Message(M_INFO,"rsa-private (%s:%d): skipping RSA key validity " + "check\n",loc.file,loc.line); + } else { + mpz_init(&sig); + mpz_init(&plain); + mpz_init(&check); + read_mpbin(&plain,c,strlen(c)); + mpz_powm(&sig, &plain, &st->d, &st->n); + mpz_powm(&check, &sig, &e, &st->n); + if (mpz_cmp(&plain,&check)!=0) { + cfgfatal(loc,"rsa-private","file \"%s\" does not contain a " + "valid RSA key!\n",filename); + } + mpz_clear(&sig); + mpz_clear(&plain); + mpz_clear(&check); } - mpz_clear(&sig); - mpz_clear(&plain); - mpz_clear(&check); free(c); mpz_clear(&e); diff --git a/secnet.h b/secnet.h index f0f3a3e..f5b96b6 100644 --- a/secnet.h +++ b/secnet.h @@ -24,6 +24,7 @@ typedef enum {False,True} bool_t; struct subnet { uint32_t prefix; uint32_t mask; + uint32_t len; }; struct subnet_list { @@ -33,7 +34,8 @@ struct subnet_list { /* Match an address (in HOST byte order) with a subnet list. Returns True if matched. */ -extern bool_t subnet_match(struct subnet_list *list, uint32_t address); +extern bool_t subnet_match(struct subnet *s, uint32_t address); +extern bool_t subnet_matches_list(struct subnet_list *list, uint32_t address); extern bool_t subnets_intersect(struct subnet a, struct subnet b); extern bool_t subnet_intersects_with_list(struct subnet a, struct subnet_list *b); @@ -353,12 +355,20 @@ struct transform_if { /* NETLINK interface */ -/* Used by netlink to deliver to site, and by site to deliver to netlink. - cid is the client identifier returned by netlink_regnets_fn */ +/* Used by netlink to deliver to site, and by site to deliver to + netlink. cid is the client identifier returned by + netlink_regnets_fn. If buf has size 0 then the function is just + being called for its site-effects (eg. making the site code attempt + to bring up a network link) */ typedef void netlink_deliver_fn(void *st, void *cid, struct buffer_if *buf); /* site code can tell netlink when outgoing packets will be dropped, - so netlink can generate appropriate ICMP */ -typedef void netlink_can_deliver_fn(void *st, void *cid, bool_t can_deliver); + so netlink can generate appropriate ICMP and make routing decisions */ +#define LINK_QUALITY_DOWN 0 /* No chance of a packet being delivered */ +#define LINK_QUALITY_DOWN_STALE_ADDRESS 1 /* Link down, old address information */ +#define LINK_QUALITY_DOWN_CURRENT_ADDRESS 2 /* Link down, current address information */ +#define LINK_QUALITY_UP 3 /* Link active */ +#define MAXIMUM_LINK_QUALITY 3 +typedef void netlink_link_quality_fn(void *st, void *cid, uint32_t quality); /* Register for packets from specified networks. Return value is client identifier. */ typedef void *netlink_regnets_fn(void *st, struct subnet_list *networks, @@ -370,7 +380,7 @@ struct netlink_if { void *st; netlink_regnets_fn *regnets; netlink_deliver_fn *deliver; - netlink_can_deliver_fn *set_delivery; + netlink_link_quality_fn *set_quality; }; /* DH interface */ diff --git a/site.c b/site.c index efa1ebb..9bfbf9d 100644 --- a/site.c +++ b/site.c @@ -704,7 +704,7 @@ static void enter_state_run(struct site *st) slog(st,LOG_STATE,"entering state RUN"); st->state=SITE_RUN; st->timeout=0; - st->netlink->set_delivery(st->netlink->st,st->netlink_cid,True); + st->netlink->set_quality(st->netlink->st,st->netlink_cid,LINK_QUALITY_UP); /* XXX get rid of key setup data */ } @@ -821,7 +821,8 @@ static void enter_state_wait(struct site *st) st->timeout=st->now+st->wait_timeout; st->state=SITE_WAIT; st->peer_valid=False; - st->netlink->set_delivery(st->netlink->st,st->netlink_cid,False); + st->netlink->set_quality(st->netlink->st,st->netlink_cid, + LINK_QUALITY_DOWN); BUF_FREE(&st->buffer); /* will have had an outgoing packet in it */ /* XXX Erase keys etc. */ } @@ -892,13 +893,15 @@ static void site_outgoing(void *sst, void *cid, struct buffer_if *buf) a valid key and a valid address to send it to. */ if (st->current_valid && st->peer_valid) { /* Transform it and send it */ - buf_prepend_uint32(buf,LABEL_MSG9); - st->current_transform->forwards(st->current_transform->st, - buf, &transform_err); - buf_prepend_uint32(buf,LABEL_MSG0); - buf_prepend_uint32(buf,(uint32_t)st); - buf_prepend_uint32(buf,st->remote_session_id); - st->comm->sendmsg(st->comm->st,buf,&st->peer); + if (buf->size>0) { + buf_prepend_uint32(buf,LABEL_MSG9); + st->current_transform->forwards(st->current_transform->st, + buf, &transform_err); + buf_prepend_uint32(buf,LABEL_MSG0); + buf_prepend_uint32(buf,(uint32_t)st); + buf_prepend_uint32(buf,st->remote_session_id); + st->comm->sendmsg(st->comm->st,buf,&st->peer); + } BUF_FREE(buf); return; } @@ -912,7 +915,7 @@ static void site_outgoing(void *sst, void *cid, struct buffer_if *buf) /* Otherwise we're in the middle of key setup or a wait - just throw the outgoing packet away */ - slog(st,LOG_DROP,"discarding outgoing packet"); + slog(st,LOG_DROP,"discarding outgoing packet of size %d",buf->size); BUF_FREE(buf); return; } @@ -1169,6 +1172,11 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->transform->max_start_pad+(4*4), st->transform->max_end_pad, st->tunname); + if (!st->netlink_cid) { + fatal("%s: netlink device did not let us register our remote " + "networks. Check that they are not local or excluded.\n", + st->tunname); + } st->comm->request_notify(st->comm->st, st, site_incoming); diff --git a/util.c b/util.c index 0ffdcdd..503dfe2 100644 --- a/util.c +++ b/util.c @@ -239,7 +239,12 @@ uint32_t write_mpbin(MP_INT *a, uint8_t *buffer, uint32_t buflen) return i; } -bool_t subnet_match(struct subnet_list *list, uint32_t address) +bool_t subnet_match(struct subnet *s, uint32_t address) +{ + return (s->prefix==(address&s->mask)); +} + +bool_t subnet_matches_list(struct subnet_list *list, uint32_t address) { uint32_t i; for (i=0; ientries; i++) { @@ -303,7 +308,10 @@ string_t subnet_to_string(struct subnet *sn) for (i=0; mask; i++) { mask=(mask<<1); } - snprintf(s, 19, "%d.%d.%d.%d/%d", a, b, c, d, i); + if (i!=sn->len) { + fatal("subnet_to_string: invalid subnet structure!\n"); + } + snprintf(s, 19, "%d.%d.%d.%d/%d", a, b, c, d, sn->len); return s; } @@ -607,7 +615,7 @@ static list_t *buffer_apply(closure_t *self, struct cloc loc, dict_t *context, buffer_new(&st->ops,len); if (lockdown) { - Message(M_WARNING,"buffer: XXX lockdown\n"); + /* XXX mlock the buffer if possible */ } return new_closure(&st->cl); -- 2.11.0