Fix for bug 1235. Elapsed time is in 10ms units.
[obnox/wireshark/wip.git] / epan / dissectors / packet-dhcpv6.c
index 955b0507b45ded4b54de4f75bd037895407da566..a942cf5b68c9578b9c5320939059ac71cfe3a59e 100644 (file)
@@ -1,5 +1,6 @@
 /* packet-dhpcv6.c
  * Routines for DHCPv6 packet disassembly
+ * Copyright 2004, Nicolas DICHTEL - 6WIND - <nicolas.dichtel@6wind.com>
  * Jun-ichiro itojun Hagino <itojun@iijlab.net>
  * IItom Tsutomu MIENO <iitom@utouto.com>
  * SHIRASAKI Yasuhiro <yasuhiro@gnome.gr.jp>
@@ -8,17 +9,20 @@
  * $Id$
  *
  * The information used comes from:
- * RFC3315.txt
- * RFC3319.txt
- * RFC3633.txt
- * RFC3646.txt
- * draft-ietf-dhc-dhcpv6-opt-nisconfig-02.txt
- * draft-ietf-dhc-dhcpv6-opt-timeconfig-02.txt
+ * RFC3315.txt (DHCPv6)
+ * RFC3319.txt (SIP options)
+ * RFC3633.txt (Prefix options)
+ * RFC3646.txt (DNS servers/domains)
+ * RFC3898.txt (NIS options)
+ * draft-ietf-dhc-dhcpv6-opt-timeconfig-03.txt
+ * draft-ietf-dhc-dhcpv6-opt-fqdn-00.txt
+ * draft-ietf-dhc-dhcpv6-opt-lifetime-00.txt
+ *
  * Note that protocol constants are still subject to change, based on IANA
  * assignment decisions.
  *
- * Ethereal - Network traffic analyzer
- * By Gerald Combs <gerald@ethereal.com>
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
  * Copyright 1998 Gerald Combs
  *
  * This program is free software; you can redistribute it and/or
 #include <string.h>
 #include <glib.h>
 #include <epan/packet.h>
-#include <epan/ipv6-utils.h>
+#include "packet-arp.h"
 
 static int proto_dhcpv6 = -1;
 static int hf_dhcpv6_msgtype = -1;
+static int hf_fqdn_1 = -1;
+static int hf_fqdn_2 = -1;
+static int hf_fqdn_3 = -1;
+static int hf_fqdn_4 = -1;
 
-static guint ett_dhcpv6 = -1;
-static guint ett_dhcpv6_option = -1;
+static gint ett_dhcpv6 = -1;
+static gint ett_dhcpv6_option = -1;
 
 #define UDP_PORT_DHCPV6_DOWNSTREAM     546
 #define UDP_PORT_DHCPV6_UPSTREAM       547
@@ -96,22 +104,33 @@ static guint ett_dhcpv6_option = -1;
 #define        OPTION_DOMAIN_LIST      24
 #define        OPTION_IA_PD            25
 #define        OPTION_IAPREFIX         26
+#define OPTION_NIS_SERVERS     27
+#define OPTION_NISP_SERVERS    28
+#define OPTION_NIS_DOMAIN_NAME  29
+#define OPTION_NISP_DOMAIN_NAME 30
 
 /*
  * The followings are unassigned numbers.
  */
-#define OPTION_NIS_SERVERS     35
-#define OPTION_NISP_SERVERS    36
-#define OPTION_NIS_DOMAIN_NAME  37
-#define OPTION_NISP_DOMAIN_NAME 38
-#define OPTION_NTP_SERVERS     40
+#define OPTION_CLIENT_FQDN      34
+#define OPTION_SNTP_SERVERS    40
 #define OPTION_TIME_ZONE       41
+#define OPTION_LIFETIME         42
+
+/* temporary value until defined by IETF */
+#define OPTION_MIP6_HA         165
+#define OPTION_MIP6_HOA                166
+#define OPTION_NAI             167
 
 #define        DUID_LLT                1
 #define        DUID_EN                 2
 #define        DUID_LL                 3
 #define        DUID_LL_OLD             4
 
+static void
+dissect_dhcpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
+    gboolean downstream, int off, int eoff);
+
 static const value_string msgtype_vals[] = {
        { SOLICIT,      "Solicit" },
        { ADVERTISE,    "Advertise" },
@@ -132,8 +151,8 @@ static const value_string msgtype_vals[] = {
 static const value_string opttype_vals[] = {
        { OPTION_CLIENTID,      "Client Identifier" },
        { OPTION_SERVERID,      "Server Identifier" },
-       { OPTION_IA_NA,         "Identify Association" },
-       { OPTION_IA_TA,         "Identify Association for Temporary Address" },
+       { OPTION_IA_NA,         "Identity Association for Non-temporary Address" },
+       { OPTION_IA_TA,         "Identity Association for Temporary Address" },
        { OPTION_IAADDR,        "IA Address" },
        { OPTION_ORO,           "Option Request" },
        { OPTION_PREFERENCE,    "Preference" },
@@ -154,14 +173,19 @@ static const value_string opttype_vals[] = {
        { OPTION_SIP_SERVER_A,  "SIP Servers IPv6 Address List" },
        { OPTION_DNS_SERVERS,   "DNS recursive name server" },
        { OPTION_DOMAIN_LIST,   "Domain Search List" },
-       { OPTION_IA_PD,         "Identify Association for Prefix Delegation" },
+       { OPTION_IA_PD,         "Identity Association for Prefix Delegation" },
        { OPTION_IAPREFIX,      "IA Prefix" },
        { OPTION_NIS_SERVERS,   "Network Information Server" },
        { OPTION_NISP_SERVERS,  "Network Information Server V2" },
        { OPTION_NIS_DOMAIN_NAME, "Network Information Server Domain Name" },
        { OPTION_NISP_DOMAIN_NAME,"Network Information Server V2 Domain Name" },
-       { OPTION_NTP_SERVERS,   "Network Time Protocol Server" },
+       { OPTION_SNTP_SERVERS,  "Simple Network Time Protocol Server" },
        { OPTION_TIME_ZONE,     "Time zone" },
+       { OPTION_LIFETIME,      "Lifetime" },
+       { OPTION_CLIENT_FQDN,   "Fully Qualified Domain Name" },
+       { OPTION_MIP6_HA,       "Mobile IPv6 Home Agent" },
+       { OPTION_MIP6_HOA,      "Mobile IPv6 Home Address" },
+       { OPTION_NAI,           "Network Access Identifier" },
        { 0,    NULL }
 };
 
@@ -186,13 +210,89 @@ static const value_string duidtype_vals[] =
        { 0, NULL }
 };
 
+/* This FQDN draft is a mess, I've tried to understand, 
+   but N,O,S bit descriptions are really cryptic */
+static const true_false_string fqdn_n = {
+/*    "Client doesn't want server to perform DNS update", "" */
+    "N bit set","N bit cleared"
+};
+
+static const true_false_string fqdn_o = {
+    "O bit set", "O bit cleared" 
+};
+
+static const true_false_string fqdn_s = {
+/*    "Forward mapping (FQDN-to-IPv6, AAAA) performed by client", 
+      "Forward mapping (FQDN-to-IPv6, AAAA) performed by server" */
+    "S bit set", "S bit cleared"
+}; 
+
+/* Adds domain */
+static void
+dhcpv6_domain(proto_tree * subtree, tvbuff_t *tvb, int offset, guint16 optlen)
+{
+    int start_offset=offset;
+    char domain[256];
+    int pos;
+    guint8 len;
+
+    pos=0;
+    while(optlen){
+        /* this is the start of the domain name */
+        if(!pos){
+            start_offset=offset;
+        }
+        domain[pos]=0;
+
+        /* read length of the next substring */
+        len = tvb_get_guint8(tvb, offset);
+        offset++;
+        optlen--;
+
+        /* if len==0 and pos>0 we have read an entire domain string */
+        if(!len){
+            if(!pos){
+                /* empty string, this must be an error? */
+                proto_tree_add_text(subtree, tvb, start_offset, offset-start_offset, "Malformed option");
+                return;
+            } else {
+                proto_tree_add_text(subtree, tvb, start_offset, offset-start_offset, "Domain: %s", domain);
+                pos=0;
+                continue;
+            }
+        }
+
+        /* add the substring to domain */
+        if(pos){
+            domain[pos]='.';
+            pos++;
+        }
+        if(pos+len>254){
+                /* too long string, this must be an error? */
+                proto_tree_add_text(subtree, tvb, start_offset, offset-start_offset, "Malformed option");
+                return;
+        }
+        tvb_memcpy(tvb, domain+pos, offset, len);
+        pos+=len;
+        offset+=len;
+        optlen-=len;
+    }        
+    
+    if(pos){
+        domain[pos]=0;
+        proto_tree_add_text(subtree, tvb, start_offset, offset-start_offset, "Domain: %s", domain);
+    }
+}    
+
 /* Returns the number of bytes consumed by this option. */
 static int
-dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
-    gboolean *at_end)
+dhcpv6_option(tvbuff_t *tvb, packet_info *pinfo, proto_tree *bp_tree, 
+               gboolean downstream, int off, int eoff, gboolean *at_end)
 {
+       guint8 *buf;
        guint16 opttype;
        guint16 optlen;
+       guint16 hwtype;
        guint16 temp_optlen = 0;
        proto_item *ti;
        proto_tree *subtree;
@@ -245,15 +345,18 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                                        optlen, "DUID: malformed option");
                                break;
                        }
-                       /* XXX seconds since Jan 1 2000 */
+                       hwtype=tvb_get_ntohs(tvb, off + 2);
                        proto_tree_add_text(subtree, tvb, off + 2, 2,
-                               "Hardware type: %u",
-                               tvb_get_ntohs(tvb, off + 2));
+                               "Hardware type: %s (%u)",
+                               arphrdtype_to_str(hwtype, "Unknown"),
+                               hwtype);
+                       /* XXX seconds since Jan 1 2000 */
                        proto_tree_add_text(subtree, tvb, off + 4, 4,
                                "Time: %u", tvb_get_ntohl(tvb, off + 4));
                        if (optlen > 8) {
                                proto_tree_add_text(subtree, tvb, off + 8,
-                                       optlen - 8, "Link-layer address");
+                                       optlen - 8, "Link-layer address: %s",
+                                       arphrdaddr_to_str(tvb_get_ptr(tvb, off+8, optlen-8), optlen-8, hwtype));
                        }
                        break;
                case DUID_EN:
@@ -276,12 +379,15 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                                        optlen, "DUID: malformed option");
                                break;
                        }
+                       hwtype=tvb_get_ntohs(tvb, off + 2);
                        proto_tree_add_text(subtree, tvb, off + 2, 2,
-                               "Hardware type: %u",
-                               tvb_get_ntohs(tvb, off + 2));
+                               "Hardware type: %s (%u)",
+                               arphrdtype_to_str(hwtype, "Unknown"),
+                               hwtype);
                        if (optlen > 4) {
                                proto_tree_add_text(subtree, tvb, off + 4,
-                                       optlen - 4, "Link-layer address");
+                                       optlen - 4, "Link-layer address: %s",
+                                       arphrdaddr_to_str(tvb_get_ptr(tvb, off+4, optlen-4), optlen-4, hwtype));
                        }
                        break;
                }
@@ -300,15 +406,26 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
          proto_tree_add_text(subtree, tvb, off, 4,
                              "IAID: %u",
                              tvb_get_ntohl(tvb, off));
-         proto_tree_add_text(subtree, tvb, off+4, 4,
-                             "T1: %u", tvb_get_ntohl(tvb, off+4));
-         proto_tree_add_text(subtree, tvb, off+8, 4,
-                             "T2: %u", tvb_get_ntohl(tvb, off+8));
+         if (tvb_get_ntohl(tvb, off+4) == DHCPV6_LEASEDURATION_INFINITY) {
+             proto_tree_add_text(subtree, tvb, off+4, 4,
+                                 "T1: infinity");
+         } else {
+             proto_tree_add_text(subtree, tvb, off+4, 4,
+                                 "T1: %u", tvb_get_ntohl(tvb, off+4));
+         }
+
+         if (tvb_get_ntohl(tvb, off+8) == DHCPV6_LEASEDURATION_INFINITY) {
+             proto_tree_add_text(subtree, tvb, off+8, 4,
+                                 "T2: infinity");
+         } else {
+             proto_tree_add_text(subtree, tvb, off+8, 4,
+                                 "T2: %u", tvb_get_ntohl(tvb, off+8));
+         }
 
           temp_optlen = 12;
          while ((optlen - temp_optlen) > 0) {
-           temp_optlen += dhcpv6_option(tvb, subtree, off+temp_optlen,
-                                        off + optlen, at_end);
+           temp_optlen += dhcpv6_option(tvb, pinfo, subtree, downstream,
+                           off+temp_optlen, off + optlen, at_end);
            if (*at_end) {
              /* Bad option - just skip to the end */
              temp_optlen = optlen;
@@ -326,8 +443,8 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                              tvb_get_ntohl(tvb, off));
           temp_optlen = 4;
          while ((optlen - temp_optlen) > 0) {
-           temp_optlen += dhcpv6_option(tvb, subtree, off+temp_optlen,
-                                        off + optlen, at_end);
+           temp_optlen += dhcpv6_option(tvb, pinfo, subtree, downstream,
+                           off+temp_optlen, off + optlen, at_end);
            if (*at_end) {
              /* Bad option - just skip to the end */
              temp_optlen = optlen;
@@ -343,7 +460,7 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                                   optlen, "IAADDR: malformed option");
               break;
            }
-           tvb_memcpy(tvb, (guint8 *)&in6, off, sizeof(in6));
+           tvb_get_ipv6(tvb, off, &in6);
            proto_tree_add_text(subtree, tvb, off,
                                sizeof(in6), "IPv6 address: %s",
                                ip6_to_str(&in6));
@@ -368,8 +485,8 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
            
            temp_optlen = 24;
            while ((optlen - temp_optlen) > 0) {
-              temp_optlen += dhcpv6_option(tvb, subtree, off+temp_optlen,
-                                           off + optlen, at_end);
+              temp_optlen += dhcpv6_option(tvb, pinfo, subtree, downstream,
+                             off+temp_optlen, off + optlen, at_end);
               if (*at_end) {
                 /* Bad option - just skip to the end */
                 temp_optlen = optlen;
@@ -406,24 +523,20 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
            break;
          }
          proto_tree_add_text(subtree, tvb, off, 2,
-                             "elapsed-time: %d sec",
-                             (guint32)tvb_get_ntohs(tvb, off));
+                             "elapsed-time: %u ms",
+                             10*(guint32)tvb_get_ntohs(tvb, off));
          break;
        case OPTION_RELAY_MSG:
          if (optlen == 0) {
            proto_tree_add_text(subtree, tvb, off,
                                optlen, "RELAY-MSG: malformed option");
-           break;
          } else {
-           /* XXX - shouldn't we be dissecting a full DHCP message
-              here? */
-           dhcpv6_option(tvb, subtree, off, off + optlen, at_end);
-           if (*at_end)
-             return 0;
+           /* here, we should dissect a full DHCP message */
+           dissect_dhcpv6(tvb, pinfo, subtree, downstream, off, off + optlen);
           } 
          break;
        case OPTION_AUTH:
-         if (optlen < 15) {
+         if (optlen < 11) {
            proto_tree_add_text(subtree, tvb, off,
                                optlen, "AUTH: malformed option");
            break;
@@ -438,9 +551,10 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                              "RDM: %d",
                              (guint32)tvb_get_guint8(tvb, off+2));
          proto_tree_add_text(subtree, tvb, off+3, 8,
-                             "Reply Detection");
-         proto_tree_add_text(subtree, tvb, off+11, optlen-11,
-                             "Authentication Information");
+                             "Replay Detection");
+         if (optlen != 11)
+               proto_tree_add_text(subtree, tvb, off+11, optlen-11,
+                                                       "Authentication Information");
          break;
        case OPTION_UNICAST:
          if (optlen != 16) {
@@ -448,7 +562,7 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                                optlen, "UNICAST: malformed option");
            break;
          }
-         tvb_memcpy(tvb, (guint8 *)&in6, off, sizeof(in6));
+         tvb_get_ipv6(tvb, off, &in6);
          proto_tree_add_text(subtree, tvb, off,
                              sizeof(in6), "IPv6 address: %s",
                                ip6_to_str(&in6));
@@ -465,11 +579,10 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                                    status_code);
 
                if (optlen - 2 > 0) {
-                   status_message = tvb_get_string(tvb, off + 2, optlen - 2);
+                   status_message = tvb_get_ephemeral_string(tvb, off + 2, optlen - 2);
                    proto_tree_add_text(subtree, tvb, off + 2, optlen - 2,
                                        "Status Message: %s",
                                        status_message);
-                   g_free(status_message);
                }
            }
            break;
@@ -526,6 +639,8 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                        proto_tree_add_text(subtree, tvb, off, optlen,
                                "SIP Servers Domain Search List");
                }
+               dhcpv6_domain(subtree,tvb, off, optlen);
+               break;
        case OPTION_SIP_SERVER_A:
                if (optlen % 16) {
                        proto_tree_add_text(subtree, tvb, off, optlen,
@@ -533,7 +648,7 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                        break;
                }
                for (i = 0; i < optlen; i += 16) {
-                       tvb_memcpy(tvb, (guint8 *)&in6, off + i, sizeof(in6));
+                       tvb_get_ipv6(tvb, off + i, &in6);
                        proto_tree_add_text(subtree, tvb, off + i,
                                sizeof(in6), "SIP servers address: %s",
                                ip6_to_str(&in6));
@@ -546,7 +661,7 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                        break;
                }
                for (i = 0; i < optlen; i += 16) {
-                       tvb_memcpy(tvb, (guint8 *)&in6, off + i, sizeof(in6));
+                       tvb_get_ipv6(tvb, off + i, &in6);
                        proto_tree_add_text(subtree, tvb, off + i,
                                sizeof(in6), "DNS servers address: %s",
                                ip6_to_str(&in6));
@@ -556,6 +671,7 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
          if (optlen > 0) {
            proto_tree_add_text(subtree, tvb, off, optlen, "DNS Domain Search List");
          }
+         dhcpv6_domain(subtree,tvb, off, optlen);
          break;
        case OPTION_NIS_SERVERS:
                if (optlen % 16) {
@@ -564,7 +680,7 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                        break;
                }
                for (i = 0; i < optlen; i += 16) {
-                       tvb_memcpy(tvb, (guint8 *)&in6, off + i, sizeof(in6));
+                       tvb_get_ipv6(tvb, off + i, &in6);
                        proto_tree_add_text(subtree, tvb, off + i,
                                sizeof(in6), "NIS servers address: %s",
                                ip6_to_str(&in6));
@@ -577,7 +693,7 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                        break;
                }
                for (i = 0; i < optlen; i += 16) {
-                       tvb_memcpy(tvb, (guint8 *)&in6, off + i, sizeof(in6));
+                       tvb_get_ipv6(tvb, off + i, &in6);
                        proto_tree_add_text(subtree, tvb, off + i,
                                sizeof(in6), "NISP servers address: %s",
                                ip6_to_str(&in6));
@@ -587,30 +703,64 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
          if (optlen > 0) {
            proto_tree_add_text(subtree, tvb, off, optlen, "nis-domain-name");
          }
+         dhcpv6_domain(subtree,tvb, off, optlen);
          break;
        case OPTION_NISP_DOMAIN_NAME:
          if (optlen > 0) {
            proto_tree_add_text(subtree, tvb, off, optlen, "nisp-domain-name");
          }
+         dhcpv6_domain(subtree,tvb, off, optlen);
          break;
-       case OPTION_NTP_SERVERS:
+       case OPTION_SNTP_SERVERS:
                if (optlen % 16) {
                        proto_tree_add_text(subtree, tvb, off, optlen,
-                               "NTP servers address: malformed option");
+                               "SNTP servers address: malformed option");
                        break;
                }
                for (i = 0; i < optlen; i += 16) {
-                       tvb_memcpy(tvb, (guint8 *)&in6, off + i, sizeof(in6));
+                       tvb_get_ipv6(tvb, off + i, &in6);
                        proto_tree_add_text(subtree, tvb, off + i,
-                               sizeof(in6), "NTP servers address: %s",
+                               sizeof(in6), "SNTP servers address: %s",
                                ip6_to_str(&in6));
                }
                break;
        case OPTION_TIME_ZONE:
          if (optlen > 0) {
-           proto_tree_add_text(subtree, tvb, off, optlen, "time-zone");
+             buf = tvb_get_ephemeral_string(tvb, off, optlen);
+             proto_tree_add_text(subtree, tvb, off, optlen, "time-zone: %s", buf);
+         }
+         break;
+       case OPTION_LIFETIME:
+         if (optlen != 4) {
+           proto_tree_add_text(subtree, tvb, off,
+                               optlen, "LIFETIME: malformed option");
+           break;
+         }
+         proto_tree_add_text(subtree, tvb, off, 4,
+                             "Lifetime: %d",
+                             (guint32)tvb_get_ntohl(tvb, off));
+         break;
+       case OPTION_CLIENT_FQDN:
+         if (optlen < 1) {
+           proto_tree_add_text(subtree, tvb, off,
+                               optlen, "FQDN: malformed option");
+           break;
          }
+         /*
+          * +-----+-+-+-+
+          * | MBZ |N|O|S|
+          * +-----+-+-+-+
+          */
+         proto_tree_add_item(subtree, hf_fqdn_1, tvb, off, 1, FALSE);
+         proto_tree_add_item(subtree, hf_fqdn_2, tvb, off, 1, FALSE);
+         proto_tree_add_item(subtree, hf_fqdn_3, tvb, off, 1, FALSE);
+         proto_tree_add_item(subtree, hf_fqdn_4, tvb, off, 1, FALSE);
+/*       proto_tree_add_text(subtree, tvb, off, 1, */
+/*                           "flags: %d", */
+/*                           (guint32)tvb_get_guint8(tvb, off)); */
+         dhcpv6_domain(subtree,tvb, off+1, (guint16) (optlen-1));
          break;
+
        case OPTION_IAPREFIX:
            {
                guint32 preferred_lifetime, valid_lifetime;
@@ -642,15 +792,15 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                }
                proto_tree_add_text(subtree, tvb, off + 8, 1,
                                    "Prefix length: %d", prefix_length);
-               tvb_memcpy(tvb, (guint8 *)&in6, off + 9 , sizeof(in6));
+               tvb_get_ipv6(tvb, off + 9, &in6);
                proto_tree_add_text(subtree, tvb, off + 9,
                                    16, "Prefix address: %s",
                                    ip6_to_str(&in6));
                 
                 temp_optlen = 25;
                 while ((optlen - temp_optlen) > 0) {
-                   temp_optlen += dhcpv6_option(tvb, subtree, off+temp_optlen,
-                                                off + optlen, at_end);
+                   temp_optlen += dhcpv6_option(tvb, pinfo, subtree, downstream,
+                                  off+temp_optlen, off + optlen, at_end);
                    if (*at_end) {
                      /* Bad option - just skip to the end */
                      temp_optlen = optlen;
@@ -658,6 +808,37 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
                 }
            }
            break;
+       case OPTION_MIP6_HA:
+               if (optlen != 16) {
+                       proto_tree_add_text(subtree, tvb, off, optlen,
+                               "MIP6_HA: malformed option");
+                       break;
+               }
+
+               tvb_get_ipv6(tvb, off, &in6);
+               proto_tree_add_text(subtree, tvb, off,
+                       16, "Home Agent: %s", ip6_to_str(&in6));
+               break;
+       case OPTION_MIP6_HOA:
+               if (optlen != 16) {
+                       proto_tree_add_text(subtree, tvb, off, optlen,
+                               "MIP6_HOA: malformed option");
+                       break;
+               }
+
+               tvb_get_ipv6(tvb, off, &in6);
+               proto_tree_add_text(subtree, tvb, off,
+                       16, "Home Address: %s", ip6_to_str(&in6));
+               break;
+       case OPTION_NAI:
+               if (optlen < 4) {
+                       proto_tree_add_text(subtree, tvb, off, optlen,
+                               "NAI: malformed option");
+                       break;
+               }
+               proto_tree_add_text(subtree, tvb, off, optlen,
+                       "NAI : %s", tvb_get_ptr(tvb, off, optlen - 2));
+               break;
        }
 
        return 4 + optlen;
@@ -666,25 +847,16 @@ dhcpv6_option(tvbuff_t *tvb, proto_tree *bp_tree, int off, int eoff,
 
 static void
 dissect_dhcpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
-    gboolean downstream)
+    gboolean downstream, int off, int eoff)
 {
        proto_tree *bp_tree = NULL;
        proto_item *ti;
        guint8 msgtype, hop_count ;
        guint32 xid;
-       int off = 0;
-        int eoff;
        struct e_in6_addr in6;
        gboolean at_end;
-        gboolean relay_msg_option = FALSE;
-        int length;
 
-       eoff = tvb_reported_length(tvb);
        downstream = 0; /* feature reserved */
-       if (check_col(pinfo->cinfo, COL_PROTOCOL))
-               col_set_str(pinfo->cinfo, COL_PROTOCOL, "DHCPv6");
-       if (check_col(pinfo->cinfo, COL_INFO))
-               col_clear(pinfo->cinfo, COL_INFO);
 
        msgtype = tvb_get_guint8(tvb, off);
 
@@ -693,92 +865,81 @@ dissect_dhcpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
                bp_tree = proto_item_add_subtree(ti, ett_dhcpv6);
         }
 
-        while (msgtype == RELAY_FORW || msgtype == RELAY_REPLY) {
+        if (msgtype == RELAY_FORW || msgtype == RELAY_REPLY) {
            
-           if (check_col(pinfo->cinfo, COL_INFO)) {
-              col_set_str(pinfo->cinfo, COL_INFO,
-                          val_to_str(msgtype,
-                                     msgtype_vals,
-                                     "Message Type %u"));
-           }
+           if (!off) {
+              if (check_col(pinfo->cinfo, COL_INFO)) {
+                 col_set_str(pinfo->cinfo, COL_INFO,
+                             val_to_str(msgtype,
+                                        msgtype_vals,
+                                        "Message Type %u"));
+              }
+          }
 
            proto_tree_add_uint(bp_tree, hf_dhcpv6_msgtype, tvb, off, 1, msgtype);
 
            hop_count = tvb_get_guint8(tvb, off+1);
            proto_tree_add_text(bp_tree, tvb, off+1, 1, "Hop count: %d", hop_count);
 
-           tvb_memcpy(tvb, (guint8 *)&in6, off+2, sizeof(in6));
+           tvb_get_ipv6(tvb, off+2, &in6);
            proto_tree_add_text(bp_tree, tvb, off+2, sizeof(in6), 
                                "Link-address: %s",ip6_to_str(&in6));
 
-           tvb_memcpy(tvb, (guint8 *)&in6, off+18, sizeof(in6));
+           tvb_get_ipv6(tvb, off+18, &in6);
            proto_tree_add_text(bp_tree, tvb, off+18, sizeof(in6), 
                                "Peer-address: %s",ip6_to_str(&in6));
 
            off += 34;
-           relay_msg_option = FALSE;
-
-           while (!relay_msg_option && off < eoff) {
-              length = dhcpv6_option(tvb, bp_tree, off, eoff, &at_end);
-              if (at_end)
-                return;
-
-              if (tvb_get_ntohs(tvb, off) == OPTION_RELAY_MSG) {
-                 relay_msg_option = TRUE;
-                 off += 4;
-              }
-              else {
-                 if (length > 0)
-                    off += length;
-                 else {
-                    proto_tree_add_text(bp_tree, tvb, off, eoff, "Message: malformed");
-                    return;
-                 }
-              }
-           }
-           
-           msgtype = tvb_get_guint8(tvb, off);
-        }
+        } else {
         
-       xid = tvb_get_ntohl(tvb, off) & 0x00ffffff;
-
-        if (!off) {
-           if (check_col(pinfo->cinfo, COL_INFO)) {
-              col_set_str(pinfo->cinfo, COL_INFO,
-                          val_to_str(msgtype,
-                                     msgtype_vals,
-                                     "Message Type %u"));
+          xid = tvb_get_ntohl(tvb, off) & 0x00ffffff;
+
+           if (!off) {
+              if (check_col(pinfo->cinfo, COL_INFO)) {
+                 col_set_str(pinfo->cinfo, COL_INFO,
+                             val_to_str(msgtype,
+                                        msgtype_vals,
+                                        "Message Type %u"));
+              }
            }
-        }
 
-       if (tree) {
-               proto_tree_add_uint(bp_tree, hf_dhcpv6_msgtype, tvb, off, 1,
-                       msgtype);
-               proto_tree_add_text(bp_tree, tvb, off+1, 3, "Transaction-ID: 0x%08x", xid);
+          if (tree) {
+                  proto_tree_add_uint(bp_tree, hf_dhcpv6_msgtype, tvb, off, 1,
+                          msgtype);
+                  proto_tree_add_text(bp_tree, tvb, off+1, 3, "Transaction-ID: 0x%08x", xid);
 #if 0
-               tvb_memcpy(tvb, (guint8 *)&in6, 4, sizeof(in6));
-               proto_tree_add_text(bp_tree, tvb, 4, sizeof(in6),
-                       "Server address: %s", ip6_to_str(&in6));
+                  tvb_get_ipv6(tvb, 4, &in6);
+                  proto_tree_add_text(bp_tree, tvb, 4, sizeof(in6),
+                          "Server address: %s", ip6_to_str(&in6));
 #endif
-       }
+          }
 
-       off += 4;
+          off += 4;
+       }
 
        at_end = FALSE;
        while (off < eoff && !at_end)
-               off += dhcpv6_option(tvb, bp_tree, off, eoff, &at_end);
+               off += dhcpv6_option(tvb, pinfo, bp_tree, downstream, off, eoff, &at_end);
 }
 
 static void
 dissect_dhcpv6_downstream(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
 {
-       dissect_dhcpv6(tvb, pinfo, tree, TRUE);
+       if (check_col(pinfo->cinfo, COL_PROTOCOL))
+               col_set_str(pinfo->cinfo, COL_PROTOCOL, "DHCPv6");
+       if (check_col(pinfo->cinfo, COL_INFO))
+               col_clear(pinfo->cinfo, COL_INFO);
+       dissect_dhcpv6(tvb, pinfo, tree, TRUE, 0, tvb_reported_length(tvb));
 }
 
 static void
 dissect_dhcpv6_upstream(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
 {
-       dissect_dhcpv6(tvb, pinfo, tree, FALSE);
+       if (check_col(pinfo->cinfo, COL_PROTOCOL))
+               col_set_str(pinfo->cinfo, COL_PROTOCOL, "DHCPv6");
+       if (check_col(pinfo->cinfo, COL_INFO))
+               col_clear(pinfo->cinfo, COL_INFO);
+       dissect_dhcpv6(tvb, pinfo, tree, FALSE, 0, tvb_reported_length(tvb));
 }
 
 
@@ -786,10 +947,20 @@ void
 proto_register_dhcpv6(void)
 {
   static hf_register_info hf[] = {
+
     { &hf_dhcpv6_msgtype,
       { "Message type",                        "dhcpv6.msgtype",        FT_UINT8,
          BASE_DEC,                     VALS(msgtype_vals),   0x0,
        "", HFILL }},
+    { &hf_fqdn_1,
+      { "Reserved", "", FT_UINT8, BASE_HEX, NULL, 0xF8, "", HFILL}},
+    { &hf_fqdn_2,
+      { "N", "dhcpv6.msgtype.n", FT_BOOLEAN, 8, TFS(&fqdn_n), 0x4, "", HFILL}},
+    { &hf_fqdn_3,
+      { "O", "dhcpv6.msgtype.o", FT_BOOLEAN, 8, TFS(&fqdn_o), 0x2, "", HFILL}},
+    { &hf_fqdn_4,
+      { "S", "dhcpv6.msgtype.s", FT_BOOLEAN, 8, TFS(&fqdn_s), 0x1, "", HFILL}}
+    
   };
   static gint *ett[] = {
     &ett_dhcpv6,