New peer option `-mobile': follow rapid IP address and port changes.
authorMark Wooding <mdw@distorted.org.uk>
Mon, 27 Jun 2011 02:26:00 +0000 (03:26 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 21 Mar 2012 15:54:35 +0000 (15:54 +0000)
Mobile devices on 3G networks can change apparent IP address and port
numbers rapidly.  If any peer has the `-mobile' flag (PSF_MOBILE) set,
and a MSG_PACKET message arrives from an unexpected source, see if any
of the `-mobile' peers can decrypt it: if so, update the peer's address
rather than rejecting the packet.

This is inefficient because it involves a linear search of the peer
list.  If this turns out to be a problem, we can make some protocol
changes (e.g., inserting a peer hint into the packet) later.

Note that an adversary can deny service by capturing legitimate packets
from a mobile source and sending them on with a different address.  The
peer will assume that the target's address has changed.  But to make
this work effectively, the adversary's packet has to reach the target
before (or instead of) the legitimate one, or else the bogus packet will
be rejected for having a duplicate sequence number.  The next packet
received unmodified from the source will switch the target's idea of the
source's address back again anyway.  An adversary who can consistently
prevent packets from being delivered can trivially deny service anyway,
so this isn't actually much of a concern.

mon/tripemon.in
peerdb/peers.in.5.in
py/tripe.py.in
server/admin.c
server/peer.c
server/tripe-admin.5.in
server/tripe.h
svc/connect.8.in
svc/connect.in

index 413c8ab..4b8dd24 100644 (file)
@@ -293,7 +293,13 @@ class Peer (MonitorObject):
 
   def update(me, hunoz = None):
     """Update the peer, fetching information about it from the server."""
-    addr = conn.addr(me.name)
+    me._setaddr(me, conn.addr(me.name))
+    me.ifname = conn.ifname(me.name)
+    me.__dict__.update(conn.peerinfo(me.name))
+    me.changehook.run()
+
+  def _setaddr(me, addr):
+    """Set the peer's address."""
     if addr[0] == 'INET':
       ipaddr, port = addr[1:]
       try:
@@ -303,8 +309,10 @@ class Peer (MonitorObject):
         me.addr = 'INET %s:%s' % (ipaddr, port)
     else:
       me.addr = ' '.join(addr)
-    me.ifname = conn.ifname(me.name)
-    me.__dict__.update(conn.peerinfo(me.name))
+
+  def setaddr(me, addr):
+    """Informs the object of a change to its address to ADDR."""
+    me._setaddr(addr)
     me.changehook.run()
 
   def setifname(me, newname):
@@ -476,6 +484,11 @@ class Monitor (HookClient):
         me.peers[rest[0]].setifname(rest[2])
       except KeyError:
         pass
+    elif code == 'NEWADDR':
+      try:
+        me.peers[rest[0]].setaddr(rest[1:])
+      except KeyError:
+        pass
     elif code == 'SVCCLAIM':
       T.aside(me.services.add, rest[0], rest[1])
       if rest[0] == 'connect':
index ad7faed..c876365 100644 (file)
@@ -135,7 +135,7 @@ Shell command for initiating connection to this peer.  Used by
 .BR watch (8).
 .TP
 .B cork
-Don't initiate immediate key exchange..  Used by
+Don't initiate immediate key exchange.  Used by
 .BR connect (8).
 .TP
 .B every
@@ -170,6 +170,10 @@ Interval for sending keepalive pings.  Used by
 Key tag to use to authenticate the peer.  Used by
 .BR connect (8).
 .TP
+.B mobile
+Peer's IP address is highly volatile.  Used by
+.BR connect (8).
+.TP
 .B mtu
 Maximum transmission unit for the tunnel interface.  Used by
 .BR tripe-ifup (8).
index 37de5a4..cefb667 100644 (file)
@@ -831,7 +831,7 @@ class TripeCommandDispatcher (TripeConnection):
     return _simple(me.command(bg = True,
                               *['ADD'] +
                               _kwopts(kw, ['tunnel', 'keepalive',
-                                           'key', 'cork']) +
+                                           'key', 'cork', 'mobile']) +
                               [peer] +
                               list(addr)))
   def addr(me, peer):
index df8af92..7fa3f0f 100644 (file)
@@ -1258,6 +1258,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
        xfree(add->peer.tag);
       add->peer.tag = xstrdup(arg);
     })
+    OPT("-mobile", { add->peer.f |= PSF_MOBILE; })
   });
 
   /* --- Make sure someone's not got there already --- */
index ec89f77..72287cd 100644 (file)
@@ -33,6 +33,7 @@
 static sym_table byname;
 static addrmap byaddr;
 static sel_file sock;
+static unsigned nmobile;
 
 /*----- Tunnel table ------------------------------------------------------*/
 
@@ -122,6 +123,23 @@ found:
   p_pingdone(pg, PING_OK);
 }
 
+/* --- @p_rxupdstats@ --- *
+ *
+ * Arguments:  @peer *p@ = peer to update
+ *             @size_t n@ = size of incoming packet
+ *
+ * Returns:    ---
+ *
+ * Use:                Updates the peer's incoming packet statistics.
+ */
+
+static void p_rxupdstats(peer *p, size_t n)
+{
+  p->st.t_last = time(0);
+  p->st.n_in++;
+  p->st.sz_in += n;
+}
+
 /* --- @p_encrypt@ --- *
  *
  * Arguments:  @peer *p@ = peer to encrypt message to
@@ -151,7 +169,9 @@ static int p_encrypt(peer *p, int ty, buf *bin, buf *bout)
 
 /* --- @p_decrypt@ --- *
  *
- * Arguments:  @peer *p@ = peer to decrypt message from
+ * Arguments:  @peer **pp@ = pointer to peer to decrypt message from
+ *             @addr *a@ = address the packet arrived on
+ *             @size_t n@ = size of original incoming packet
  *             @int ty@ = message type to expect
  *             @buf *bin, *bout@ = input and output buffers
  *
@@ -159,13 +179,67 @@ static int p_encrypt(peer *p, int ty, buf *bin, buf *bout)
  *
  * Use:                Convenience function for packet decryption.  Reports errors
  *             and updates statistics appropriately.
+ *
+ *             If @*pp@ is null on entry and there are mobile peers then we
+ *             see if any of them can decrypt the packet.  If so, we record
+ *             @*a@ as the peer's new address and send a notification.
  */
 
-static int p_decrypt(peer *p, int ty, buf *bin, buf *bout)
+static int p_decrypt(peer **pp, addr *a, size_t n,
+                    int ty, buf *bin, buf *bout)
 {
-  if (ksl_decrypt(&p->ks, ty, bin, bout)) {
-    p->st.n_reject++;
-    a_warn("PEER", "?PEER", p, "decrypt-failed", A_END);
+  peer *p;
+  peer_byaddr *pa;
+  int err = KSERR_DECRYPT;
+  unsigned f;
+
+  if (*pp) {
+    p = *pp;
+    T( trace(T_PEER, "peer: decrypting packet from known peer `%s'",
+            p_name(p)); )
+    err = ksl_decrypt(&p->ks, ty, bin, bout);
+  } else {
+    p = 0;
+    if (nmobile) {
+      T( trace(T_PEER, "peer: unknown source: trying mobile peers..."); )
+      FOREACH_PEER(q, {
+       if ((err = ksl_decrypt(&q->ks, ty, bin, bout)) == KSERR_DECRYPT) {
+         T( trace(T_PEER, "peer: peer `%s' failed to decrypt",
+                  p_name(q)); )
+         continue;
+       } else {
+         p = *pp = q;
+         IF_TRACING(T_PEER, {
+           if (!err)
+             trace(T_PEER, "peer: peer `%s' reports success", p_name(p));
+           else {
+             trace(T_PEER, "peer: peer `%s' reports decryption error %d",
+                   p_name(p), err);
+           }
+         })
+         break;
+       }
+      });
+    }
+    if (!p) {
+      a_warn("PEER", "-", "unexpected-source", "?ADDR", a, A_END);
+      return (-1);
+    }
+    if (!err) {
+      T( trace(T_PEER, "peer: updating address for `%s'", p_name(p)); )
+      p_rxupdstats(p, n);
+      pa = am_find(&byaddr, a, sizeof(peer_byaddr), &f); assert(!f);
+      am_remove(&byaddr, p->byaddr);
+      p->byaddr = pa;
+      pa->p = p;
+      p->spec.sa = *a;
+      a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
+    }
+  }
+  if (err) {
+    if (p) p->st.n_reject++;
+    a_warn("PEER", "?PEER", p, "decrypt-failed",
+          "error-code", "%d", err, A_END);
     return (-1);
   }
   if (!BOK(bout))
@@ -225,23 +299,28 @@ static void p_read(int fd, unsigned mode, void *v)
     return;
   }
 
-  /* --- Find the appropriate peer --- */
+  /* --- Find the appropriate peer --- *
+   *
+   * At this stage, don't worry too much about whether we actually found it.
+   */
 
-  if ((p = p_findbyaddr(&a)) == 0) {
-    a_warn("PEER", "-", "unexpected-source", "?ADDR", &a, A_END);
-    return;
-  }
+  p = p_findbyaddr(&a);
 
   IF_TRACING(T_PEER, {
-    trace(T_PEER, "peer: packet received from `%s'", p->spec.name);
+    if (p) {
+      trace(T_PEER,
+           "peer: packet received from `%s' from address INET %s %d",
+           p_name(p), inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+    } else {
+      trace(T_PEER, "peer: packet received from unknown address INET %s %d",
+           inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+    }
     trace_block(T_PACKET, "peer: packet contents", buf_i, n);
   })
 
   /* --- Pick the packet apart --- */
 
-  p->st.t_last = time(0);
-  p->st.n_in++;
-  p->st.sz_in += n;
+  if (p) p_rxupdstats(p, n);
   buf_init(&b, buf_i, n);
   if ((ch = buf_getbyte(&b)) < 0) {
     a_warn("PEER", "?PEER", p, "bad-packet", "no-type", A_END);
@@ -255,11 +334,11 @@ static void p_read(int fd, unsigned mode, void *v)
               "bad-packet",
               "unknown-type", "0x%02x", ch,
               A_END);
-       p->st.n_reject++;
+       if (p) p->st.n_reject++;
        return;
       }
       buf_init(&bb, buf_o, sizeof(buf_o));
-      if (p_decrypt(p, MSG_PACKET, &b, &bb))
+      if (p_decrypt(&p, &a, n, MSG_PACKET, &b, &bb))
        return;
       if (BOK(&bb)) {
        p->st.n_ipin++;
@@ -271,23 +350,27 @@ static void p_read(int fd, unsigned mode, void *v)
       }
       break;
     case MSG_KEYEXCH:
+      if (!p) goto unexp;
       kx_message(&p->kx, ch & MSG_TYPEMASK, &b);
       break;
     case MSG_MISC:
       switch (ch & MSG_TYPEMASK) {
        case MISC_NOP:
+         if (!p) goto unexp;
          T( trace(T_PEER, "peer: received NOP packet"); )
          break;
        case MISC_PING:
+         if (!p) goto unexp;
          buf_put(p_txstart(p, MSG_MISC | MISC_PONG), BCUR(&b), BLEFT(&b));
          p_txend(p);
          break;
        case MISC_PONG:
+         if (!p) goto unexp;
          p_ponged(p, MISC_PONG, &b);
          break;
        case MISC_EPING:
          buf_init(&bb, buf_t, sizeof(buf_t));
-         if (p_decrypt(p, ch, &b, &bb))
+         if (p_decrypt(&p, &a, n, ch, &b, &bb))
            return;
          if (BOK(&bb)) {
            buf_flip(&bb);
@@ -298,7 +381,7 @@ static void p_read(int fd, unsigned mode, void *v)
          break;
        case MISC_EPONG:
          buf_init(&bb, buf_t, sizeof(buf_t));
-         if (p_decrypt(p, ch, &b, &bb))
+         if (p_decrypt(&p, &a, n, ch, &b, &bb))
            return;
          if (BOK(&bb)) {
            buf_flip(&bb);
@@ -308,13 +391,16 @@ static void p_read(int fd, unsigned mode, void *v)
       }
       break;
     default:
-      p->st.n_reject++;
+      if (p) p->st.n_reject++;
       a_warn("PEER",
             "?PEER", p,
             "bad-packet",
             "unknown-category" "0x%02x", ch,
             A_END);
       break;
+    unexp:
+      a_warn("PEER", "-", "unexpected-source", "?ADDR", &a, A_END);
+      break;
   }
 }
 
@@ -764,6 +850,7 @@ peer *p_create(peerspec *spec)
     a_notify("KXSTART", "?PEER", p, A_END);
     /* Couldn't tell anyone before */
   }
+  if (p->spec.f & PSF_MOBILE) nmobile++;
   return (p);
 
 tidy_4:
@@ -790,7 +877,8 @@ tidy_0:
  * Returns:    A pointer to the peer's name.
  */
 
-const char *p_name(peer *p) { return (p->spec.name); }
+const char *p_name(peer *p)
+  { if (p) return (p->spec.name); else return ("-"); }
 
 /* --- @p_tag@ --- *
  *
@@ -824,8 +912,10 @@ peer *p_findbyaddr(const addr *a)
 {
   peer_byaddr *pa;
 
-  if ((pa = am_find(&byaddr, a, 0, 0)) != 0)
+  if ((pa = am_find(&byaddr, a, 0, 0)) != 0) {
+    assert(pa->p);
     return (pa->p);
+  }
   return (0);
 }
 
@@ -864,6 +954,8 @@ void p_destroy(peer *p)
   a_notify("KILL", "%s", p->spec.name, A_END);
   ksl_free(&p->ks);
   kx_free(&p->kx);
+  if (p->spec.f & PSF_MOBILE)
+    nmobile--;
   if (p->ifname)
     xfree(p->ifname);
   if (p->spec.tag)
index 2142674..5b0fe3b 100644 (file)
@@ -354,6 +354,16 @@ Use the public key
 to authenticate the peer.  The default is to use the key tagged
 .IR peer .
 .TP
+.B "\-mobile"
+The peer is a mobile device, and is likely to change address rapidly.
+If a packet arrives from an unknown address, the server's usual response
+is to log a warning and discard it.  If the server knows of any mobile
+peers, however, it will attempt to decrypt the packet using their keys,
+and if one succeeds, the server will update its idea of the peer's
+address and emit an
+.B NEWADDR
+notification.
+.TP
 .BI "\-tunnel " tunnel
 Use the named tunnel driver, rather than the default.
 .\"-opts
@@ -1082,6 +1092,12 @@ Key exchange with
 has begun or restarted.  If key exchange keeps failing, this message
 will be repeated periodically.
 .SP
+.BI "NEWADDR " peer " " address
+The given mobile
+.IR peer 's
+IP address has been changed to
+.IR address .
+.SP
 .BI "NEWIFNAME " peer " " old-name " " new-name
 The given
 .IR peer 's
index b6c1cd5..8a0be51 100644 (file)
@@ -341,7 +341,8 @@ typedef struct peerspec {
   addr sa;                             /* Socket address to speak to */
   size_t sasz;                         /* Socket address size */
   unsigned f;                          /* Flags for the peer */
-#define PSF_KXMASK 255u                        /*   Key exchange flags to set */
+#define PSF_KXMASK 255u                        /*   Key-exchange flags to set */
+#define PSF_MOBILE 256u                        /*   Address may change rapidly */
 } peerspec;
 
 typedef struct peer_byname {
index ddd2636..d529918 100644 (file)
@@ -174,6 +174,7 @@ The service will submit the command
 .IR time ]
 .RB [ \-key
 .IR tag ]
+.RB [ \-mobile ]
 .RB [ \-tunnel
 .IR driver ]
 .I address
@@ -211,6 +212,18 @@ to the
 key.
 .hP \*o
 The option
+.B \-mobile
+is provided if the peer's database record assigns the
+.B mobile
+key one of the values
+.BR t ,
+.BR true ,
+.BR y ,
+.BR yes,
+or
+.BR on .
+.hP \*o
+The option
 .B \-tunnel
 .I driver
 is provided if the database record assigns a value
index 37241bb..0ae539f 100644 (file)
@@ -87,11 +87,13 @@ def addpeer(peer, addr):
   if peer.name in S.list():
     S.kill(peer.name)
   try:
+    booltrue = ['t', 'true', 'y', 'yes', 'on']
     S.add(peer.name,
           tunnel = peer.get('tunnel', None),
           keepalive = peer.get('keepalive', None),
           key = peer.get('key', None),
-          cork = peer.get('cork', 'nil') in ['t', 'true', 'y', 'yes', 'on'],
+          mobile = peer.get('mobile', 'nil') in booltrue,
+          cork = peer.get('cork', 'nil') in booltrue,
           *addr)
   except T.TripeError, exc:
     raise T.TripeJobError(*exc.args)