From 794f2398b8fe84bf398bb10d6eeca6fe6737f65f Mon Sep 17 00:00:00 2001 From: Stephen Early Date: Sat, 27 Oct 2001 15:29:00 +0100 Subject: [PATCH] Import release 0.1.11 --- BUGS | 2 + INSTALL | 81 +++++--- Makefile.in | 4 +- NEWS | 36 +++- NOTES | 4 +- TODO | 16 +- conffile.c | 95 --------- config.h.bot | 6 + config.h.in | 6 + debian/changelog | 7 +- debian/files | 1 - debian/postinst.debhelper | 13 -- debian/postrm.debhelper | 5 - debian/prerm.debhelper | 10 - debian/rules | 2 +- debian/substvars | 1 - ipaddr.c | 416 +++++++++++++++++++++++++++++++++++--- ipaddr.h | 61 +++++- netlink.c | 130 +++++++----- netlink.h | 8 +- process.c | 17 ++ secnet.c | 25 ++- secnet.h | 29 +-- site.c | 109 +++++++--- slip.c | 28 ++- tun.c | 4 +- udp.c | 12 +- 27 files changed, 803 insertions(+), 325 deletions(-) create mode 100644 BUGS delete mode 100644 debian/files delete mode 100644 debian/postinst.debhelper delete mode 100644 debian/postrm.debhelper delete mode 100644 debian/prerm.debhelper delete mode 100644 debian/substvars diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..7145f3a --- /dev/null +++ b/BUGS @@ -0,0 +1,2 @@ +Known bugs in secnet + diff --git a/INSTALL b/INSTALL index ab4debd..f41b4ba 100644 --- a/INSTALL +++ b/INSTALL @@ -32,11 +32,11 @@ linux/Documentation/networking/tuntap.txt If you're using TUN/TAP on a platform other than Linux-2.4, see http://vtun.sourceforge.net/tun/ -Note than TUN comes in two flavours, one (called 'tun' in the secnet -config file) which has only one device file (usually /dev/net/tun) and -the other (called 'tun-old') which has many device files (/dev/tun*). -Linux-2.4 has new-style TUN, Linux-2.2, BSD and Solaris have old-style -TUN. +Note than TUN comes in several flavours. Their names in the +configuration file are: + tun: Linux-2.4; only one device file (usually /dev/net/tun) + tun-old: Linux-2.2, BSD; device files /dev/tun* + tun-solaris: Solaris (not yet implemented) ** System and network configuration @@ -54,18 +54,18 @@ modify the routing table.) If you are joining an existing VPN, read that VPN's documentation now. It may supersede the next paragraph. -You will need to allocate two IP addresses for use by secnet. One -will be for the tunnel interface on your tunnel endpoint machine (i.e. -the address you see in 'ifconfig' when you look at the tunnel -interface). The other will be for secnet itself. These addresses -should probably be allocated from the range used by your internal -network: if you do this, you should provide appropriate proxy-ARP on -the internal network interface of the machine running secnet (eg. add -an entry net/ipv4/conf/eth_whatever/proxy_arp = 1 to /etc/sysctl.conf -on Debian systems and run sysctl -p). Alternatively the addresses -could be from some other range - this works well if the machine -running secnet is the default route out of your network - but this -requires more thought. +In most configurations, you will need to allocate two IP addresses for +use by secnet. One will be for the tunnel interface on your tunnel +endpoint machine (i.e. the address you see in 'ifconfig' when you look +at the tunnel interface). The other will be for secnet itself. These +addresses should probably be allocated from the range used by your +internal network: if you do this, you should provide appropriate +proxy-ARP on the internal network interface of the machine running +secnet (eg. add an entry net/ipv4/conf/eth_whatever/proxy_arp = 1 to +/etc/sysctl.conf on Debian systems and run sysctl -p). Alternatively +the addresses could be from some other range - this works well if the +machine running secnet is the default route out of your network - but +this requires more thought. http://www.ucam.org/cam-grin/ may be useful. @@ -83,10 +83,32 @@ $ make # mkdir /etc/secnet (Note: you may see the following warning while compiling -conffile.tab.c; I believe this is a bison bug: +conffile.tab.c; this is a bug in bison-1.28: /usr/share/bison/bison.simple: In function `yyparse': /usr/share/bison/bison.simple:285: warning: `yyval' might be used uninitialized in this function + +You may if you wish apply the following patch to bison.simple: +diff -pu -r1.28.0.1 -r1.28.0.3 +--- bison.s1 1999/08/30 19:23:24 1.28.0.1 ++++ bison.s1 1999/08/30 21:15:18 1.28.0.3 +@@ -523,8 +523,14 @@ yydefault: + /* Do a reduction. yyn is the number of a rule to reduce with. */ + yyreduce: + yylen = yyr2[yyn]; +- if (yylen > 0) +- yyval = yyvsp[1-yylen]; /* implement default value of the action */ ++ ++ /* If yylen is nonzero, implement the default value of the action. ++ Otherwise, the following line sets yyval to the semantic value of ++ the lookahead token. This behavior is undocumented and bison ++ users should not rely upon it. Assigning to yyval ++ unconditionally makes the parser a bit smaller, and it avoids a ++ GCC warning that yyval may be used uninitialized. */ ++ yyval = yyvsp[1-yylen]; + + #if YYDEBUG != 0 + if (yydebug) ) Any other warnings or errors should be reported to @@ -103,22 +125,23 @@ $ LDFLAGS="-L/usr/local/lib" ./configure $ gmake CFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" XXX this should eventually be worked out automatically by 'configure'.] -Generate a site file fragment for your site (see below), and submit it -for inclusion in your VPN's 'sites' file. Download the vpn-sites file -to /etc/secnet/sites - MAKE SURE YOU GET AN AUTHENTIC COPY because the -sites file contains public keys for all the sites in the VPN. Use the -make-secnet-sites program provided with the secnet distribution to -convert the distributed sites file into one that can be included in a -secnet configuration file: +Generate a site file fragment for your site (see your VPN's +documentation, or see below), and submit it for inclusion in your +VPN's 'sites' file. Download the vpn-sites file to /etc/secnet/sites +- MAKE SURE YOU GET AN AUTHENTIC COPY because the sites file contains +public keys for all the sites in the VPN. Use the make-secnet-sites +program provided with the secnet distribution to convert the +distributed sites file into one that can be included in a secnet +configuration file: -# make-secnet-sites sites sites.conf +# make-secnet-sites /etc/secnet/sites /etc/secnet/sites.conf * Configuration Should be reasonably obvious - edit /etc/secnet/secnet.conf as -prompted by the comments. XXX Fuller documentation of the -configuration file format should be forthcoming in time. Its syntax -is described in the README file at the moment. +prompted by the comments in example.conf. XXX Fuller documentation of +the configuration file format should be forthcoming in time. Its +syntax is described in the README file at the moment. * Constructing your site file fragment diff --git a/Makefile.in b/Makefile.in index 0e4926c..9a912b5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -18,7 +18,7 @@ .PHONY: all clean realclean dist install distclean PACKAGE:=secnet -VERSION:=0.1.10 +VERSION:=0.1.11 @SET_MAKE@ @@ -48,7 +48,7 @@ OBJECTS:=secnet.o util.o conffile.yy.o conffile.tab.o conffile.o modules.o \ serpent.o md5.o version.o tun.o slip.o sha1.o ipaddr.o log.o \ process.o @LIBOBJS@ -DISTFILES:=COPYING CREDITS INSTALL LICENSE.txt Makefile.in \ +DISTFILES:=BUGS COPYING CREDITS INSTALL LICENSE.txt Makefile.in \ NEWS NOTES README TODO \ alloca.c \ conffile.c conffile.fl conffile.h conffile.y \ diff --git a/NEWS b/NEWS index be5c38a..60cf744 100644 --- a/NEWS +++ b/NEWS @@ -5,7 +5,41 @@ Netlink device that implements an Ethernet bridge. Modular transform code: choice of block ciphers, modes, sequence numbers / timestamps, etc. similar to IWJ's udptunnel -* New in versino 0.1.11 +* New in version 0.1.11 + +Lists of IP addresses in the configuration file can now include +exclusions as well as inclusions. For example, you can specify all +the hosts on a subnet except one as follows: + +networks "192.168.73.0/24","!192.168.73.70"; + +(If you were only allowed inclusions, you'd have to specify that like +this: +networks "192.168.73.71/32","192.168.73.68/31","192.168.73.64/30", + "192.168.73.72/29","192.168.73.80/28","192.168.73.96/27", + "192.168.73.0/26","192.168.73.128/25"; +) + +secnet now ensures that it invokes userv-ipif with a non-overlapping +list of subnets. + +There is a new command-line option, --sites-key or -s, that enables +the configuration file key that's checked to determine the list of +active sites (default "sites") to be changed. This enables a single +configuration file to contain multiple cofigurations conveniently. + +NAKs are now sent when packets arrive that are not understood. The +tunnel code initiates a key setup if it sees a NAK. Future +developments should include configuration options that control this. + +The tunnel code notifies its peer when secnet is terminating, so the +peer can close the session. + +The netlink "exclude-remote-networks" option has now been replaced by +a "remote-networks" option; instead of specifying networks that no +site may access, you specify the set of networks that remote sites are +allowed to access. A sensible example: "192.168.0.0/16", +"172.16.0.0/12", "10.0.0.0/8", "!your-local-network" * New in version 0.1.10 diff --git a/NOTES b/NOTES index 2443549..485443b 100644 --- a/NOTES +++ b/NOTES @@ -231,9 +231,7 @@ retransmit or confirm reception. It is suggested that this message be sent when a key times out, or the tunnel is forcibly terminated for some reason. -XXX not yet implemented. - -8) i?,i?,NAK/msg8 +8) i?,i?,NAK (encoded as zero) If the link-layer can't work out what to do with a packet (session has gone away, etc.) it can transmit a NAK back to the sender. The sender diff --git a/TODO b/TODO index 5ae53cf..e02c627 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,18 @@ Makefile.in: autodep stuff +Make it work using the distributed install.sh (which doesn't support -D) dh.c: change format to binary from decimal string (without introducing endianness problems) -ipaddr.c: implement the useful functionality from ipaddr.py +netlink.c: test the 'allow_route' option properly. -netlink.c: investigate why 'default' routes don't appear to work -(reported by JDA). -Test the 'allow_route' option properly. +process.c: capture output from children in sys_cmd() and log it random.c: test +resolver.c: ought to return a list of addresses for each address; the +site code ought to remember them and try contacting them in turn. + rsa.c: check padding type, change format to binary from decimal string (without introducing endianness problems) @@ -24,14 +26,8 @@ does it take to prompt a key exchange?) slip.c: restart userv-ipif to cope with soft routes? Restart it if it fails in use? -userv-ipif doesn't like the same bit of network to be specified -twice. Use the new functionality in ipaddr.c once it's done to prevent -this. -Work out why slip.c doesn't compile on Solaris-2.5.1 transform.c: separate the transforms into multiple parts, which can then be combined in the configuration file. Will allow the user to plug in different block ciphers, invent an authenticity-only mode, etc. - -udp.c: actually send NAKs rather than just complaining. diff --git a/conffile.c b/conffile.c index f33354e..3c629e4 100644 --- a/conffile.c +++ b/conffile.c @@ -717,101 +717,6 @@ bool_t dict_read_bool(dict_t *dict, string_t key, bool_t required, return r; } -static struct subnet string_to_subnet(item_t *i, string_t desc) -{ - struct subnet s; - uint32_t a, b, c, d, n; - uint32_t match; - - /* i is not guaranteed to be a string */ - if (i->type!=t_string) { - cfgfatal(i->loc,desc,"expecting a string (subnet specification)\n"); - } - - if (strcmp(i->data.string,"default")==0) { - s.prefix=0; - s.mask=0; - s.len=0; - return s; - } - - /* We expect strings of the form "a.b.c.d[/n]", i.e. the dots are - NOT optional. The subnet mask is optional; if missing it is assumed - to be /32. */ - match=sscanf(i->data.string,"%u.%u.%u.%u/%u", &a, &b, &c, &d, &n); - if (match<4) { - cfgfatal(i->loc,desc,"\"%s\" is not a valid " - "subnet specification\n",i->data.string); - } - if (match<5) { - n=32; - } - if (a>255 || b>255 || c>255 || d>255 || n>32) { - cfgfatal(i->loc,desc,"\"%s\": range error\n",i->data.string); - } - s.prefix=(a<<24)|(b<<16)|(c<<8)|(d); - s.mask=n?(~0UL << (32-n)):0; - s.len=n; - if (s.prefix & ~s.mask) { - cfgfatal(i->loc,desc,"\"%s\": prefix not fully contained " - "in mask\n",i->data.string); - } - return s; -} - -uint32_t string_to_ipaddr(item_t *i, string_t desc) -{ - uint32_t a, b, c, d; - uint32_t match; - - /* i is not guaranteed to be a string */ - if (i->type!=t_string) { - cfgfatal(i->loc,desc,"expecting a string (IP address)\n"); - } - - match=sscanf(i->data.string,"%u.%u.%u.%u", &a, &b, &c, &d); - if (match<4) { - cfgfatal(i->loc,desc,"\"%s\" is not a valid " - "IP address\n",i->data.string); - } - if (a>255 || b>255 || c>255 || d>255) { - cfgfatal(i->loc,desc,"\"%s\": range error\n",i->data.string); - } - return (a<<24)|(b<<16)|(c<<8)|(d); -} - -void dict_read_subnet_list(dict_t *dict, string_t key, bool_t required, - string_t desc, struct cloc loc, - struct subnet_list *sl) -{ - list_t *l, *li; - item_t *i; - uint32_t e=0; - - sl->entries=0; - sl->list=NULL; - l=dict_lookup(dict, key); - if (!l) { - if (!required) return; - cfgfatal(loc,desc,"required parameter \"%s\" not found\n",key); - } - /* Count the items in the list */ - e=list_length(l); - if (e==0) return; - sl->entries=e; - sl->list=safe_malloc(sizeof(struct subnet)*e, "dict_read_subnet_list"); - e=0; - /* Fill in the list */ - for (li=l; li; li=li->next) { - i=li->item; - if (i->type!=t_string) { - cfgfatal(loc,desc,"parameter \"%s\": all elements must " - "be strings\n",key); - } - sl->list[e++]=string_to_subnet(i,desc); - } -} - uint32_t string_to_word(string_t s, struct cloc loc, struct flagstr *f, string_t desc) { diff --git a/config.h.bot b/config.h.bot index 889736c..cf288bd 100644 --- a/config.h.bot +++ b/config.h.bot @@ -10,8 +10,10 @@ #else #if SIZEOF_UNSIGNED_LONG_LONG==8 typedef unsigned long long uint64_t; +typedef long long int64_t; #elif SIZEOF_UNSIGNED_LONG==8 typedef unsigned long uint64_t; +typedef long int64_t; #else #error I do not know what to use for a uint64_t. #endif @@ -19,8 +21,10 @@ typedef unsigned long uint64_t; /* Give us an unsigned 32-bit data type. */ #if SIZEOF_UNSIGNED_LONG==4 typedef unsigned long uint32_t; +typedef long int32_t; #elif SIZEOF_UNSIGNED_INT==4 typedef unsigned int uint32_t; +typedef int int32_t; #else #error I do not know what to use for a uint32_t. #endif @@ -28,8 +32,10 @@ typedef unsigned int uint32_t; /* An unsigned 16-bit data type. */ #if SIZEOF_UNSIGNED_INT==2 typedef unsigned int uint16_t; +typedef int int16_t; #elif SIZEOF_UNSIGNED_SHORT==2 typedef unsigned short uint16_t; +typedef short int16_t; #else #error I do not know what to use for a uint16_t. #endif diff --git a/config.h.in b/config.h.in index d7044fc..76d28f0 100644 --- a/config.h.in +++ b/config.h.in @@ -100,8 +100,10 @@ #else #if SIZEOF_UNSIGNED_LONG_LONG==8 typedef unsigned long long uint64_t; +typedef long long int64_t; #elif SIZEOF_UNSIGNED_LONG==8 typedef unsigned long uint64_t; +typedef long int64_t; #else #error I do not know what to use for a uint64_t. #endif @@ -109,8 +111,10 @@ typedef unsigned long uint64_t; /* Give us an unsigned 32-bit data type. */ #if SIZEOF_UNSIGNED_LONG==4 typedef unsigned long uint32_t; +typedef long int32_t; #elif SIZEOF_UNSIGNED_INT==4 typedef unsigned int uint32_t; +typedef int int32_t; #else #error I do not know what to use for a uint32_t. #endif @@ -118,8 +122,10 @@ typedef unsigned int uint32_t; /* An unsigned 16-bit data type. */ #if SIZEOF_UNSIGNED_INT==2 typedef unsigned int uint16_t; +typedef int int16_t; #elif SIZEOF_UNSIGNED_SHORT==2 typedef unsigned short uint16_t; +typedef short int16_t; #else #error I do not know what to use for a uint16_t. #endif diff --git a/debian/changelog b/debian/changelog index 2011174..8ac5260 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,8 @@ -secnet (0.1.10-1) unstable; urgency=low +secnet (0.1.11-1) unstable; urgency=low * New upstream version. - -- Stephen Early Sat, 29 Jun 2001 15:21:25 +0100 + * Note configuration file format has changed slightly; re-run + make-secnet-sites to fix it + + -- Stephen Early Sat, 27 Oct 2001 15:00:00 +0100 diff --git a/debian/files b/debian/files deleted file mode 100644 index 1638b95..0000000 --- a/debian/files +++ /dev/null @@ -1 +0,0 @@ -secnet_0.1.9-1_i386.deb net extra diff --git a/debian/postinst.debhelper b/debian/postinst.debhelper deleted file mode 100644 index 2ebe82f..0000000 --- a/debian/postinst.debhelper +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically added by dh_installdocs -if [ "$1" = "configure" ]; then - if [ -d /usr/doc -a ! -e /usr/doc/secnet -a -d /usr/share/doc/secnet ]; then - ln -sf ../share/doc/secnet /usr/doc/secnet - fi -fi -# End automatically added section -# Automatically added by dh_installinit -if [ -e "/etc/init.d/secnet" ]; then - update-rc.d secnet defaults >/dev/null - /etc/init.d/secnet start -fi -# End automatically added section diff --git a/debian/postrm.debhelper b/debian/postrm.debhelper deleted file mode 100644 index 5f1bce9..0000000 --- a/debian/postrm.debhelper +++ /dev/null @@ -1,5 +0,0 @@ -# Automatically added by dh_installinit -if [ "$1" = "purge" ] ; then - update-rc.d secnet remove >/dev/null -fi -# End automatically added section diff --git a/debian/prerm.debhelper b/debian/prerm.debhelper deleted file mode 100644 index 23a07d7..0000000 --- a/debian/prerm.debhelper +++ /dev/null @@ -1,10 +0,0 @@ -# Automatically added by dh_installdocs -if [ \( "$1" = "upgrade" -o "$1" = "remove" \) -a -L /usr/doc/secnet ]; then - rm -f /usr/doc/secnet -fi -# End automatically added section -# Automatically added by dh_installinit -if [ -e "/etc/init.d/secnet" ]; then - /etc/init.d/secnet stop -fi -# End automatically added section diff --git a/debian/rules b/debian/rules index a85db21..d9d1dd5 100755 --- a/debian/rules +++ b/debian/rules @@ -46,7 +46,7 @@ binary-arch: build install dh_testdir dh_testroot # dh_installdebconf - dh_installdocs INSTALL README NOTES TODO NEWS + dh_installdocs INSTALL README NOTES TODO NEWS BUGS CREDITS dh_installexamples example.conf # dh_installmenu # dh_installlogrotate diff --git a/debian/substvars b/debian/substvars deleted file mode 100644 index aa787d5..0000000 --- a/debian/substvars +++ /dev/null @@ -1 +0,0 @@ -shlibs:Depends=libc6 (>= 2.2.3-7), libgmp2 diff --git a/ipaddr.c b/ipaddr.c index f42e820..1edc334 100644 --- a/ipaddr.c +++ b/ipaddr.c @@ -1,48 +1,321 @@ +/* The 'ipset' data structure and related algorithms in this file were + inspired by the 'ipaddr.py' library from Cendio Systems AB. */ + #include "secnet.h" #include +#include "ipaddr.h" + +#define DEFAULT_ALLOC 2 +#define EXTEND_ALLOC_BY 4 + +struct subnet_list *subnet_list_new(void) +{ + struct subnet_list *r; + r=safe_malloc(sizeof(*r),"subnet_list_new:list"); + r->entries=0; + r->alloc=DEFAULT_ALLOC; + r->list=safe_malloc(sizeof(*r->list)*r->alloc,"subnet_list_new:data"); + return r; +} + +void subnet_list_free(struct subnet_list *a) +{ + if (a->list) free(a->list); + free(a); +} + +static void subnet_list_set_len(struct subnet_list *a, uint32_t l) +{ + struct subnet *nd; + uint32_t na; + + if (l>a->alloc) { + na=a->alloc+EXTEND_ALLOC_BY; + nd=realloc(a->list,sizeof(*nd)*na); + if (!nd) { + fatal_perror("subnet_list_set_len: realloc"); + } + a->alloc=na; + a->list=nd; + } + a->entries=l; +} + +void subnet_list_append(struct subnet_list *a, uint32_t prefix, uint32_t len) +{ + struct subnet *sn; + subnet_list_set_len(a,a->entries+1); + sn=&a->list[a->entries-1]; + sn->prefix=prefix; + sn->len=len; + sn->mask=len?(0xffffffff << (32-len)):0; +} -/* This file should eventually incorporate all the functionality of - ipaddr.py */ +struct ipset *ipset_new(void) +{ + struct ipset *r; + r=safe_malloc(sizeof(*r),"ipset_new:set"); + r->l=0; + r->a=DEFAULT_ALLOC; + r->d=safe_malloc(sizeof(*r->d)*r->a,"ipset_new:data"); + return r; +} -bool_t subnet_match(struct subnet *s, uint32_t address) +void ipset_free(struct ipset *a) { - return (s->prefix==(address&s->mask)); + if (a->d) free(a->d); + free(a); } -bool_t subnet_matches_list(struct subnet_list *list, uint32_t address) +#ifdef DEBUG +static void ipset_dump(struct ipset *a, string_t name) { uint32_t i; - for (i=0; ientries; i++) { - if (list->list[i].prefix == (address&list->list[i].mask)) return True; + + printf("%s: ",name); + for (i=0; il; i++) { + printf("[%08x-%08x] ",a->d[i].a,a->d[i].b); } - return False; + printf("\n"); } +#endif -bool_t subnets_intersect(struct subnet a, struct subnet b) +struct ipset *ipset_from_subnet(struct subnet s) { - uint32_t mask=a.mask&b.mask; - return ((a.prefix&mask)==(b.prefix&mask)); + struct ipset *r; + + r=ipset_new(); + r->l=1; + r->d[0].a=s.prefix; + r->d[0].b=s.prefix | (~s.mask); + return r; } -bool_t subnet_intersects_with_list(struct subnet a, struct subnet_list *b) +struct ipset *ipset_from_subnet_list(struct subnet_list *l) { + struct ipset *r, *a, *b; uint32_t i; - for (i=0; ientries; i++) { - if (subnets_intersect(a,b->list[i])) return True; + r=ipset_new(); + for (i=0; ientries; i++) { + a=ipset_from_subnet(l->list[i]); + b=ipset_union(r,a); + ipset_free(a); + ipset_free(r); + r=b; } - return False; + return r; } -bool_t subnet_lists_intersect(struct subnet_list *a, struct subnet_list *b) +static void ipset_set_len(struct ipset *a, uint32_t l) +{ + struct iprange *nd; + uint32_t na; + + if (l>a->a) { + na=a->a+EXTEND_ALLOC_BY; + nd=realloc(a->d,sizeof(*nd)*na); + if (!nd) { + fatal_perror("ipset_set_len: realloc"); + } + a->a=na; + a->d=nd; + } + a->l=l; +} + +static void ipset_append_range(struct ipset *a, struct iprange r) +{ + ipset_set_len(a,a->l+1); + a->d[a->l-1]=r; +} + +#define max(a,b) (a>b?a:b) +struct ipset *ipset_union(struct ipset *a, struct ipset *b) +{ + struct ipset *c; + struct iprange r; + uint32_t ia,ib; + + c=ipset_new(); + ia=0; ib=0; + while (ial || ibl) { + if (ial) + if (ibl) + if (a->d[ia].a < b->d[ib].a) + r=a->d[ia++]; + else + r=b->d[ib++]; + else + r=a->d[ia++]; + else + r=b->d[ib++]; + + if (c->l==0) + ipset_append_range(c,r); + else if (r.a <= c->d[c->l-1].b+1) + /* Extends (or is consumed by) the last range */ + c->d[c->l-1].b=max(c->d[c->l-1].b, r.b); + else + ipset_append_range(c,r); + } + return c; +} + +struct ipset *ipset_intersection(struct ipset *a, struct ipset *b) +{ + struct ipset *r; + struct iprange ra, rb; + uint32_t ia,ib; + + r=ipset_new(); + ia=0; ib=0; + + while (ial && ibl) { + ra=a->d[ia]; + rb=b->d[ib]; + if (ra.b < rb.a) + /* The first entry of a doesn't overlap with any part of b */ + ia++; + else if (ra.a > rb.b) + /* The first entry of b doesn't overlap with any part of a */ + ib++; + else { + /* Trim away any leading edges */ + if (ra.a < rb.a) + /* a starts before b */ + ra.a=rb.a; + else if (ra.a > rb.a) + /* b starts before a */ + rb.a=ra.a; + + /* Now the ranges start at the same point */ + if (ra.b == rb.b) { + /* The ranges are equal */ + ipset_append_range(r,ra); + ia++; + ib++; + } else if (ra.b < rb.b) { + /* a is the smaller range */ + ipset_append_range(r,ra); + ia++; + } else { + /* y is the smaller range */ + ipset_append_range(r,rb); + ib++; + } + } + } + return r; +} + +struct ipset *ipset_complement(struct ipset *a) +{ + struct ipset *r; + struct iprange n; + int64_t pre; + uint32_t i,lo,hi; + + r=ipset_new(); + pre=-1; + for (i=0; il; i++) { + lo=a->d[i].a; + hi=a->d[i].b; + if (lo!=0) { + n.a=pre+1; + n.b=lo-1; + ipset_append_range(r,n); + } + pre=hi; + } + if (pre!=0xffffffff) { + n.a=pre+1; + n.b=0xffffffff; + ipset_append_range(r,n); + } + return r; +} + +/* Return a-b */ +struct ipset *ipset_subtract(struct ipset *a, struct ipset *b) +{ + struct ipset *c, *r; + c=ipset_complement(b); + r=ipset_intersection(a,c); + ipset_free(c); + return r; +} + +bool_t ipset_is_empty(struct ipset *a) +{ + return (a->l==0); +} + +bool_t ipset_contains_addr(struct ipset *a, uint32_t addr) { uint32_t i; - for (i=0; ientries; i++) { - if (subnet_intersects_with_list(a->list[i],b)) return True; + struct iprange r; + + for (i=0; il; i++) { + r=a->d[i]; + if (addr>=r.a && addr<=r.b) return True; + if (addrl; i++) { + a=is->d[i].a; + b=is->d[i].b; + + lomask=1; + lobit=1; + himask=0xfffffffe; + bits=32; + while (a<=b) { + if ((a & lomask) != 0) { + subnet_list_append(r,a,bits); + a=a+lobit; + } else if ((b & lomask) != lomask) { + subnet_list_append(r,b&himask,bits); + b=b-lobit; + } else { + lomask = (lomask << 1) | 1; + lobit = (lobit << 1); + himask = himask ^ lobit; + bits = bits - 1; + ASSERT(bits>=0); + } + } + } + /* Sort the list? */ + return r; +} + /* The string buffer must be at least 16 bytes long */ string_t ipaddr_to_string(uint32_t addr) { @@ -58,25 +331,114 @@ string_t ipaddr_to_string(uint32_t addr) return s; } -string_t subnet_to_string(struct subnet *sn) +string_t subnet_to_string(struct subnet sn) { - uint32_t mask=sn->mask, addr=sn->prefix; + uint32_t addr=sn.prefix; uint8_t a,b,c,d; string_t s; - int i; s=safe_malloc(19,"subnet_to_string"); a=addr>>24; b=addr>>16; c=addr>>8; d=addr; - for (i=0; mask; i++) { - mask=(mask<<1); + snprintf(s, 19, "%d.%d.%d.%d/%d", a, b, c, d, sn.len); + return s; +} + +static struct subnet string_item_to_subnet(item_t *i, string_t desc, + bool_t *invert) +{ + struct subnet s; + uint32_t a, b, c, d, n; + uint32_t match; + string_t in; + + *invert=False; + + /* i is not guaranteed to be a string */ + if (i->type!=t_string) { + cfgfatal(i->loc,desc,"expecting a string (subnet specification)\n"); + } + in=i->data.string; + + if (strcmp(in,"default")==0) { + s.prefix=0; + s.mask=0; + s.len=0; + return s; + } + + if (*in=='!') { + *invert=True; + in++; + } + /* We expect strings of the form "a.b.c.d[/n]", i.e. the dots are + NOT optional. The subnet mask is optional; if missing it is assumed + to be /32. */ + match=sscanf(in,"%u.%u.%u.%u/%u", &a, &b, &c, &d, &n); + if (match<4) { + cfgfatal(i->loc,desc,"\"%s\" is not a valid " + "subnet specification\n",in); + } + if (match<5) { + n=32; } - if (i!=sn->len) { - fatal("subnet_to_string: invalid subnet structure " - "(i=%d sn->len=%d mask=0x%08x)!\n",i,sn->len,sn->mask); + if (a>255 || b>255 || c>255 || d>255 || n>32) { + cfgfatal(i->loc,desc,"\"%s\": range error\n",in); + } + s.prefix=(a<<24)|(b<<16)|(c<<8)|(d); + s.mask=n?(~0UL << (32-n)):0; + s.len=n; + if (s.prefix & ~s.mask) { + cfgfatal(i->loc,desc,"\"%s\": prefix not fully contained " + "in mask\n",in); } - snprintf(s, 19, "%d.%d.%d.%d/%d", a, b, c, d, sn->len); return s; } + +uint32_t string_item_to_ipaddr(item_t *i, string_t desc) +{ + uint32_t a, b, c, d; + uint32_t match; + + /* i is not guaranteed to be a string */ + if (i->type!=t_string) { + cfgfatal(i->loc,desc,"expecting a string (IP address)\n"); + } + + match=sscanf(i->data.string,"%u.%u.%u.%u", &a, &b, &c, &d); + if (match<4) { + cfgfatal(i->loc,desc,"\"%s\" is not a valid " + "IP address\n",i->data.string); + } + if (a>255 || b>255 || c>255 || d>255) { + cfgfatal(i->loc,desc,"\"%s\": range error\n",i->data.string); + } + return (a<<24)|(b<<16)|(c<<8)|(d); +} + +struct ipset *string_list_to_ipset(list_t *l, struct cloc loc, + string_t module, string_t param) +{ + struct ipset *r, *n, *isn; + uint32_t e,i; + item_t *item; + bool_t inv; + + r=ipset_new(); + e=list_length(l); + for (i=0; in_routes; i++) { - if (st->routes[i].up && subnet_match(&st->routes[i].net,dest)) { + if (st->routes[i].up && subnet_match(st->routes[i].net,dest)) { /* It's an available route to the correct destination. But is it better than the one we already have? */ @@ -334,7 +334,7 @@ static void netlink_packet_deliver(struct netlink *st, if (best_match==-1) { /* The packet's not going down a tunnel. It might (ought to) be for the host. */ - if (subnet_matches_list(&st->networks,dest)) { + if (ipset_contains_addr(st->networks,dest)) { st->deliver_to_host(st->dst,buf); st->outcount++; BUF_ASSERT_FREE(buf); @@ -466,10 +466,12 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client, dest=ntohl(iph->daddr); /* Check source */ + /* XXX consider generating ICMP if we're not point-to-point and we + don't like the packet */ if (client) { /* Check that the packet source is appropriate for the tunnel it came down */ - if (!subnet_matches_list(&client->networks,source)) { + if (!ipset_contains_addr(client->networks,source)) { string_t s,d; s=ipaddr_to_string(source); d=ipaddr_to_string(dest); @@ -483,7 +485,7 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client, /* Check that the packet originates in our configured local network, and hasn't been forwarded from elsewhere or generated with the wrong source address */ - if (!subnet_matches_list(&st->networks,source)) { + if (!ipset_contains_addr(st->networks,source)) { string_t s,d; s=ipaddr_to_string(source); d=ipaddr_to_string(dest); @@ -495,9 +497,11 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client, } } - /* If this is a point-to-point device we don't examine the packet at - all; we blindly send it down our one-and-only registered tunnel, - or to the host, depending on where it came from. */ + /* If this is a point-to-point device we don't examine the + destination address at all; we blindly send it down our + one-and-only registered tunnel, or to the host, depending on + where it came from. */ + /* XXX I think we should check destination addresses */ if (st->ptp) { if (client) { st->deliver_to_host(st->dst,buf); @@ -515,20 +519,6 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client, BUF_ASSERT_FREE(buf); return; } - if (client) { - /* Check for free routing */ - if (!subnet_matches_list(&st->networks,dest)) { - string_t s,d; - s=ipaddr_to_string(source); - d=ipaddr_to_string(dest); - Message(M_WARNING,"%s: incoming packet from tunnel %s " - "with bad destination address " - "(s=%s,d=%s)\n",st->name,client->name,s,d); - free(s); free(d); - BUF_FREE(buf); - return; - } - } netlink_packet_forward(st,client,buf); BUF_ASSERT_FREE(buf); } @@ -591,7 +581,7 @@ static void netlink_dump_routes(struct netlink *st, bool_t requested) st->name, net); free(net); for (i=0; in_routes; i++) { - net=subnet_to_string(&st->routes[i].net); + net=subnet_to_string(st->routes[i].net); Message(c,"%s ",net); free(net); } @@ -599,7 +589,7 @@ static void netlink_dump_routes(struct netlink *st, bool_t requested) } else { Message(c,"%s: routing table:\n",st->name); for (i=0; in_routes; i++) { - net=subnet_to_string(&st->routes[i].net); + net=subnet_to_string(st->routes[i].net); Message(c,"%s -> tunnel %s (%s,%s route,%s,quality %d,use %d)\n",net, st->routes[i].c->name, st->routes[i].hard?"hard":"soft", @@ -613,11 +603,13 @@ static void netlink_dump_routes(struct netlink *st, bool_t requested) Message(c,"%s/32 -> netlink \"%s\" (use %d)\n", net,st->name,st->localcount); free(net); - for (i=0; inetworks.entries; i++) { - net=subnet_to_string(&st->networks.list[i]); - Message(c,"%s -> host (use %d)\n",net,st->outcount); + for (i=0; isubnets->entries; i++) { + net=subnet_to_string(st->subnets->list[i]); + Message(c,"%s ",net); free(net); } + if (i>0) + Message(c,"-> host (use %d)\n",st->outcount); } } @@ -645,8 +637,8 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase) /* 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]; + for (j=0; jsubnets->entries; j++) { + st->routes[i].net=c->subnets->list[j]; st->routes[i].c=c; /* Hard routes are always up; soft routes default to down; routes with no 'deliver' function @@ -682,6 +674,27 @@ static void netlink_signal_handler(void *sst, int signum) netlink_dump_routes(st,True); } +static void netlink_inst_output_config(void *sst, struct buffer_if *buf) +{ +/* struct netlink_client *c=sst; */ +/* struct netlink *st=c->nst; */ + + /* For now we don't output anything */ + BUF_ASSERT_USED(buf); +} + +static bool_t netlink_inst_check_config(void *sst, struct buffer_if *buf) +{ +/* struct netlink_client *c=sst; */ +/* struct netlink *st=c->nst; */ + + BUF_ASSERT_USED(buf); + /* We need to eat all of the configuration information from the buffer + for backward compatibility. */ + buf->size=0; + return True; +} + static void netlink_inst_reg(void *sst, netlink_deliver_fn *deliver, void *dst, uint32_t max_start_pad, uint32_t max_end_pad) @@ -710,13 +723,16 @@ static closure_t *netlink_inst_create(struct netlink *st, { struct netlink_client *c; string_t name; - struct subnet_list networks; + struct ipset *networks; uint32_t options; + list_t *l; name=dict_read_string(dict, "name", True, st->name, loc); - dict_read_subnet_list(dict, "routes", True, st->name, loc, - &networks); + l=dict_lookup(dict,"routes"); + if (!l) + cfgfatal(loc,st->name,"required parameter \"routes\" not found\n"); + networks=string_list_to_ipset(l,loc,st->name,"routes"); options=string_list_to_word(dict_lookup(dict,"options"), netlink_option_table,st->name); @@ -738,11 +754,10 @@ static closure_t *netlink_inst_create(struct netlink *st, } } - /* Check that nets do not intersect st->exclude_remote_networks; - refuse to register if they do. */ - if (subnet_lists_intersect(&st->exclude_remote_networks,&networks)) { - cfgfatal(loc,st->name,"networks intersect with the explicitly " - "excluded remote networks\n"); + /* Check that nets are a subset of st->remote_networks; + refuse to register if they are not. */ + if (!ipset_is_subset(st->remote_networks,networks)) { + cfgfatal(loc,st->name,"routes are not allowed\n"); return NULL; } @@ -755,9 +770,12 @@ static closure_t *netlink_inst_create(struct netlink *st, c->ops.reg=netlink_inst_reg; c->ops.deliver=netlink_inst_incoming; c->ops.set_quality=netlink_set_quality; + c->ops.output_config=netlink_inst_output_config; + c->ops.check_config=netlink_inst_check_config; c->nst=st; c->networks=networks; + c->subnets=ipset_to_subnet_list(networks); c->deliver=NULL; c->dst=NULL; c->name=name; @@ -765,7 +783,7 @@ static closure_t *netlink_inst_create(struct netlink *st, c->link_quality=LINK_QUALITY_DOWN; c->next=st->clients; st->clients=c; - st->n_routes+=networks.entries; + st->n_routes+=c->subnets->entries; return &c->cl; } @@ -779,8 +797,6 @@ static list_t *netlink_inst_apply(closure_t *self, struct cloc loc, item_t *item; closure_t *cl; - Message(M_DEBUG_CONFIG,"netlink_inst_apply\n"); - item=list_elem(args,0); if (!item || item->type!=t_dict) { cfgfatal(loc,st->name,"must have a dictionary argument\n"); @@ -799,6 +815,7 @@ netlink_deliver_fn *netlink_init(struct netlink *st, netlink_deliver_fn *to_host) { item_t *sa, *ptpa; + list_t *l; st->dst=dst; st->cl.description=description; @@ -811,12 +828,27 @@ netlink_deliver_fn *netlink_init(struct netlink *st, st->set_route=set_route; st->deliver_to_host=to_host; - st->name=dict_read_string(dict,"name",False,"netlink",loc); + st->name=dict_read_string(dict,"name",False,description,loc); if (!st->name) st->name=description; - dict_read_subnet_list(dict, "networks", True, "netlink", loc, - &st->networks); - dict_read_subnet_list(dict, "exclude-remote-networks", False, "netlink", - loc, &st->exclude_remote_networks); + l=dict_lookup(dict,"networks"); + if (l) + st->networks=string_list_to_ipset(l,loc,st->name,"networks"); + else { + Message(M_WARNING,"%s: no local networks (parameter \"networks\") " + "defined\n",st->name); + st->networks=ipset_new(); + } + l=dict_lookup(dict,"remote-networks"); + if (l) { + st->remote_networks=string_list_to_ipset(l,loc,st->name, + "remote-networks"); + } else { + struct ipset *empty; + empty=ipset_new(); + st->remote_networks=ipset_complement(empty); + ipset_free(empty); + } + sa=dict_find_item(dict,"secnet-address",False,"netlink",loc); ptpa=dict_find_item(dict,"ptp-address",False,"netlink",loc); if (sa && ptpa) { @@ -828,12 +860,16 @@ netlink_deliver_fn *netlink_init(struct netlink *st, "ptp-address for this netlink device\n"); } if (sa) { - st->secnet_address=string_to_ipaddr(sa,"netlink"); + st->secnet_address=string_item_to_ipaddr(sa,"netlink"); st->ptp=False; } else { - st->secnet_address=string_to_ipaddr(ptpa,"netlink"); + st->secnet_address=string_item_to_ipaddr(ptpa,"netlink"); st->ptp=True; } + /* XXX we may want to subtract secnet_address from networks here, to + be strictly correct. It shouldn't make any practical difference, + though, and will make the route dump look complicated... */ + st->subnets=ipset_to_subnet_list(st->networks); st->mtu=dict_read_number(dict, "mtu", False, "netlink", loc, DEFAULT_MTU); buffer_new(&st->icmp,ICMP_BUFSIZE); st->n_routes=0; @@ -868,7 +904,7 @@ static bool_t null_set_route(void *sst, struct netlink_route *route) string_t t; if (route->up!=route->kup) { - t=subnet_to_string(&route->net); + t=subnet_to_string(route->net); Message(M_INFO,"%s: setting route %s to state %s\n",st->nl.name, t, route->up?"up":"down"); free(t); diff --git a/netlink.h b/netlink.h index c13b0ae..9f46fa8 100644 --- a/netlink.h +++ b/netlink.h @@ -13,7 +13,8 @@ struct netlink_client { closure_t cl; struct netlink_if ops; struct netlink *nst; - struct subnet_list networks; + struct ipset *networks; + struct subnet_list *subnets; /* Same information as 'networks' */ netlink_deliver_fn *deliver; void *dst; string_t name; @@ -45,8 +46,9 @@ struct netlink { string_t name; uint32_t max_start_pad; uint32_t max_end_pad; - struct subnet_list networks; - struct subnet_list exclude_remote_networks; + struct ipset *networks; /* Local networks */ + struct subnet_list *subnets; /* Same information as networks */ + struct ipset *remote_networks; /* Allowable remote networks */ uint32_t secnet_address; /* our own address, or the address of the other end of a point-to-point link */ bool_t ptp; diff --git a/process.c b/process.c index d9d01ce..8e464be 100644 --- a/process.c +++ b/process.c @@ -5,6 +5,23 @@ #include #include "process.h" +/* Advice about children from Peter: +Better way: before the fork, make a pipe. In the child close the ++reading end. Make the writing end close-on-exec. If the dup2 or exec fails, ++write the errno value. In the parent, close the writing end. Now you can read ++from it. If you get an errno value from the pipe, the process failed and you ++know why. If you get EOF, the exec succeeded. + + So, close on exec only closes if exec isn't going to return then? + qu: I wouldn't bother with all that with pipes. Remember that the ++runtime system can still make exec fail when it's `too late'. + Diz - I would rather have a coherant error message than 'child failed' + The child, if it fails to exec, should print a message to stderr ++(giving errno and what it was trying to execute, most likely), and exit ++nonzero. + It should exit calling _exit. +*/ + /* Process handling - subprocesses, signals, etc. */ static bool_t signal_handling=False; diff --git a/secnet.c b/secnet.c index a3cf153..20f9999 100644 --- a/secnet.c +++ b/secnet.c @@ -22,6 +22,7 @@ extern char version[]; /* XXX should be from autoconf */ static char *configfile="/etc/secnet/secnet.conf"; +static char *sites_key="sites"; bool_t just_check_config=False; static char *userid=NULL; static uid_t uid=0; @@ -73,10 +74,11 @@ static void parse_options(int argc, char **argv) {"debug", 1, 0, 'd'}, {"config", 1, 0, 'c'}, {"just-check-config", 0, 0, 'j'}, + {"sites-key", 1, 0, 's'}, {0,0,0,0} }; - c=getopt_long(argc, argv, "vwdnjc:ft:", + c=getopt_long(argc, argv, "vwdnjc:ft:s:", long_options, &option_index); if (c==-1) break; @@ -90,11 +92,15 @@ static void parse_options(int argc, char **argv) " -w, --nowarnings suppress warnings\n" " -v, --verbose output extra diagnostics\n" " -c, --config=filename specify a configuration file\n" - " -j, --just-check-config stop after reading configfile\n" + " -j, --just-check-config stop after reading " + "configuration file\n" + " -s, --sites-key=name configuration key that " + "specifies active sites\n" " -n, --nodetach do not run in background\n" " -d, --debug=item,... set debug options\n" " --help display this help and exit\n" - " --version output version information and exit\n" + " --version output version information " + "and exit\n" ); exit(0); break; @@ -137,6 +143,13 @@ static void parse_options(int argc, char **argv) just_check_config=True; break; + case 's': + if (optarg) + sites_key=safe_strdup(optarg,"sites-key"); + else + fatal("secnet: no sites key specified"); + break; + case '?': break; @@ -202,10 +215,10 @@ static void setup(dict_t *config) } /* Go along site list, starting sites */ - l=dict_lookup(config,"sites"); + l=dict_lookup(config,sites_key); if (!l) { - Message(M_WARNING,"secnet: configuration did not define any " - "remote sites\n"); + Message(M_WARNING,"secnet: configuration key \"%s\" is missing; no " + "remote sites are defined\n",sites_key); } else { i=0; while ((site=list_elem(l, i++))) { diff --git a/secnet.h b/secnet.h index 3821b81..c40ed1b 100644 --- a/secnet.h +++ b/secnet.h @@ -14,24 +14,8 @@ typedef char *string_t; typedef enum {False,True} bool_t; -#define ASSERT(x) do { if (!(x)) { fatal("assertion failed line " __LINE__ \ - " file " __FILE__ "\n"); } while(0) - -/***** SHARED types *****/ - -/* These are stored in HOST byte order */ -struct subnet { - uint32_t prefix; - uint32_t mask; - uint32_t len; -}; - -struct subnet_list { - uint32_t entries; - struct subnet *list; -}; - -/***** END of shared types *****/ +#define ASSERT(x) do { if (!(x)) { fatal("assertion failed line %d file " \ + __FILE__ "\n",__LINE__); } } while(0) /***** CONFIGURATION support *****/ @@ -112,10 +96,6 @@ extern uint32_t dict_read_number(dict_t *dict, string_t key, bool_t required, string_t desc, struct cloc loc, uint32_t def); extern bool_t dict_read_bool(dict_t *dict, string_t key, bool_t required, string_t desc, struct cloc loc, bool_t def); -extern void dict_read_subnet_list(dict_t *dict, string_t key, bool_t required, - string_t desc, struct cloc loc, - struct subnet_list *sl); -extern uint32_t string_to_ipaddr(item_t *i, string_t desc); struct flagstr { string_t name; uint32_t value; @@ -386,12 +366,15 @@ typedef void netlink_link_quality_fn(void *st, uint32_t quality); typedef void netlink_register_fn(void *st, netlink_deliver_fn *deliver, void *dst, uint32_t max_start_pad, uint32_t max_end_pad); - +typedef void netlink_output_config_fn(void *st, struct buffer_if *buf); +typedef bool_t netlink_check_config_fn(void *st, struct buffer_if *buf); struct netlink_if { void *st; netlink_register_fn *reg; netlink_deliver_fn *deliver; netlink_link_quality_fn *set_quality; + netlink_output_config_fn *output_config; + netlink_check_config_fn *check_config; }; /* DH interface */ diff --git a/site.c b/site.c index fc045c9..cb0e0aa 100644 --- a/site.c +++ b/site.c @@ -221,7 +221,8 @@ static void slog(struct site *st, uint32_t event, string_t msg, ...) } static void set_link_quality(struct site *st); -static bool_t initiate_key_setup(struct site *st); +static void delete_key(struct site *st, string_t reason, uint32_t loglevel); +static bool_t initiate_key_setup(struct site *st, string_t reason); static void enter_state_run(struct site *st); static bool_t enter_state_resolve(struct site *st); static bool_t enter_state_sentmsg1(struct site *st); @@ -516,6 +517,9 @@ static bool_t generate_msg5(struct site *st) /* We are going to add three words to the transformed message */ buffer_init(&st->buffer,st->transform->max_start_pad+(4*3)); buf_append_uint32(&st->buffer,LABEL_MSG5); + /* Give the netlink code an opportunity to put its own stuff in the + message (configuration information, etc.) */ + st->netlink->output_config(st->netlink->st,&st->buffer); st->new_transform->forwards(st->new_transform->st,&st->buffer, &transform_err); buf_prepend_uint32(&st->buffer,LABEL_MSG5); @@ -565,6 +569,10 @@ static bool_t process_msg5(struct site *st, struct buffer_if *msg5, slog(st,LOG_SEC,"MSG5/PING packet contained invalid data"); return False; } + if (!st->netlink->check_config(st->netlink->st,msg5)) { + slog(st,LOG_SEC,"MSG5/PING packet contained bad netlink config"); + return False; + } CHECK_EMPTY(msg5); return True; } @@ -577,6 +585,9 @@ static bool_t generate_msg6(struct site *st) /* We are going to add three words to the transformed message */ buffer_init(&st->buffer,st->transform->max_start_pad+(4*3)); buf_append_uint32(&st->buffer,LABEL_MSG6); + /* Give the netlink code an opportunity to put its own stuff in the + message (configuration information, etc.) */ + st->netlink->output_config(st->netlink->st,&st->buffer); st->new_transform->forwards(st->new_transform->st,&st->buffer, &transform_err); buf_prepend_uint32(&st->buffer,LABEL_MSG6); @@ -607,6 +618,10 @@ static bool_t process_msg6(struct site *st, struct buffer_if *msg6, slog(st,LOG_SEC,"MSG6/PONG packet contained invalid data"); return False; } + if (!st->netlink->check_config(st->netlink->st,msg6)) { + slog(st,LOG_SEC,"MSG6/PONG packet contained bad netlink config"); + return False; + } CHECK_EMPTY(msg6); return True; } @@ -620,7 +635,7 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0, if (!st->current_valid) { slog(st,LOG_DROP,"incoming message but no current key -> dropping"); - return initiate_key_setup(st); + return initiate_key_setup(st,"incoming message but no current key"); } if (!unpick_msg0(st,msg0,&m)) return False; @@ -629,11 +644,16 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0, msg0,&transform_err)) { /* There's a problem */ slog(st,LOG_SEC,"transform: %s",transform_err); - return initiate_key_setup(st); + return initiate_key_setup(st,"incoming message would not decrypt"); } CHECK_AVAIL(msg0,4); type=buf_unprepend_uint32(msg0); switch(type) { + case LABEL_MSG7: + /* We must forget about the current session. */ + delete_key(st,"request from peer",LOG_SEC); + return True; + break; case LABEL_MSG9: /* Deliver to netlink layer */ st->netlink->deliver(st->netlink->st,msg0); @@ -701,15 +721,15 @@ static void site_resolve_callback(void *sst, struct in_addr *address) } } -static bool_t initiate_key_setup(struct site *st) +static bool_t initiate_key_setup(struct site *st,string_t reason) { if (st->state!=SITE_RUN) return False; + slog(st,LOG_SETUP_INIT,"initiating key exchange (%s)",reason); if (st->address) { - slog(st,LOG_SETUP_INIT,"initiating key exchange; resolving address"); + slog(st,LOG_SETUP_INIT,"resolving peer address"); return enter_state_resolve(st); } else if (st->peer_valid) { - slog(st,LOG_SETUP_INIT,"initiating key exchange using old " - "peer address"); + slog(st,LOG_SETUP_INIT,"using old peer address"); st->setup_peer=st->peer; return enter_state_sentmsg1(st); } @@ -736,6 +756,18 @@ static void activate_new_key(struct site *st) enter_state_run(st); } +static void delete_key(struct site *st, string_t reason, uint32_t loglevel) +{ + if (st->current_valid) { + slog(st,loglevel,"session closed (%s)",reason); + + st->current_valid=False; + st->current_transform->delkey(st->current_transform->st); + st->current_key_timeout=0; + set_link_quality(st); + } +} + static void state_assert(struct site *st, bool_t ok) { if (!ok) fatal("state_assert\n"); @@ -745,14 +777,7 @@ static void enter_state_stop(struct site *st) { st->state=SITE_STOP; st->timeout=0; - st->current_transform->delkey(st->current_transform->st); - st->current_valid=False; - st->current_key_timeout=0; - - st->peer_valid=False; - - set_link_quality(st); - + delete_key(st,"entering state STOP",LOG_TIMEOUT_KEY); st->new_transform->delkey(st->new_transform->st); } @@ -887,6 +912,27 @@ static bool_t send_msg6(struct site *st) return False; } +static bool_t send_msg7(struct site *st,string_t reason) +{ + string_t transform_err; + + if (st->current_valid && st->peer_valid && st->buffer.free) { + BUF_ALLOC(&st->buffer,"site:MSG7"); + buffer_init(&st->buffer,st->transform->max_start_pad+(4*3)); + buf_append_uint32(&st->buffer,LABEL_MSG7); + buf_append_string(&st->buffer,reason); + st->current_transform->forwards(st->current_transform->st, + &st->buffer, &transform_err); + buf_prepend_uint32(&st->buffer,LABEL_MSG0); + buf_prepend_uint32(&st->buffer,(uint32_t)st); + buf_prepend_uint32(&st->buffer,st->remote_session_id); + st->comm->sendmsg(st->comm->st,&st->buffer,&st->peer); + BUF_FREE(&st->buffer); + return True; + } + return False; +} + /* We go into this state if our peer becomes uncommunicative. Similar to the "stop" state, we forget all session keys for a while, before re-entering the "run" state. */ @@ -943,11 +989,7 @@ static void site_afterpoll(void *sst, struct pollfd *fds, int nfds, } } if (st->current_key_timeout && *now>st->current_key_timeout) { - slog(st,LOG_TIMEOUT_KEY,"maximum key life exceeded; session closed"); - st->current_valid=False; - st->current_transform->delkey(st->current_transform->st); - st->current_key_timeout=0; - set_link_quality(st); + delete_key(st,"maximum key life exceeded",LOG_TIMEOUT_KEY); } } @@ -983,7 +1025,7 @@ static void site_outgoing(void *sst, struct buffer_if *buf) slog(st,LOG_DROP,"discarding outgoing packet of size %d",buf->size); BUF_FREE(buf); - initiate_key_setup(st); + initiate_key_setup(st,"outgoing packet"); } /* This function is called by the communication device to deliver @@ -1045,10 +1087,19 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, return False; /* Not for us. */ } if (dest==(uint32_t)st) { - uint32_t msgtype=ntohl(*(uint32_t *)(buf->start+8)); + uint32_t msgtype=ntohl(get_uint32(buf->start+8)); /* Explicitly addressed to us */ if (msgtype!=LABEL_MSG0) dump_packet(st,buf,source,True); switch (msgtype) { + case 0: /* NAK */ + /* If the source is our current peer then initiate a key setup, + because our peer's forgotten the key */ + if (get_uint32(buf->start+4)==st->remote_session_id) { + initiate_key_setup(st,"received a NAK"); + } else { + slog(st,LOG_SEC,"bad incoming NAK"); + } + break; case LABEL_MSG0: process_msg0(st,buf,source); break; @@ -1113,10 +1164,6 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, slog(st,LOG_SEC,"invalid MSG6"); } break; - case LABEL_MSG8: - /* NAK packet: enter state where we ping and check for response */ - slog(st,LOG_ERROR,"received a NAK"); - break; default: slog(st,LOG_SEC,"received message of unknown type 0x%08x", msgtype); @@ -1136,6 +1183,14 @@ static void site_control(void *vst, bool_t run) else enter_state_stop(st); } +static void site_phase_hook(void *sst, uint32_t newphase) +{ + struct site *st=sst; + + /* The program is shutting down; tell our peer */ + send_msg7(st,"shutting down"); +} + static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, list_t *args) { @@ -1254,6 +1309,8 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, enter_state_stop(st); + add_hook(PHASE_SHUTDOWN,site_phase_hook,st); + return new_closure(&st->cl); } diff --git a/slip.c b/slip.c index d0a77c0..60152c9 100644 --- a/slip.c +++ b/slip.c @@ -114,7 +114,7 @@ static void slip_init(struct slip *st, struct cloc loc, dict_t *dict, netlink_init(&st->nl,st,loc,dict, "netlink-userv-ipif",NULL,to_host); st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"name",loc); - st->local_address=string_to_ipaddr( + st->local_address=string_item_to_ipaddr( dict_find_item(dict,"local-address", True, name, loc),"netlink"); BUF_ALLOC(st->buff,"slip_init"); st->pending_esc=False; @@ -240,6 +240,8 @@ static void userv_invoke_userv(struct userv *st) string_t nets; string_t s; struct netlink_route *r; + struct ipset *isnets; + struct subnet_list *snets; int i; uint8_t confirm; @@ -255,19 +257,31 @@ static void userv_invoke_userv(struct userv *st) ipaddr_to_string(st->slip.local_address), ipaddr_to_string(st->slip.nl.secnet_address),st->slip.nl.mtu); - nets=safe_malloc(1024,"userv_invoke_userv:nets"); - *nets=0; r=st->slip.nl.routes; + isnets=ipset_new(); for (i=0; islip.nl.n_routes; i++) { if (r[i].up) { + struct ipset *sn,*nis; r[i].kup=True; - s=subnet_to_string(&r[i].net); - strcat(nets,s); - strcat(nets,","); - free(s); + sn=ipset_from_subnet(r[i].net); + nis=ipset_union(isnets,sn); + ipset_free(sn); + ipset_free(isnets); + isnets=nis; } } + snets=ipset_to_subnet_list(isnets); + ipset_free(isnets); + nets=safe_malloc(20*snets->entries,"userv_invoke_userv:nets"); + *nets=0; + for (i=0; ientries; i++) { + s=subnet_to_string(snets->list[i]); + strcat(nets,s); + strcat(nets,","); + free(s); + } nets[strlen(nets)-1]=0; + subnet_list_free(snets); Message(M_INFO,"%s: about to invoke: %s %s %s %s %s\n",st->slip.nl.name, st->userv_path,st->service_user,st->service_name,addrs,nets); diff --git a/tun.c b/tun.c index 90b9161..20ae29b 100644 --- a/tun.c +++ b/tun.c @@ -233,7 +233,7 @@ static list_t *tun_apply(closure_t *self, struct cloc loc, dict_t *context, if (!st->ifconfig_path) st->ifconfig_path="ifconfig"; if (!st->route_path) st->route_path="route"; st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"tun-netlink",loc); - st->local_address=string_to_ipaddr( + st->local_address=string_item_to_ipaddr( dict_find_item(dict,"local-address", True, "netlink", loc),"netlink"); add_hook(PHASE_GETRESOURCES,tun_phase_hook,st); @@ -276,7 +276,7 @@ static list_t *tun_old_apply(closure_t *self, struct cloc loc, dict_t *context, if (!st->ifconfig_path) st->ifconfig_path="ifconfig"; if (!st->route_path) st->route_path="route"; st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"tun-netlink",loc); - st->local_address=string_to_ipaddr( + st->local_address=string_item_to_ipaddr( dict_find_item(dict,"local-address", True, "netlink", loc),"netlink"); /* Old TUN interface: the network interface name depends on which diff --git a/udp.c b/udp.c index 307a67a..6520865 100644 --- a/udp.c +++ b/udp.c @@ -21,6 +21,7 @@ #include #include #include "util.h" +#include "unaligned.h" static beforepoll_fn udp_beforepoll; static afterpoll_fn udp_afterpoll; @@ -91,8 +92,17 @@ static void udp_afterpoll(void *state, struct pollfd *fds, int nfds, } } if (!done) { + uint32_t source,dest; /* XXX manufacture and send NAK packet */ - Message(M_WARNING,"Need to send NAK\n"); + source=get_uint32(st->rbuf->start); /* Us */ + dest=get_uint32(st->rbuf->start+4); /* Them */ + Message(M_INFO,"udp (port %d): sending NAK\n",st->port); + buffer_init(st->rbuf,0); + buf_append_uint32(st->rbuf,dest); + buf_append_uint32(st->rbuf,source); + buf_append_uint32(st->rbuf,0); /* NAK is msg type 0 */ + sendto(st->fd, st->rbuf->start, st->rbuf->size, 0, + (struct sockaddr *)&from, sizeof(from)); BUF_FREE(st->rbuf); } BUF_ASSERT_FREE(st->rbuf); -- 2.30.2