[quagga-users 6721] Re: md5qd (fwd)

Chris Caputo ccaputo at alt.net
Fri Apr 7 05:36:21 IST 2006


On Mon, 20 Mar 2006, Paul Jakma wrote:
> If anyone is interested in md5qd, I cleaned it up a bit more this 
> morning, and put a source RPM together:
> 
> http://hibernia.jakma.org.nyud.net:8090/~paul/rpms/SRPMS/quagga-0.99.3-2006031901.src.rpm
> 
> (please do not remove the nyud.net from that URL ;) ).
> 
> It seems to work between two Linux boxes with recentish kernels. Tcpdump seems
> to think the MD5 sums are valid. I fixed some optimistic handling of TCP
> options. And some of the debug options.
> 
> It'd need a good bit more work UI-wise in order to be integratable though, so
> I'm pretty much going to leave it alone now to sit and slowly bit-rot, unless
> I hear of people actually using it successfully..

Hi.  I think md5qd is really clever.  Thank you for creating it Paul.

The TCP MD5 support in tcpdump 3.9.4 (the latest) is broken per the 
following bug:

 http://sourceforge.net/tracker/?group_id=53066&atid=469573&func=detail&aid=1298259

As a result, tcpdump 3.9.4 reports the exact opposite of what it should.  
Valid checksums are reported as invalid and vice versa.  I recommend 
applying the patch mentioned in the bug to help with further diagnosis.

The below patch fixes some remaining issues needed to make md5qd 
compatible with Cisco routers and routers based on Hasso's RFC2385 linux 
kernel/quagga patches.

The main unresolved issue I have right now is I believe the code which 
reduces TCPOPT_MAXSEG by 20 bytes, to accommodate for the MD5 option, 
doesn't actually do anything.  It sends out a lower MSS as part of a SYN, 
but only the other side of a connection will honor it.  The other side 
sends back its own MSS preference which will be honored, but it may not 
ensure enough room in packets sent by the local side.  I have commented it 
out.

This is a shame because md5qd is independent of bgpd at the moment, but I 
believe bgpd needs to do a getsockopt/setsockopt to reduce TCP_MAXSEG by 
20, when MD5 is configured for a peer.  I have not coded that up because I 
don't know how you envision the interface for this to evolve.

If talking with a Cisco, this is not a problem because Cisco announces a 
reduced MSS.  Not sure about other commercial routers.

The linux kernel RFC2385 patch does not properly announce a reduced MSS so 
interoperability there is not certain.

Also, quagga to quagga sessions will not be stable until the TCP_MAXSEG 
reduction is employed.  Simple sessions should come up, but if full 
packets are sent, I am not sure what will happen when the MD5 option is 
added to an already full packet.

Chris

--- md5qd/md5qd.c.original	2006-03-20 00:48:47.000000000 +0000
+++ md5qd/md5qd.c	2006-04-07 04:29:47.000000000 +0000
@@ -39,7 +39,7 @@
 
 #define _LINUX_IF_H
 #include <linux/netfilter.h>
-#include <libipq.h>
+#include <libipq/libipq.h>
 
 /* md5qd options. */
 static struct option longopts[] = 
@@ -278,6 +278,9 @@
         {
           case TCPOPT_EOL:
             return nextfree;
+          case TCPOPT_NOP:
+            bytes++;
+            break;
           case TCPOPT_MAXSEG:
             if ( ((len - bytes) < TCPOLEN_MAXSEG)
                 || (from[bytes+1] != TCPOLEN_MAXSEG)  )
@@ -294,7 +297,7 @@
             memcpy (&mslot->data, (from + bytes + 2), 2);
             
             /* reduce MSS by sizeof(5 slots) to compensate for TCP-MD5 */
-            mslot->data = htons(ntohs(mslot->data)-20);
+            // mslot->data = htons(ntohs(mslot->data)-20);
             bytes += TCPOLEN_MAXSEG;
             break;
           case TCPOPT_WINDOW:
@@ -332,9 +335,13 @@
             memcpy ((tslot + 2), &from[bytes], TCPOLEN_TIMESTAMP);
             bytes += TCPOLEN_TIMESTAMP;
             break;
-          case TCPOPT_NOP:
           default:
-            bytes++;
+	    /* Per IANA, all options besides EOL and NOP have "their one octet
+	     * kind field, followed by a one octet length field, followed by
+	     * length-2 octets of option data."
+	     */
+            bytes += from[bytes+1];
+            break;
         }
     }
   return nextfree;
@@ -357,6 +364,7 @@
   uint16_t tcpseglen, nseglen, tmp, data_offset;
   size_t data_size, optslots;
   MD5_CTX ctx;
+  char zero_pad = 0;
   
   tcph = (struct tcphdr *)(m->payload + (4 * iph->ip_hl));
   
@@ -462,13 +470,14 @@
   /* TCP pseudo-header */
   MD5Update (&ctx, &iph->ip_src.s_addr, sizeof (in_addr_t));
   MD5Update (&ctx, &iph->ip_dst.s_addr, sizeof (in_addr_t));
+  MD5Update (&ctx, &zero_pad, sizeof(zero_pad));
   MD5Update (&ctx, &iph->ip_p, 1);
   nseglen = htons (tcpseglen);
   MD5Update (&ctx, &nseglen, sizeof(nseglen));
   /* TCP header, static section, sans options */
   MD5Update (&ctx, tcph, sizeof(struct tcphdr));
   /* Segment data */
-  if (data_offset > m->data_len)
+  if (data_offset < m->data_len)
     MD5Update (&ctx, iov[4].iov_base, iov[4].iov_len);
   /* password */
   MD5Update (&ctx, hc->password, strlen (hc->password));
@@ -521,6 +530,51 @@
   return;
 }
 
+static u_char *
+md5q_find_md5(u_char *ptr, u_char *options_end)
+{
+  int length = options_end - ptr;
+  u_char *ret = NULL;
+
+  zlog_debug ("%s: length = %d", __func__, length);
+
+  if (length < TCPMD5_OPT_SIZE)
+    goto done_opts;
+
+  /* code borrowed from linux kernel RFC2385 patch tcp_v4_inbound_md5_hash() */
+  while (length > 0)
+    {
+      int opcode = *ptr++;
+      int opsize;
+
+      switch (opcode)
+	{
+	case TCPOPT_EOL:
+	  goto done_opts;
+	case TCPOPT_NOP:
+	  length--;
+	  continue;
+	default:
+	  opsize = *ptr++;
+	  if (opsize < 2)
+	    goto done_opts;
+	  if (opsize > length)
+	    goto done_opts;
+	  if (opcode == TCPOPT_MD5)
+	    {
+	      ret = ptr;
+	      goto done_opts;
+	    }
+	  ptr += opsize - 2;
+	  length -= opsize;
+	  break;
+	}
+    }
+
+ done_opts:
+  return ret;
+}
+
 static int
 md5q_pkt_verify (struct md5q_thread *md5qt, struct host_config *hc)
 {
@@ -532,6 +586,7 @@
   u_char digest[16];
   u_char *authdata;
   MD5_CTX ctx;
+  char zero_pad = 0;
   
   if (m->data_len < ((4 * iph->ip_hl) + sizeof (struct tcphdr)))
     {
@@ -552,32 +607,21 @@
   
   if (debug_packet)
     DUMPBYTES ("original packet", m->payload, m->data_len);
-  
-  /* Find the tcp MD5 authentication data */
-  authdata = (m->payload + 4*iph->ip_hl + sizeof(struct tcphdr));
-  
-  while (authdata < (m->payload + data_offset - TCPMD5_OPT_SIZE))
-    {
-      if (*authdata == TCPOPT_MD5)
-        break;
-      authdata++;
-    }
-  if (authdata >= (m->payload + data_offset - TCPMD5_OPT_SIZE))
+
+  /* No MD5 on reset packets. */
+  if (1 == tcph->rst)
     {
       if (debug)
-        zlog_debug ("%s: no TCPMD5_opt found", __func__);
-      return NF_DROP;
+        zlog_debug ("%s: RST", __func__);
+      return NF_ACCEPT;
     }
-  assert (*authdata == TCPOPT_MD5);
   
-  if (*(authdata + 1) != TCPMD5_OPT_SIZE)
+  /* Find the tcp MD5 authentication data */
+  if (NULL == (authdata = md5q_find_md5(tcph + 1, m->payload + data_offset)))
     {
-      zlog_warn ("%s: tcpmd5 option found but size wrong, %hhx", __func__,
-                 *(authdata + 1));
+      zlog_err ("%s: MD5 TCP option not found", __func__);
       return NF_DROP;
     }
-  else
-    authdata += 2; /* skip past tcp option kind and bytelen fields */
   
   tcph->check = 0;
   
@@ -586,13 +630,14 @@
   /* TCP pseudo-header */
   MD5Update (&ctx, &iph->ip_src.s_addr, sizeof (in_addr_t));
   MD5Update (&ctx, &iph->ip_dst.s_addr, sizeof (in_addr_t));
+  MD5Update (&ctx, &zero_pad, sizeof(zero_pad));
   MD5Update (&ctx, &iph->ip_p, 1);
   tmp = htons (tcpseglen);
   MD5Update (&ctx, &tmp, sizeof(tmp));
   /* TCP header, static section, sans options */
   MD5Update (&ctx, tcph, sizeof(struct tcphdr));
   /* Segment data */
-  if (data_offset > m->data_len)
+  if (data_offset < m->data_len)
     MD5Update (&ctx, (m->payload + data_offset), data_size);
   /* password */
   MD5Update (&ctx, hc->password, strlen (hc->password));
@@ -612,6 +657,7 @@
   return NF_DROP;
   
 }
+
 static void
 md5q_pkt_process (struct md5q_thread *md5qt)
 {


More information about the Quagga-users mailing list