/* dnsmasq is Copyright (c) 2000-2012 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 dated June, 1991, or (at your option) version 3 dated 29 June, 2007. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "dnsmasq.h" #ifdef HAVE_DHCP6 static size_t outpacket_counter; static int make_duid1(unsigned short type, unsigned int flags, char *mac, size_t maclen, void *parm); void make_duid(time_t now) { iface_enumerate(AF_LOCAL, &now, make_duid1); if (!daemon->duid) die("Cannot create DHCPv6 server DUID", NULL, EC_MISC); } static int make_duid1(unsigned short type, unsigned int flags, char *mac, size_t maclen, void *parm) { /* create DUID as specified in RFC3315. We use the MAC of the first interface we find that isn't loopback or P-to-P */ unsigned char *p; if (flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) return 1; daemon->duid = p = safe_malloc(maclen + 8); daemon->duid_len = maclen + 8; PUTSHORT(1, p); /* DUID_LLT */ PUTSHORT(type, p); /* address type */ PUTLONG(*((time_t *)parm), p); /* time */ memcpy(p, mac, maclen); return 0; } void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize) { u16 opt, opt_len; void *start; if (!opts) return NULL; while (1) { if (end - opts < 4) return NULL; start = opts; GETSHORT(opt, opts); GETSHORT(opt_len, opts); if (opt_len > (end - opts)) return NULL; if (opt == search && (opt_len >= minsize)) return start; opts += opt_len; } } void *opt6_next(void *opts, void *end) { u16 opt_len; if (end - opts < 4) return NULL; opts += 2; GETSHORT(opt_len, opts); if (opt_len >= (end - opts)) return NULL; return opts + opt_len; } #define opt6_len(opt) ((int)(((unsigned short *)(opt))[1])) #define opt6_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[4u+(unsigned int)(i)])) static unsigned int opt6_uint(unsigned char *opt, int offset, int size) { /* this worries about unaligned data and byte order */ unsigned int ret = 0; int i; unsigned char *p = opt6_ptr(opt, offset); for (i = 0; i < size; i++) ret = (ret << 8) | *p++; return ret; } /* daemon->outpacket_counter = 4; message type and ID elapsed time: int o = new_opt(OPTION_ELAPSED_TIME); put_opt_short(o, 100) finalise_opt(o); IA_NA int o = new_opt(OPTION_IA_NA); put_opt_long(o, IAID); put_opt_long(o, T1); put_opt_long(0, T2); int o1 = new_opt(OPTION_IAADDR); put_opt(o1, &addr, sizeof(addr)); put_opt_long(o1, preferred_lifetime); put_opt_long(o1, valid_lifetime); finalise_opt(o1); finalise_opt(o); */ void end_opt6(int container) { void *p = daemon->outpacket.iov_base + container + 2; u16 len = outpacket_counter - container - 4 ; PUTSHORT(len, p); } static void *expand(size_t headroom) { void *ret; if (expand_buf(&daemon->outpacket, outpacket_counter + headroom)) { ret = daemon->outpacket.iov_base + outpacket_counter; outpacket_counter += headroom; return ret; } return NULL; } int new_opt6(int opt) { int ret = outpacket_counter; void *p; if ((p = expand(4))) { PUTSHORT(opt, p); PUTSHORT(0, p); } return ret; } void *put_opt6(void *data, size_t len) { void *p; if (data && (p = expand(len))) memcpy(p, data, len); return p; } void put_opt6_long(unsigned int val) { void *p; if (( p = expand(4))) PUTLONG(p, val); } void put_opt6_short(unsigned int val) { void *p; if ((p = expand(2))) PUTSHORT(val, p); } void put_opt6_byte(unsigned int val) { void *p; if ((p = expand(1))) *((unsigned char *)p) = val; } size_t dhcp6_reply(struct dhcp_context *context, size_t sz) { void *packet_options = ((void *)daemon->dhcp_packet.iov_base) + 4; void *end = ((void *)daemon->dhcp_packet.iov_base) + sz; void *na_option, *na_end; void *opt, *p; int o; outpacket_counter = 4; /* skip message type and transaction-id */ if (!(opt = opt6_find(packet_options, end, OPTION6_CLIENT_ID, 1))) return; o = new_opt6(OPTION6_CLIENT_ID); put_opt6(opt6_ptr(opt, 0), opt6_len(opt)); end_opt6(o); o = new_opt6(OPTION6_SERVER_ID); put_opt6(daemon->duid, daemon->duid_len); end_opt6(o); if ((opt = opt6_find(packet_options, end, OPTION6_IA_NA, 12))) { while (opt = opt6_find(opt, end, OPTION6_IA_NA, 12)) { void *ia_end = opt6_ptr(opt, opt6_len(opt)); void *ia_option = opt6_find(opt6_ptr(opt, 12), ia_end, OPTION6_IAADDR, 24); unsigned int iaid = opt6_uint(opt, 0, 4); unsigned int t1 = opt6_uint(opt, 4, 4); unsigned int t2 = opt6_uint(opt, 8, 4); if (ia_option) while ((ia_option = ia_option, ia_end, OPTION6_IAADDR, 24)) { /* do address option */ ia_option = opt6_next(ia_option, ia_end); } else { /* no preferred address call address allocate */ } opt = opt6_next(opt, end); } } else if ((opt = opt6_find(packet_options, end, OPTION6_IA_TA, 4))) while (opt = opt6_find(opt, end, OPTION6_IA_TA, 4)) { void *ia_end = opt6_ptr(opt, opt6_len(opt)); void *ia_option = opt6_find(opt6_ptr(opt, 4), ia_end, OPTION6_IAADDR, 24); unsigned int iaid = opt6_uint(opt, 0, 4); if (ia_option) while ((ia_option = ia_option, ia_end, OPTION6_IAADDR, 24)) { /* do address option */ ia_option = opt6_next(ia_option, ia_end); } else { /* no preferred address */ } opt = opt6_next(opt, end); } else return; /* no IA_NA and no IA_TA */ } #endif