Exemple #1
0
static int
dnsReplyId(char *buf, int offset, int n, int *id_return)
{
    if(n - offset < 12)
        return -1;
    DO_NTOHS(*id_return, &buf[offset]);
    return 1;
}
Exemple #2
0
static int
dnsDecodeReply(char *buf, int offset, int n, int *id_return,
               AtomPtr *name_return, AtomPtr *value_return,
               int *af_return, unsigned *ttl_return)
{
    int i = offset, j, m;
    int id = -1, b23, qdcount, ancount, nscount, arcount, rdlength;
    int class, type;
    unsigned int ttl;
    char b[2048];
    int af = -1;
    AtomPtr name = NULL, value;
    char addresses[1024];
    int addr_index = 0;
    int error = EDNS_NO_ADDRESS;
    unsigned final_ttl = 7 * 24 * 3600;
    int dnserror;

    if(n - i < 12) {
        error = EDNS_INVALID;
        goto fail;
    }

    DO_NTOHS(id, &buf[i]); i += 2;
    DO_NTOHS(b23, &buf[i]); i += 2;
    DO_NTOHS(qdcount, &buf[i]); i += 2;
    DO_NTOHS(ancount, &buf[i]); i += 2;
    DO_NTOHS(nscount, &buf[i]); i += 2;
    DO_NTOHS(arcount, &buf[i]); i += 2;

    do_log(D_DNS, 
           "DNS id %d, b23 0x%x, qdcount %d, ancount %d, "
           "nscount %d, arcount %d\n",
           id, b23, qdcount, ancount, nscount, arcount);

    if((b23 & (0xF870)) != 0x8000) {
        do_log(L_ERROR, "Incorrect DNS reply (b23 = 0x%x).\n", b23);
        error = EDNS_INVALID;
        goto fail;
    }

    dnserror = b23 & 0xF;

    if(b23 & 0x200) {
        do_log(L_WARN, "Truncated DNS reply (b23 = 0x%x).\n", b23);
    }

    if(dnserror || qdcount != 1) {
        if(!dnserror)
            do_log(L_ERROR, 
                   "Unexpected number %d of DNS questions.\n", qdcount);
        if(dnserror == 1)
            error = EDNS_FORMAT;
        else if(dnserror == 2)
            error = EDNS_NO_RECOVERY;
        else if(dnserror == 3)
            error = EDNS_HOST_NOT_FOUND;
        else if(dnserror == 4 || dnserror == 5)
            error = EDNS_REFUSED;
        else if(dnserror == 0)
            error = EDNS_INVALID;
        else
            error = EUNKNOWN;
        goto fail;
    }

    /* We do this early, so that we can return the address family to
       the caller in case of error. */
    i = labelsToString(buf, i, n, b, 2048, &m);
    if(i < 0) {
        error = EDNS_FORMAT;
        goto fail;
    }
    DO_NTOHS(type, &buf[i]); i += 2;
    DO_NTOHS(class, &buf[i]); i += 2;

    if(type == 1)
        af = 4;
    else if(type == 28)
        af = 6;
    else {
        error = EDNS_FORMAT;
        goto fail;
    }

    do_log(D_DNS, "DNS q: ");
    do_log_n(D_DNS, b, m);
    do_log(D_DNS, " (%d, %d)\n", type, class);
    name = internAtomLowerN(b, m);
    if(name == NULL) {
        error = ENOMEM;
        goto fail;
    }

    if(class != 1) {
        error = EDNS_FORMAT;
        goto fail;
    }

#define PARSE_ANSWER(kind, label) \
do { \
    i = labelsToString(buf, i, 1024, b, 2048, &m); \
    if(i < 0) goto label; \
    DO_NTOHS(type, &buf[i]); i += 2; if(i > 1024) goto label; \
    DO_NTOHS(class, &buf[i]); i += 2; if(i > 1024) goto label; \
    DO_NTOHL(ttl, &buf[i]); i += 4; if(i > 1024) goto label; \
    DO_NTOHS(rdlength, &buf[i]); i += 2; if(i > 1024) goto label; \
    do_log(D_DNS, "DNS " kind ": "); \
    do_log_n(D_DNS, b, m); \
    do_log(D_DNS, " (%d, %d): %d bytes, ttl %u\n", \
           type, class, rdlength, ttl); \
   } while(0)


    for(j = 0; j < ancount; j++) {
        PARSE_ANSWER("an", fail);
        if(strcasecmp_n(name->string, b, m) == 0) {
            if(class != 1) {
                do_log(D_DNS, "DNS: %s: unknown class %d.\n", 
                       name->string, class);
                error = EDNS_UNSUPPORTED;
                goto cont;
            }
            if(type == 1 || type == 28) {
                if((type == 1 && rdlength != 4) ||
                   (type == 28 && rdlength != 16)) {
                    do_log(L_ERROR, 
                           "DNS: %s: unexpected length %d of %s record.\n",
                           scrub(name->string),
                           rdlength, type == 1 ? "A" : "AAAA");
                    error = EDNS_INVALID;
                    if(rdlength <= 0 || rdlength >= 32)
                        goto fail;
                    goto cont;
                }
                if(af == 0) {
                    do_log(L_WARN, "DNS: %s: host has both A and CNAME -- "
                           "ignoring CNAME.\n", scrub(name->string));
                    addr_index = 0;
                    af = -1;
                }
                if(type == 1) {
                    if(af < 0)
                        af = 4;
                    else if(af == 6) {
                        do_log(L_WARN, "Unexpected AAAA reply.\n");
                        goto cont;
                    }
                } else {
                    if(af < 0)
                        af = 6;
                    else if(af == 4) {
                        do_log(L_WARN, "Unexpected A reply.\n");
                        goto cont;
                    }
                }

                if(addr_index == 0) {
                    addresses[0] = DNS_A;
                    addr_index++;
                } else {
                    if(addr_index > 1000) {
                        error = EDNS_INVALID;
                        goto fail;
                    }
                }
                assert(addresses[0] == DNS_A);
                if(final_ttl > ttl)
                    final_ttl = ttl;
                memset(&addresses[addr_index], 0, sizeof(HostAddressRec));
                if(type == 1) {
                    addresses[addr_index] = 4;
                    memcpy(addresses + addr_index + 1, buf + i, 4);
                } else {
                    addresses[addr_index] = 6;
                    memcpy(addresses + addr_index + 1, buf + i, 16);
                }
                addr_index += sizeof(HostAddressRec);
            } else if(type == 5) {
                int j, k;
                if(af != 0 && addr_index > 0) {
                    do_log(L_WARN, "DNS: host has both CNAME and A -- "
                           "ignoring CNAME.\n");
                    goto cont;
                }
                af = 0;

                if(addr_index != 0) {
                    /* Only warn if the CNAMEs are not identical */
                    char tmp[512]; int jj, kk;
                    assert(addresses[0] == DNS_CNAME);
                    jj = labelsToString(buf, i, n,
                                        tmp, 512, &kk);
                    if(jj < 0 ||
                       kk != strlen(addresses + 1) ||
                       memcmp(addresses + 1, tmp, kk) != 0) {
                        do_log(L_WARN, "DNS: "
                               "%s: host has multiple CNAMEs -- "
                               "ignoring subsequent.\n",
                               scrub(name->string));

                    }
                    goto cont;
                }
                addresses[0] = DNS_CNAME;
                addr_index++;
                j = labelsToString(buf, i, n,
                                   addresses + 1, 1020, &k);
                if(j < 0) {
                    addr_index = 0;
                    error = ENAMETOOLONG;
                    continue;
                }
                addr_index = k + 1;
            } else {
                error = EDNS_NO_ADDRESS;
                i += rdlength;
                continue;
            }

        }
    cont:
        i += rdlength;
    }

#if (LOGGING_MAX & D_DNS)
    for(j = 0; j < nscount; j++) {
        PARSE_ANSWER("ns", nofail);
        i += rdlength;
    }

    for(j = 0; j < arcount; j++) {
        PARSE_ANSWER("ar", nofail);
        i += rdlength;
    }

 nofail:
#endif

#undef PARSE_ANSWER

    do_log(D_DNS, "DNS: %d bytes\n", addr_index);
    if(af < 0)
        goto fail;

    value = internAtomN(addresses, addr_index);
    if(value == NULL) {
        error = ENOMEM;
        goto fail;
    }

    assert(af >= 0);
    *id_return = id;
    *name_return = name;
    *value_return = value;
    *af_return = af;
    *ttl_return = final_ttl;
    return 1;

 fail:
    *id_return = id;
    *name_return = name;
    *value_return = NULL;
    *af_return = af;
    return -error;
}
Exemple #3
0
int
parse_dhcpv6(const unsigned char *buf, int buflen,
             struct prefix_list **dns, struct prefix_list **ntp)
{
    int i = 0;

    while(i < buflen) {
        unsigned const char *tlv = buf + i;
        int type, bodylen;

        if(buflen - i < 4) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        DO_NTOHS(type, tlv);
        DO_NTOHS(bodylen, tlv + 2);
        if(buflen - i < 4 + bodylen) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        switch(type) {
        case 23:
        case 31: {
            int j;
            struct in6_addr addr;
            struct prefix_list *pl;
            debugf(type == 23 ?
                   "         OPTION_DNS_SERVERS " :
                   "         OPTION_SNTP_SERVERS ");
            for(j = 0; j < bodylen / 16; j++) {
                memcpy(&addr, tlv + i + 2 + j * 16, 16);
                debug_address(&addr);
                debugf(" ");
                if(type == 23) {
                    pl = prefix_list_cons(*dns, &addr, 128, NULL, 0, 0);
                    if(pl != NULL)
                        *dns = pl;
                } else {
                    pl = prefix_list_cons(*ntp, &addr, 128, NULL, 0, 0);
                    if(pl != NULL)
                        *ntp = pl;
                }
            }
            debugf("\n");
            break;
        }
        default:
            if(debug_level >= 3)
                debugf("         %d: %d\n", type, bodylen);
            break;
        }
        i += 4 + bodylen;
        i += -i & 3;
    }
    return 1;

 fail:
    return -1;
}
Exemple #4
0
struct external *
parse_external(struct node *node, const unsigned char *buf, int buflen)
{
    int i = 0;
    struct external *ext = calloc(1, sizeof(struct external));
    if(ext == NULL)
        return NULL;

    while(i < buflen) {
        unsigned const char *tlv = buf + i;
        int type, bodylen;

        if(buflen - i < 4) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        DO_NTOHS(type, tlv);
        DO_NTOHS(bodylen, tlv + 2);
        if(buflen - i < 4 + bodylen) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        switch(type) {
        case 34: {
            unsigned char plen;
            struct in6_addr addr;
            struct prefix_list *pl;
            if(bodylen < 9) {
                fprintf(stderr, "Truncated DELEGATED-PREFIX.\n");
                break;
            }
            plen = *(tlv + 12);
            if(bodylen < 9 + (plen + 7) / 8) {
                fprintf(stderr, "Truncated DELEGATED-PREFIX.\n");
                break;
            }
            parse_pref(&addr, tlv + 13, plen);
            debugf("       DELEGATED-PREFIX ");
            debug_prefix_raw(&addr, plen);
            debugf("\n");
            /* XXX parse embedded TLVs. */
            pl = prefix_list_cons(ext->delegated, &addr, plen, node->id, 0, 0);
            if(pl != NULL)
                ext->delegated = pl;
            break;
        }
        case 37: {
            debugf("       DHCPV4-DATA\n");
            parse_dhcpv4(tlv + 4, bodylen, &ext->dns, &ext->ntp);
            data_change_time = now;
            break;
        }
        case 38: {
            debugf("       DHCPV6-DATA\n");
            parse_dhcpv6(tlv + 4, bodylen, &ext->dns, &ext->ntp);
            data_change_time = now;
            break;
        }
        default:
            if(debug_level >= 3)
                debugf("       %d: %d\n", type, bodylen);
            break;
        }
        i += 4 + bodylen;
        i += -i & 3;
    }

    return ext;

 fail:
    destroy_external(ext);
    return NULL;
}
Exemple #5
0
int
parse_packet(const unsigned char *buf, int buflen,
             struct sockaddr_in6 *from, int unicast,
             struct interface *interface)
{
    unsigned char id[4];
    unsigned int eid = 42;      /* silence gcc */
    int have_id = 0;
    int i = 0, rc;
    int recompute = 0;

    if(debug_level >= 3)
        debugf("Received %d bytes.\n", buflen);

    while(i < buflen) {
        unsigned const char *tlv = buf + i;
        int type, bodylen;

        if(buflen - i < 4) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        DO_NTOHS(type, tlv);
        DO_NTOHS(bodylen, tlv + 2);
        if(buflen - i < 4 + bodylen) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        switch(type) {
        case 1: {
            debugf("   REQ-NETWORK-STATE\n");
            int i;
            buffer_network_state(from, NULL);
            for(i = 0; i < numnodes; i++)
                buffer_node_state(&nodes[i], 0, from, NULL);
            break;
        }
        case 2: {
            struct node *node;
            if(bodylen < 4) {
                fprintf(stderr, "Truncated REQ-NODE-STATE\n");
                goto fail;
            }
            debugf("   REQ-NODE-STATE %s\n", format_32(tlv + 4));
            node = find_node(tlv + 4, 0);
            if(node)
                buffer_node_state(node, 1, from, NULL);
            break;
        }
        case 3: {
            struct neighbour *neigh;
            if(bodylen < 8) {
                fprintf(stderr, "Truncated NODE-ENDPOINT.\n");
                break;
            }
            if(have_id) {
                fprintf(stderr, "Duplicate NODE-ENDPOINT.\n");
                break;
            }
            memcpy(id, tlv + 4, 4);
            DO_NTOHL(eid, tlv + 8);
            debugf("   NODE-ENDPOINT %s %u\n", format_32(id), eid);
            if(id_eq(id, myid)) {
                fprintf(stderr, "Node id collision.\n");
                goto fail;
            }
            have_id = 1;
            if(unicast) {
                neigh = find_neighbour(interface, id, eid, from);
                if(neigh && unicast)
                    neigh->last_contact = now;
            }
            break;
        }
        case 4: {
            int rc;
            unsigned char h[8];
            struct neighbour *neigh = NULL;
            struct timespec t;

            if(have_id)
                neigh = find_neighbour(interface, id, eid, NULL);

            if(bodylen < 8) {
                fprintf(stderr, "Truncated NETWORK-STATE.\n");
                goto fail;
            }

            rc = network_hash(h);
            if(rc < 0) {
                fprintf(stderr, "Eek!\n");
                goto fail;
            }

            if(memcmp(h, tlv + 4, 8) == 0) {
                debugf("   NETWORK-STATE %s (consistent)\n",
                       format_64(tlv + 4));
                if(neigh)
                    neigh->last_contact = now;
                trickle_reset(&interface->trickle, 1);
                break;
            }

            debugf("   NETWORK-STATE %s (inconsistent, %s)\n",
                   format_64(tlv + 4), format_64(h));
            /* But don't reset Trickle. */

            ts_add_msec(&t, &interface->last_request_sent, HNCP_I_min);
            if(ts_compare(&now, &t) >= 0) {
                interface->last_request_sent = now;
                debugf("-> REQ-NETWORK-STATE\n");
                buffer_tlv(1, NULL, 0, from, NULL);
            }
            break;
        }
        case 5: {
            struct node *node;
            unsigned int seqno;
            int msecs, mine;
            int datalen = bodylen - 20;

            if(bodylen < 20) {
                fprintf(stderr, "Truncated NODE-STATE.\n");
                goto fail;
            }

            if(!have_id) {
                fprintf(stderr, "NODE-STATE with no NODE-ENDPOINT.\n");
            }

            DO_NTOHL(seqno, tlv + 8);
            DO_NTOHL(msecs, tlv + 12);

            debugf("   NODE-STATE %s %d %d", format_32(tlv + 4), seqno, msecs);

            mine = id_eq(tlv + 4, myid);
            if(mine)
                debugf(" (mine)");

            node = find_node(tlv + 4, 0);

            if(node && (seqno - node->seqno) & 0x80000000) {
                debugf(" (older)\n");
            } else if(!node || seqno != node->seqno ||
                      memcmp(tlv + 16, node->datahash, 8) != 0) {
                debugf(" (newer%s, %s)\n",
                       datalen ? ", data" : "", format_64(tlv + 16));

                if(mine) {
                    fprintf(stderr,
                            "Duplicate node identifier -- reclaiming.\n");
                    node->seqno = seqno + 42;
                    break;
                }

                if(datalen) {
                    unsigned char *new_data;
                    unsigned char h[8];
                    int rc;

                    node_hash(h, tlv + 24, datalen);
                    if(memcmp(h, tlv + 16, 8) != 0) {
                        fprintf(stderr, "Corrupt hash.\n");
                        goto fail;
                    }

                    if(!node)
                        node = find_node(tlv + 4, 1);
                    if(!node) {
                        fprintf(stderr, "Couldn't create node.\n");
                        goto fail;
                    }

                    new_data = realloc(node->data, datalen);
                    if(new_data == NULL) {
                        fprintf(stderr, "Eek!\n");
                        goto fail;
                    }
                    node->interface = interface;
                    node->seqno = seqno;
                    ts_add_msec(&node->orig_time, &now, msecs + 1);
                    node->data = new_data;
                    memcpy(node->data, tlv + 24, datalen);
                    node->datalen = datalen;
                    memcpy(node->datahash, tlv + 16, 8);
                    rc = parse_node_state(node);
                    if(rc < 0)
                        fprintf(stderr, "Couldn't parse node state.\n");
                    else
                        trickle_reset_all();
                    recompute = 1;
                } else {
                    struct neighbour *neigh = NULL;
                    if(have_id)
                        neigh = find_neighbour(interface, id, eid, from);
                    if(neigh) {
                        debugf("-> REQ-NODE-STATE %s\n", format_32(tlv + 4));
                        buffer_tlv(2, tlv + 4, 4, from, NULL);
                    } else
                        fprintf(stderr, "No neighbour to send request to.\n");
                }
            } else {
                debugf(" (consistent)\n");
            }
            break;
        }
        default:
            if(debug_level >= 3)
                debugf("   %d: %d\n", type, bodylen);
            break;
            break;
        }
        i += 4 + bodylen;
        i += -i & 3;
    }

    rc = 1;
    goto done;

 fail:
    rc = -1;

 done:
    if(recompute) {
        int r;
        silly_walk(find_node(myid, 0));
        r = prefix_assignment(1);
        if(r > 0)
            republish(0, 1);
    }
    return rc;
}
Exemple #6
0
int
parse_node_state(struct node *node)
{
    const unsigned char *buf = node->data;
    int buflen = node->datalen;
    int i = 0, j, rc;
    struct node_neighbour *nn = NULL;
    int numnn = 0, maxnn = 0;
    struct external **exts = NULL;
    int numexts = 0, maxexts = 0;

    for(j = 0; j < numneighs; j++) {
        if(id_eq(neighs[j].id, node->id))
            neighs[j].keepalive_interval = 0;
    }

    while(i < buflen) {
        unsigned const char *tlv = buf + i;
        int type, bodylen;

        if(buflen - i < 4) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        DO_NTOHS(type, tlv);
        DO_NTOHS(bodylen, tlv + 2);
        if(buflen - i < 4 + bodylen) {
            fprintf(stderr, "Received truncated TLV.\n");
            goto fail;
        }

        switch(type) {
        case 7:
            fprintf(stderr, "Received fragmented nonsense.\n");
            goto fail;
        case 8: {
            if(bodylen < 12) {
                fprintf(stderr, "Truncated neighbour TLV.\n");
                break;
            }

            if(numnn >= maxnn) {
                struct node_neighbour *nnn =
                    realloc(nn,
                            (2 * maxnn + 2) *
                            sizeof(struct node_neighbour));
                if(nnn == NULL) {
                    fprintf(stderr, "Eek.\n");
                    /* Oh, well. */
                    break;
                }
                nn = nnn;
                maxnn = 2 * maxnn + 2;
            }

            memcpy(nn[numnn].neigh, tlv + 4, 4);
            DO_NTOHL(nn[numnn].nei, tlv + 8);
            DO_NTOHL(nn[numnn].lei, tlv + 12);
            debugf("     NEIGHBOR %s (nei=%u, lei=%u)\n",
                   format_32(nn[numnn].neigh),
                   nn[numnn].nei, nn[numnn].lei);
            numnn++;
            break;
        }
        case 9: {
            unsigned eid, interval;
            if(bodylen < 8) {
                fprintf(stderr, "Truncated KEEP-ALIVE-INTERVAL.\n");
                break;
            }
            DO_NTOHL(eid, tlv + 4);
            DO_NTOHL(interval, tlv + 8);
            debugf("    KEEP-ALIVE-INTERVAL %u (eid=%u)\n", interval, eid);
            for(j = 0; j < numneighs; j++) {
                if(id_eq(neighs[j].id, node->id) && neighs[j].eid == eid)
                    neighs[j].keepalive_interval =
                        interval ? interval : 0xFFFFFFFF;
            }
            break;
        }
        case 32: {
            if(bodylen < 4) {
                debugf("Truncated VERSION\n");
                goto fail;
            }
            debugf("     VERSION\n");
            memcpy(node->capabilities, tlv + 6, 2);
            break;
        }
        case 33: {
            struct external *ext;
            debugf("     EXTERNAL-CONNECTION\n");
            ext = parse_external(node, tlv + 4, bodylen);
            if(ext) {
                if(numexts >= maxexts) {
                    struct external **e =
                        realloc(exts,
                                (2 * maxexts + 2) * sizeof(struct external *));
                    if(e == NULL) {
                        fprintf(stderr, "Eek.\n");
                        break;
                    }
                    maxexts = 2 * maxexts + 2;
                    exts = e;
                }
                exts[numexts++] = ext;
            }
            break;
        }
        case 35: {
            unsigned char plen;
            struct in6_addr addr;
            int prio;
            unsigned int eid;
            struct prefix_list *pl;

            if(bodylen < 6) {
                fprintf(stderr, "Truncated ASSIGNED-PREFIX.\n");
                break;
            }
            DO_NTOHL(eid, tlv + 4);
            plen = *(tlv + 9);
            if(bodylen < 6 + (plen + 7) / 8) {
                fprintf(stderr, "Truncated ASSIGNED-PREFIX.\n");
                break;
            }
            prio = tlv[8] & 0x0F;
            parse_pref(&addr, tlv + 10, plen);
            debugf("     ASSIGNED-PREFIX ");
            debug_prefix_raw(&addr, plen);
            debugf(" (%d, prio=%d)\n", eid, prio);
            pl = prefix_list_cons(node->assigned,
                                  &addr, plen, node->id, eid, prio);
            if(pl)
                node->assigned = pl;
            break;
        }
        case 36: {
            unsigned int eid;
            struct prefix_list *pl;
            struct in6_addr addr;
            if(bodylen < 20) {
                fprintf(stderr, "Truncated NODE-ADDRESS.\n");
                break;
            }
            DO_NTOHL(eid, tlv + 4);
            memcpy(&addr, tlv + 8, 16);
            debugf("     NODE-ADDRESS ");
            debug_address(&addr);
            debugf(" %d\n",eid);
            pl = prefix_list_cons(node->addresses,
                                  &addr, 128, node->id, eid, 0);
            if(pl)
                node->addresses = pl;
            break;
        }
        default:
            if(debug_level >= 3)
                debugf("     %d: %d\n", type, bodylen);
            break;
        }
        i += 4 + bodylen;
        i += -i & 3;
    }

    rc = 1;
    goto done;

 fail:
    rc = -1;

 done:
    if(node->neighs)
        free(node->neighs);
    node->neighs = nn;
    node->numneighs = numnn;
    if(node->exts) {
        for(i = 0; i < node->numexts; i++)
            destroy_external(node->exts[i]);
        free(node->exts);
    }
    node->exts = exts;
    node->numexts = numexts;

    return rc;
}
Exemple #7
0
/* The main function -- deal with a packet. */
int
handle_packet(int sock, unsigned char *packet, int packetlen,
              struct interface *interface, struct in6_addr *from)
{
    int bodylen, length, i;
    struct in6_addr nexthop;
    int have_nexthop = 0;

    if(!linklocal(from)) {
        fprintf(stderr, "Received non-link-local packet.\n");
        return -1;
    }

    if(packetlen < 4 || packet[0] != 42 || packet[1] != 2)
        goto fail;

    DO_NTOHS(bodylen, packet + 2);
    if(bodylen + 4 > packetlen)
        goto fail;

    i = 0;
    while(i < bodylen) {
        unsigned char *tlv = packet + 4 + i;
        unsigned int type;
        type = tlv[0];
        if(type == MESSAGE_PAD1) {
            i++;
            continue;
        }

        if(i + 1 > bodylen)
            goto fail;

        length = tlv[1];
        if(i + length > bodylen)
            goto fail;

#define CHECK(l) do { if(length + 2 < l) goto fail; } while(0)

        switch(type) {
        case MESSAGE_ACK_REQ:
            CHECK(8);
            send_ack(sock, interface, from, tlv + 4);
            break;
        case MESSAGE_HELLO: {
            unsigned short interval;
            CHECK(8);
            DO_NTOHS(interval, tlv + 6);
            update_neighbour(from, interface, 0, interval);
            break;
        }
        case MESSAGE_IHU:
            CHECK(8);
            if(tlv[2] == AE_WILDCARD ||
               (tlv[2] == AE_LL && length + 2 >= 16 &&
                address_match(tlv + 8, interface))) {
                unsigned short rxcost;
                DO_NTOHS(rxcost, tlv + 4);
                update_neighbour(from, interface, 1, rxcost);
            }
            break;
        case MESSAGE_NH:
            CHECK(4);
            if(tlv[2] == AE_LL) {
                unsigned char ll[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0};
                CHECK(12);
                memcpy((unsigned char*)&nexthop, ll, 8);
                memcpy(((unsigned char*)&nexthop) + 8, tlv + 4, 8);
                have_nexthop = 1;
            }
            break;
        case MESSAGE_UPDATE:
            CHECK(12);
            /* We're only interested in IPv6 default routes. */
            if(tlv[2] == AE_IPV6 && tlv[4] == 0) {
                unsigned short interval, metric;
                DO_NTOHS(interval, tlv + 6);
                DO_NTOHS(metric, tlv + 10);
                update_selected_route(interface,
                                      have_nexthop ? &nexthop : from,
                                      interval, metric);
            }
            break;
        case MESSAGE_REQUEST:
            CHECK(4);
            if(tlv[2] == AE_WILDCARD) {
                /* Request for a full table dump. */
                send_update(sock, interface, 0);
            } else if(tlv[2] == AE_IPV6 && tlv[3] == 64) {
                /* Request for a specific /64.  Is it ours? */
                CHECK(12);
                if(have_prefix && memcmp(myprefix, tlv + 4, 8) == 0)
                    send_update(sock, interface, 0);
            }
            break;
        case MESSAGE_MH_REQUEST:
            CHECK(16);
            /* There's no such thing as a wildcard multi-hop request. */
            if(tlv[2] == AE_IPV6 && tlv[3] == 64) {
                unsigned int seqno;
                DO_NTOHS(seqno, tlv + 4);
                if(have_prefix &&
                   tlv[3] == 6 && memcmp(myprefix, tlv + 8, 8) == 0 &&
                   memcmp(tlv + 8, my_router_id, 8) == 0) {
                    increment_myseqno(seqno);
                    send_update(sock, interface, 0);
                }
            }
            break;
        default:
            /* We're ignoring all other TLVs. */
            break;
        }

        i += length + 2;
    }
    return 1;

 fail:
    fprintf(stderr, "Received malformed packet.\n");
    return -1;
}