chiark / gitweb /
Import release 0.1.9
[secnet.git] / netlink.c
index 886ce058cd6099c4a9d3a2bc7af16f6b9a5bde9e..b7cdb4cdef4a4ca6496f18b2ffa0cd08f3900f57 100644 (file)
--- a/netlink.c
+++ b/netlink.c
@@ -10,7 +10,9 @@
 
 #include "secnet.h"
 #include "util.h"
+#include "ipaddr.h"
 #include "netlink.h"
+#include "process.h"
 
 /* Generic IP checksum routine */
 static inline uint16_t ip_csum(uint8_t *iph,uint32_t count)
@@ -175,11 +177,19 @@ static void netlink_icmp_csum(struct icmphdr *h)
 static bool_t netlink_icmp_may_reply(struct buffer_if *buf)
 {
     struct iphdr *iph;
+    struct icmphdr *icmph;
     uint32_t source;
 
     iph=(struct iphdr *)buf->start;
-    if (iph->protocol==1) return False; /* Overly-broad; we may reply to
-                                          eg. icmp echo-request */
+    icmph=(struct icmphdr *)buf->start;
+    if (iph->protocol==1) {
+       switch(icmph->type) {
+       case 3: /* Destination unreachable */
+       case 11: /* Time Exceeded */
+       case 12: /* Parameter Problem */
+           return False;
+       }
+    }
     /* How do we spot broadcast destination addresses? */
     if (ntohs(iph->frag_off)&0x1fff) return False; /* Non-initial fragment */
     source=ntohl(iph->saddr);
@@ -274,6 +284,12 @@ static void netlink_packet_deliver(struct netlink *st,
        return;
     }
     
+    /* XXX we're going to need an extra value 'allow_route' for the
+       source of the packet. It's always True for packets from the
+       host. For packets from tunnels, we consult the client
+       options. If !allow_route and the destination is a tunnel that
+       also doesn't allow routing, we must reject the packet with an
+       'administratively prohibited' or something similar ICMP. */
     if (!client) {
        /* Origin of packet is host or secnet. Might be for a tunnel. */
        best_quality=0;
@@ -323,6 +339,9 @@ static void netlink_packet_deliver(struct netlink *st,
        }
     } else { /* client is set */
        /* We know the origin is a tunnel - packet must be for the host */
+       /* XXX THIS IS NOT NECESSARILY TRUE, AND NEEDS FIXING */
+       /* THIS FUNCTION MUST JUST DELIVER THE PACKET: IT MUST ASSUME
+          THE PACKET HAS ALREADY BEEN CHECKED */
        if (subnet_matches_list(&st->networks,dest)) {
            st->deliver_to_host(st->dst,NULL,buf);
            BUF_ASSERT_FREE(buf);
@@ -424,8 +443,8 @@ static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
 
     /* Check source */
     if (client) {
-       /* Check that the packet source is in 'nets' and its destination is
-          in st->networks */
+       /* Check that the packet source is appropriate for the tunnel
+          it came down */
        if (!subnet_matches_list(client->networks,source)) {
            string_t s,d;
            s=ipaddr_to_string(source);
@@ -437,6 +456,9 @@ static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
            return;
        }
     } else {
+       /* 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)) {
            string_t s,d;
            s=ipaddr_to_string(source);
@@ -448,6 +470,20 @@ static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
            return;
        }
     }
+
+    /* 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 (st->ptp) {
+       if (client) {
+           st->deliver_to_host(st->dst,NULL,buf);
+       } else {
+           st->clients->deliver(st->clients->dst,NULL,buf);
+       }
+       BUF_ASSERT_FREE(buf);
+       return;
+    }
+
     /* (st->secnet_address needs checking before matching destination
        addresses) */
     if (dest==st->secnet_address) {
@@ -456,6 +492,7 @@ static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
        return;
     }
     if (client) {
+       /* Check for free routing */
        if (!subnet_matches_list(&st->networks,dest)) {
            string_t s,d;
            s=ipaddr_to_string(source);
@@ -473,15 +510,18 @@ static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
 }
 
 static void netlink_set_softlinks(struct netlink *st, struct netlink_client *c,
-                                 bool_t up)
+                                 bool_t up, uint32_t quality)
 {
     uint32_t i;
 
     if (!st->routes) return; /* Table has not yet been created */
     for (i=0; i<st->n_routes; i++) {
-       if (!st->routes[i].hard && st->routes[i].c==c) {
-           st->routes[i].up=up;
-           st->set_route(st->dst,&st->routes[i]);
+       if (st->routes[i].c==c) {
+           st->routes[i].quality=quality;
+           if (!st->routes[i].hard) {
+               st->routes[i].up=up;
+               st->set_route(st->dst,&st->routes[i]);
+           }
        }
     }
 }
@@ -493,16 +533,16 @@ static void netlink_set_quality(void *sst, void *cid, uint32_t quality)
 
     c->link_quality=quality;
     if (c->link_quality==LINK_QUALITY_DOWN) {
-       netlink_set_softlinks(st,c,False);
+       netlink_set_softlinks(st,c,False,c->link_quality);
     } else {
-       netlink_set_softlinks(st,c,True);
+       netlink_set_softlinks(st,c,True,c->link_quality);
     }
 }
 
 static void *netlink_regnets(void *sst, struct subnet_list *nets,
                             netlink_deliver_fn *deliver, void *dst,
                             uint32_t max_start_pad, uint32_t max_end_pad,
-                            bool_t hard_routes, string_t client_name)
+                            uint32_t options, string_t client_name)
 {
     struct netlink *st=sst;
     struct netlink_client *c;
@@ -511,46 +551,40 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
            "max_start_pad=%d, max_end_pad=%d\n",
            nets->entries,max_start_pad,max_end_pad);
 
-    if (!hard_routes && !st->set_route) {
+    if ((options&NETLINK_OPTION_SOFTROUTE) && !st->set_route) {
        Message(M_ERROR,"%s: this netlink device does not support "
                "soft routes.\n");
        return NULL;
     }
 
-    if (!hard_routes) {
+    if (options&NETLINK_OPTION_SOFTROUTE) {
        /* XXX for now we assume that soft routes require root privilege;
-          this may not always be true. */
+          this may not always be true. The device driver can tell us. */
        require_root_privileges=True;
        require_root_privileges_explanation="netlink: soft routes";
     }
 
-#if 0
-    /* XXX POLICY: do we check nets against local networks?  If we do,
-       that prevents things like laptop tunnels working.  Perhaps we
-       can have a configuration option for this.  Or, if the admin
-       really doesn't want remote sites to be able to claim local
-       addresses, he can list them in exclude-remote-networks. */
-    if (subnet_lists_intersect(&st->networks,nets)) {
-       Message(M_ERROR,"%s: site %s specifies networks that "
-               "intersect with our local networks\n",st->name,client_name);
-       return False;
-    }
-#endif /* 0 */
     /* Check that nets do not intersect st->exclude_remote_networks;
        refuse to register if they do. */
     if (subnet_lists_intersect(&st->exclude_remote_networks,nets)) {
        Message(M_ERROR,"%s: site %s specifies networks that "
                "intersect with the explicitly excluded remote networks\n",
                st->name,client_name);
-       return False;
+       return NULL;
+    }
+
+    if (st->clients && st->ptp) {
+       fatal("%s: only one site may use a point-to-point netlink device\n",
+             st->name);
+       return NULL;
     }
 
     c=safe_malloc(sizeof(*c),"netlink_regnets");
     c->networks=nets;
     c->deliver=deliver;
     c->dst=dst;
-    c->name=client_name; /* XXX copy it? */
-    c->hard_routes=hard_routes;
+    c->name=client_name;
+    c->options=options;
     c->link_quality=LINK_QUALITY_DOWN;
     c->next=st->clients;
     st->clients=c;
@@ -561,24 +595,29 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
     return c;
 }
 
-static void netlink_dump_routes(struct netlink *st)
+static void netlink_dump_routes(struct netlink *st, bool_t requested)
 {
     int i;
     string_t net;
+    uint32_t c=M_INFO;
 
-    Message(M_INFO,"%s: routing table:\n",st->name);
+    if (requested) c=M_WARNING;
+    Message(c,"%s: routing table:\n",st->name);
     for (i=0; i<st->n_routes; i++) {
        net=subnet_to_string(&st->routes[i].net);
-       Message(M_INFO,"%s -> tunnel %s (%s,%s)\n",net,st->routes[i].c->name,
+       Message(c,"%s -> tunnel %s (%s,%s route,%s,quality %d)\n",net,
+               st->routes[i].c->name,
                st->routes[i].hard?"hard":"soft",
-               st->routes[i].up?"up":"down");
+               st->routes[i].allow_route?"free":"restricted",
+               st->routes[i].up?"up":"down",
+               st->routes[i].quality);
        free(net);
     }
-    Message(M_INFO,"%s/32 -> netlink \"%s\"\n",
+    Message(c,"%s/32 -> netlink \"%s\"\n",
            ipaddr_to_string(st->secnet_address),st->name);
     for (i=0; i<st->networks.entries; i++) {
        net=subnet_to_string(&st->networks.list[i]);
-       Message(M_INFO,"%s -> host\n",net);
+       Message(c,"%s -> host\n",net);
        free(net);
     }
 }
@@ -599,6 +638,13 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
     struct netlink_client *c;
     uint32_t i,j;
 
+    if (!st->clients && st->ptp) {
+       /* Point-to-point netlink devices must have precisely one
+          client. If none has registered by now, complain. */
+       fatal("%s: point-to-point netlink devices must have precisely "
+             "one client. This one doesn't have any.\n",st->name);
+    }
+
     /* 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.  */
@@ -610,10 +656,14 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
        for (j=0; j<c->networks->entries; j++) {
            st->routes[i].net=c->networks->list[j];
            st->routes[i].c=c;
-           st->routes[i].up=c->hard_routes; /* Hard routes are always up;
-                                               soft routes default to down */
+           /* Hard routes are always up;
+              soft routes default to down */
+           st->routes[i].up=c->options&NETLINK_OPTION_SOFTROUTE?False:True;
            st->routes[i].kup=False;
-           st->routes[i].hard=c->hard_routes;
+           st->routes[i].hard=c->options&NETLINK_OPTION_SOFTROUTE?False:True;
+           st->routes[i].allow_route=c->options&NETLINK_OPTION_ALLOW_ROUTE?
+               True:False;
+           st->routes[i].quality=c->link_quality;
            i++;
        }
     }
@@ -626,7 +676,14 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
     qsort(st->routes,st->n_routes,sizeof(*st->routes),
          netlink_compare_route_specificity);
 
-    netlink_dump_routes(st);
+    netlink_dump_routes(st,False);
+}
+
+static void netlink_signal_handler(void *sst, int signum)
+{
+    struct netlink *st=sst;
+    Message(M_INFO,"%s: route dump requested by SIGUSR1\n",st->name);
+    netlink_dump_routes(st,True);
 }
 
 netlink_deliver_fn *netlink_init(struct netlink *st,
@@ -635,6 +692,8 @@ netlink_deliver_fn *netlink_init(struct netlink *st,
                                 netlink_route_fn *set_route,
                                 netlink_deliver_fn *to_host)
 {
+    item_t *sa, *ptpa;
+
     st->dst=dst;
     st->cl.description=description;
     st->cl.type=CL_NETLINK;
@@ -656,19 +715,33 @@ netlink_deliver_fn *netlink_init(struct netlink *st,
                          &st->networks);
     dict_read_subnet_list(dict, "exclude-remote-networks", False, "netlink",
                          loc, &st->exclude_remote_networks);
-    /* local-address and secnet-address do not have to be in local-networks;
-       however, they should be advertised in the 'sites' file for the
+    /* secnet-address does not have to be in local-networks;
+       however, it should be advertised in the 'sites' file for the
        local site. */
-    st->local_address=string_to_ipaddr(
-       dict_find_item(dict,"local-address", True, "netlink", loc),"netlink");
-    st->secnet_address=string_to_ipaddr(
-       dict_find_item(dict,"secnet-address", True, "netlink", loc),"netlink");
+    sa=dict_find_item(dict,"secnet-address",False,"netlink",loc);
+    ptpa=dict_find_item(dict,"ptp-address", False, "netlink", loc);
+    if (sa && ptpa) {
+       cfgfatal(loc,st->name,"you may not specify secnet-address and "
+                "ptp-address in the same netlink device\n");
+    }
+    if (!(sa || ptpa)) {
+       cfgfatal(loc,st->name,"you must specify secnet-address or "
+                "ptp-address for this netlink device\n");
+    }
+    if (sa) {
+       st->secnet_address=string_to_ipaddr(sa,"netlink");
+       st->ptp=False;
+    } else {
+       st->secnet_address=string_to_ipaddr(ptpa,"netlink");
+       st->ptp=True;
+    }
     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);
+    request_signal_notification(SIGUSR1, netlink_signal_handler, st);
 
     return netlink_incoming;
 }