chiark / gitweb /
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 413c8abbf531fd41955ca455846b06b96b076acc..4b8dd24fb6d7d8a6fe527af6f4fc61fe41c633f3 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 ad7faeda0fb3087e742978cab4b84df15d2303bb..c8763656b458a242b512155bc93c7c859e53f8e1 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 37de5a404de6aff7f87037aee85ff742db52cb39..cefb667f0e291c6225a566e76648e16e01c2f3ae 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 df8af92b9888bb42a9a82c371601e1a3c8b08410..7fa3f0f85b8cbba34c1a0c0b545b7f975c112ec3 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 ec89f771c35f627d4d65c07ef4b265329c0f0c50..72287cdd6152c6f7cdfa82a103f48e586586a093 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 21426747a5f1e33ec9a6debb5a3c9d4f904c3f4e..5b0fe3bef03ed66a46ebc810a11c4e7a374d37e0 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 b6c1cd5d4fb6da0b17103e1b8b21439e687fe044..8a0be5188f8026b3eeca9dcc064983125ddf9ae7 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 ddd2636b0ce6716d4bcc5b70db5d60ef1340cccb..d529918d887958d90df9e9bda9e781ff476a16fe 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 37241bbce9fcd2b37c231b6b03b190b8df562800..0ae539f5123c4b46ad9f520d71e44d63f6c3b0ee 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)