From 6459c6085f7324404717418448fa8c04ffd46c20 Mon Sep 17 00:00:00 2001 From: Kozlov Dmitry Date: Sun, 28 Aug 2011 00:37:34 +0400 Subject: ipv6: initial dhcpv6 support --- accel-pppd/ipv6/dhcpv6_packet.c | 438 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 accel-pppd/ipv6/dhcpv6_packet.c (limited to 'accel-pppd/ipv6/dhcpv6_packet.c') diff --git a/accel-pppd/ipv6/dhcpv6_packet.c b/accel-pppd/ipv6/dhcpv6_packet.c new file mode 100644 index 0000000..9a70996 --- /dev/null +++ b/accel-pppd/ipv6/dhcpv6_packet.c @@ -0,0 +1,438 @@ +#include +#include +#include + +#include "log.h" +#include "memdebug.h" + +#include "dhcpv6.h" + +#define BUF_SIZE 4096 + +struct dict_option { + int code; + const char *name; + int recv; + int len; + void (*print)(struct dhcpv6_option *, void (*)(const char *fmt, ...)); +}; + +static void print_clientid(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_ia_na(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_ia_ta(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_ia_addr(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_oro(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_uint8(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_time(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_ipv6addr(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_ipv6addr_array(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_status(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_uint64(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_reconf(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); +static void print_dnssl(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); + +static struct dict_option known_options[] = { + { D6_OPTION_CLIENTID, "Client-ID", 1, 0, print_clientid }, + { D6_OPTION_SERVERID, "Server-ID", 0, 0, print_clientid }, + { D6_OPTION_IA_NA, "IA-NA", 1, sizeof(struct dhcpv6_opt_ia_na), print_ia_na }, + { D6_OPTION_IA_TA, "IA-TA", 1, sizeof(struct dhcpv6_opt_ia_ta), print_ia_ta }, + { D6_OPTION_IAADDR, "IA-Addr", 1, sizeof(struct dhcpv6_opt_ia_addr), print_ia_addr }, + { D6_OPTION_ORO, "Option-Request", 1, 0, print_oro }, + { D6_OPTION_PREFERENCE, "Preference", 0, 0, print_uint8 }, + { D6_OPTION_ELAPSED_TIME, "Elapsed-Time", 1, 0, print_time }, + { D6_OPTION_RELAY_MSG, "Relay-Message", 1, 0 }, + { D6_OPTION_AUTH, "Auth", 1, 0 }, + { D6_OPTION_PREFERENCE, "Server-Unicast", 0, 0, print_ipv6addr }, + { D6_OPTION_STATUS_CODE, "Status", 0, 0, print_status }, + { D6_OPTION_RAPID_COMMIT, "Rapid-Commit", 1, 0 }, + { D6_OPTION_USER_CLASS, "User-Class", 1, 0 }, + { D6_OPTION_VENDOR_CLASS, "Vendor-Class", 1, 0, print_uint64 }, + { D6_OPTION_VENDOR_SPECIFIC, "Vendor-Specific", 1, 0, print_uint64 }, + { D6_OPTION_INTERFACE_ID, "Interface-ID", 1, 0, print_uint64 }, + { D6_OPTION_RECONF_MSG, "Reconfigure", 0, 0, print_reconf }, + { D6_OPTION_RECONF_ACCEPT, "Reconfigure-Accept", 1, 0 }, + { D6_OPTION_DNS_SERVERS, "DNS", 1, 0, print_ipv6addr_array }, + { D6_OPTION_DOMAIN_LIST, "DNSSL", 1, 0, print_dnssl }, + { 0 } +}; + +static void *parse_option(void *ptr, void *endptr, struct list_head *opt_list) +{ + struct dict_option *dopt; + struct dhcpv6_opt_hdr *opth = ptr; + struct dhcpv6_option *opt; + + if (ptr + sizeof(*opth) + ntohs(opth->len) > endptr) { + log_warn("dhcpv6: invalid packet received\n"); + return NULL; + } + + opt = _malloc(sizeof(*opt)); + if (!opt) { + log_emerg("out of memory\n"); + return NULL; + } + + memset(opt, 0, sizeof(*opt)); + INIT_LIST_HEAD(&opt->opt_list); + opt->hdr = ptr; + list_add_tail(&opt->entry, opt_list); + + for (dopt = known_options; dopt->code; dopt++) { + if (dopt->code) + break; + } + + if (dopt->len) { + endptr = ptr + sizeof(*opth) + ntohs(opth->len); + ptr += sizeof(*opth) + dopt->len; + while (ptr < endptr) { + ptr = parse_option(ptr, endptr, &opt->opt_list); + if (!ptr) + return NULL; + } + } else + ptr = ptr + sizeof(*opth) + ntohs(opth->len); + + return ptr; +} + +struct dhcpv6_packet *dhcpv6_packet_parse(const void *buf, size_t size) +{ + struct dhcpv6_packet *pkt; + struct dhcpv6_opt_hdr *opth; + void *ptr, *endptr; + + pkt = _malloc(sizeof(*pkt)); + if (!pkt) { + log_emerg("out of memory\n"); + return NULL; + } + + memset(pkt, 0, sizeof(*pkt)); + INIT_LIST_HEAD(&pkt->opt_list); + + pkt->hdr = _malloc(size); + if (!pkt->hdr) { + log_emerg("out of memory\n"); + _free(pkt); + return NULL; + } + + memcpy(pkt->hdr, buf, size); + + ptr = pkt->hdr->data; + endptr = ((void *)pkt->hdr) + size; + + while (ptr < endptr) { + opth = ptr; + if (opth->code == htons(D6_OPTION_CLIENTID)) + pkt->clientid = ptr; + else if (opth->code == htons(D6_OPTION_SERVERID)) + pkt->serverid = ptr; + ptr = parse_option(ptr, endptr, &pkt->opt_list); + if (!ptr) { + dhcpv6_packet_free(pkt); + return NULL; + } + } + + return pkt; +} + +struct dhcpv6_option *dhcpv6_option_alloc(struct dhcpv6_packet *pkt, int code, int len) +{ + struct dhcpv6_option *opt; + + opt = _malloc(sizeof(*opt)); + if (!opt) { + log_emerg("out of memory\n"); + return NULL; + } + + memset(opt, 0, sizeof(*opt)); + INIT_LIST_HEAD(&opt->opt_list); + + opt->hdr = pkt->endptr; + opt->hdr->code = htons(code); + opt->hdr->len = htons(len); + + pkt->endptr += sizeof(struct dhcpv6_opt_hdr) + len; + + list_add_tail(&opt->entry, &pkt->opt_list); + + return opt; +} + +struct dhcpv6_option *dhcpv6_nested_option_alloc(struct dhcpv6_packet *pkt, struct dhcpv6_option *popt, int code, int len) +{ + struct dhcpv6_option *opt; + + opt = _malloc(sizeof(*opt)); + if (!opt) { + log_emerg("out of memory\n"); + return NULL; + } + + memset(opt, 0, sizeof(*opt)); + INIT_LIST_HEAD(&opt->opt_list); + opt->parent = popt; + + opt->hdr = pkt->endptr; + opt->hdr->code = htons(code); + opt->hdr->len = htons(len); + + list_add_tail(&opt->entry, &popt->opt_list); + + pkt->endptr += sizeof(struct dhcpv6_opt_hdr) + len; + + while (popt) { + popt->hdr->len = htons(ntohs(popt->hdr->len) + sizeof(struct dhcpv6_opt_hdr) + len); + popt = popt->parent; + } + + return opt; +} + + +struct dhcpv6_packet *dhcpv6_packet_alloc_reply(struct dhcpv6_packet *req, int type) +{ + struct dhcpv6_packet *pkt = _malloc(sizeof(*pkt)); + struct dhcpv6_option *opt; + + if (!pkt) { + log_emerg("out of memory\n"); + return NULL; + } + + memset(pkt, 0, sizeof(*pkt)); + INIT_LIST_HEAD(&pkt->opt_list); + pkt->ppp = req->ppp; + + pkt->hdr = _malloc(BUF_SIZE); + if (!pkt->hdr) { + log_emerg("out of memory\n"); + _free(pkt); + return NULL; + } + + pkt->hdr->type = type; + pkt->hdr->trans_id = req->hdr->trans_id; + + pkt->endptr = pkt->hdr->data; + + opt = dhcpv6_option_alloc(pkt, D6_OPTION_SERVERID, ntohs(req->serverid->hdr.len)); + memcpy(opt->hdr, req->serverid, sizeof(struct dhcpv6_opt_hdr) + ntohs(req->serverid->hdr.len)); + + opt = dhcpv6_option_alloc(pkt, D6_OPTION_CLIENTID, ntohs(req->clientid->hdr.len)); + memcpy(opt->hdr, req->clientid, sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len)); + + return pkt; +} + +static void free_options(struct list_head *opt_list) +{ + struct dhcpv6_option *opt; + + while (!list_empty(opt_list)) { + opt = list_entry(opt_list->next, typeof(*opt), entry); + list_del(&opt->entry); + free_options(&opt->opt_list); + _free(opt); + } +} + +void dhcpv6_packet_free(struct dhcpv6_packet *pkt) +{ + free_options(&pkt->opt_list); + _free(pkt->hdr); + _free(pkt); +} + +static void print_options(struct list_head *opt_list, int level, void (*print)(const char *fmt, ...)) +{ + struct dhcpv6_option *opt; + struct dict_option *dopt; + const char l_open[] = {'<', '{', '('}; + const char l_close[] = {'>', '}', ')'}; + + if (level >= sizeof(l_open)) + level = sizeof(l_open) - 1; + + list_for_each_entry(opt, opt_list, entry) { + for (dopt = known_options; dopt->code; dopt++) { + if (htons(dopt->code) == opt->hdr->code) + break; + } + if (dopt->code) { + print(" %c%s", l_open[level], dopt->name); + if (dopt->print) + dopt->print(opt, print); + + print_options(&opt->opt_list, level + 1, print); + + print("%c", l_close[level]); + } else + print(" %cOption %i%c", l_open[level], ntohs(opt->hdr->code), l_close[level]); + } +} + +void dhcpv6_packet_print(struct dhcpv6_packet *pkt, void (*print)(const char *fmt, ...)) +{ + static const char *type_name[] = { + "Solicit", + "Advertise", + "Request", + "Confirt", + "Renew", + "Rebind", + "Reply", + "Release", + "Decline", + "Reconfigure", + "Information-Request", + "Relay-Form", + "Relay-Reply" + }; + + print("[DHCPv6 "); + + if (pkt->hdr->type == 0 || pkt->hdr->type > 13) + print("Unknown"); + else + print("%s", type_name[pkt->hdr->type - 1]); + + print(" XID=%x", pkt->hdr->trans_id); + + print_options(&pkt->opt_list, 0, print); + + print("]\n"); +} + +static void print_clientid(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + int i; + struct dhcpv6_opt_clientid *o = (struct dhcpv6_opt_clientid *)opt->hdr; + + print(" %i:", htons(o->duid.type)); + + for (i = 0; i < ntohs(o->hdr.len) - 2; i++) + print("%02x", o->duid.u.raw[i]); +} + +static void print_ia_na(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + struct dhcpv6_opt_ia_na *o = (struct dhcpv6_opt_ia_na *)opt->hdr; + + print(" %x T1=%i T2=%i", o->iaid, ntohl(o->T1), ntohl(o->T2)); +} + +static void print_ia_ta(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + struct dhcpv6_opt_ia_ta *o = (struct dhcpv6_opt_ia_ta *)opt->hdr; + + print(" %x", o->iaid); +} + +static void print_ia_addr(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + struct dhcpv6_opt_ia_addr *o = (struct dhcpv6_opt_ia_addr *)opt->hdr; + char str[50]; + + inet_ntop(AF_INET6, &o->addr, str, sizeof(str)); + print(" %s pref_lifetime=%i valid_lifetime=%i", str, ntohl(o->pref_lifetime), ntohl(o->valid_lifetime)); +} + +static void print_oro(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + uint16_t *ptr = (uint16_t *)opt->hdr->data; + uint16_t *end_ptr = ptr + ntohs(opt->hdr->len)/2; + struct dict_option *dopt; + int f = 0; + + for (; ptr < end_ptr; ptr++) { + if (f) + print(","); + else + print(" "); + + for (dopt = known_options; dopt->code; dopt++) { + if (ntohs(*ptr) == dopt->code) + break; + } + + if (dopt->code) + print("%s", dopt->name); + else + print("%i", ntohs(*ptr)); + + f = 1; + } +} + +static void print_uint8(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + print(" %i", *(uint8_t *)opt->hdr->data); +} + +static void print_time(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + print(" %u", *(uint32_t *)opt->hdr->data); +} + +static void print_ipv6addr(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + char str[50]; + + inet_ntop(AF_INET6, opt->hdr->data, str, sizeof(str)); + + print(" %s", str); +} + +static void print_ipv6addr_array(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + char str[50]; + int i; + int f = 0; + struct in6_addr *addr = (struct in6_addr *)opt->hdr->data; + + for (i = ntohs(opt->hdr->len) / sizeof(*addr); i; i--, addr++) { + inet_ntop(AF_INET6, addr, str, sizeof(str)); + print("%c%s", f ? ',' : ' ', str); + f = 1; + } +} + +static void print_status(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + struct dhcpv6_opt_status *o = (struct dhcpv6_opt_status *)opt->hdr; + static char *status_name[] = { + "Success", + "UnspecFail", + "NoAddrsAvail", + "NoBindings", + "NotOnLink", + "UseMulticast" + }; + + if (ntohs(o->code) < 0 || ntohs(o->code) > 5) + print(" %u", ntohs(o->code)); + else + print(" %s", status_name[ntohs(o->code)]); +} + +static void print_uint64(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + print(" %llu", *(uint64_t *)opt->hdr->data); +} + +static void print_reconf(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + +} + +static void print_dnssl(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ + +} + -- cgit v1.2.3