ref: dfb4b522830edab8f3856289d326d6cf5e930644
dir: /sys/src/cmd/ip/ipconfig/dhcpv6.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <ip.h> #include <ndb.h> #include "ipconfig.h" enum { SOLICIT = 1, ADVERTISE, REQUEST, CONFIRM, RENEW, REBIND, REPLY, RELEASE, DECLINE, RECONFIGURE, INFOREQ, RELAYFORW, RELAYREPL, }; static uchar v6dhcpservers[IPaddrlen] = { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, }; static uchar sid[256]; static int sidlen; static int openlisten(void) { int n, fd, cfd; char data[128], devdir[40]; sprint(data, "%s/udp!%I!546", conf.mpoint, conf.lladdr); for (n = 0; (cfd = announce(data, devdir)) < 0; n++) { if(!noconfig) sysfatal("can't announce for dhcp: %r"); /* might be another client - wait and try again */ warning("can't announce %s: %r", data); sleep(jitter()); if(n > 10) return -1; } if(fprint(cfd, "headers") < 0) sysfatal("can't set header mode: %r"); fprint(cfd, "ignoreadvice"); sprint(data, "%s/data", devdir); fd = open(data, ORDWR); if(fd < 0) sysfatal("open %s: %r", data); close(cfd); return fd; } static int transaction(int fd, int type, int timeout) { union { Udphdr; uchar buf[4096]; } ipkt, opkt; uchar *p, *e, *x; int tra, opt, len, sleepfor; tra = lrand() & 0xFFFFFF; ipmove(opkt.laddr, conf.lladdr); ipmove(opkt.raddr, v6dhcpservers); ipmove(opkt.ifcaddr, conf.lladdr); hnputs(opkt.lport, 546); hnputs(opkt.rport, 547); p = opkt.buf + Udphdrsize; *p++ = type; *p++ = tra >> 16; *p++ = tra >> 8; *p++ = tra >> 0; /* client identifier */ *p++ = 0x00; *p++ = 0x01; /* len */ *p++ = conf.duidlen >> 8; *p++ = conf.duidlen; memmove(p, conf.duid, conf.duidlen); p += conf.duidlen; /* IA for non-temporary address */ len = 12; if(validip(conf.laddr)) len += 4 + IPaddrlen + 2*4; *p++ = 0x00; *p++ = 0x03; *p++ = len >> 8; *p++ = len; /* IAID */ *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x01; /* T1, T2 */ *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; if(len > 12){ *p++ = 0x00; *p++ = 0x05; *p++ = 0x00; *p++ = IPaddrlen + 2*4; memmove(p, conf.laddr, IPaddrlen); p += IPaddrlen; memset(p, 0xFF, 2*4); p += 2*4; } /* Option Request */ *p++ = 0x00; *p++ = 0x06; *p++ = 0x00; *p++ = 0x02; *p++ = 0x00; *p++ = 0x17; /* DNS servers */ if(sidlen > 0){ *p++ = 0x00; *p++ = 0x02; /* len */ *p++ = sidlen >> 8; *p++ = sidlen;; memmove(p, sid, sidlen); p += sidlen; } len = -1; for(sleepfor = 500; timeout > 0; sleepfor <<= 1){ DEBUG("sending dhcpv6 request %x", opkt.buf[Udphdrsize]); alarm(sleepfor); if(len < 0) write(fd, opkt.buf, p - opkt.buf); len = read(fd, ipkt.buf, sizeof(ipkt.buf)); timeout += alarm(0); timeout -= sleepfor; if(len == 0) break; if(len < Udphdrsize+4) continue; if(ipkt.buf[Udphdrsize+1] != ((tra>>16)&0xFF) || ipkt.buf[Udphdrsize+2] != ((tra>>8)&0xFF) || ipkt.buf[Udphdrsize+3] != ((tra>>0)&0xFF)) continue; DEBUG("got dhcpv6 reply %x from %I on %I", ipkt.buf[Udphdrsize+0], ipkt.raddr, ipkt.ifcaddr); type |= (int)ipkt.buf[Udphdrsize+0]<<8; switch(type){ case ADVERTISE << 8 | SOLICIT: case REPLY << 8 | REQUEST: goto Response; default: return -1; } } return -1; Response: for(p = ipkt.buf + Udphdrsize + 4, e = ipkt.buf + len; p < e; p = x) { if (p+4 > e) return -1; opt = (int)p[0] << 8 | p[1]; len = (int)p[2] << 8 | p[3]; p += 4; x = p+len; if (x > e) return -1; DEBUG("got dhcpv6 option %x: [%d] %.*H", opt, len, len, p); switch(opt){ case 0x01: /* client identifier */ continue; case 0x02: /* server identifier */ if(len < 1 || len > sizeof(sid)) break; sidlen = len; memmove(sid, p, sidlen); continue; case 0x03: /* IA for non-temporary address */ if(p+12+4+IPaddrlen+2*4 > x) break; /* skip IAID, T1, T2 */ p += 12; /* IA Addresss */ if(p[0] != 0x00 || p[1] != 0x05 || p[2] != 0x00 || p[3] != IPaddrlen+2*4) break; p += 4; memset(conf.mask, 0xFF, IPaddrlen); memmove(conf.laddr, p, IPaddrlen); continue; case 0x17: /* dns servers */ if(len % IPaddrlen) break; addaddrs(conf.dns, sizeof(conf.dns), p, len); continue; default: DEBUG("unknown dhcpv6 option %x", opt); continue; } warning("dhcpv6: malformed option %x: [%d] %.*H", opt, len, len, x-len); } return 0; } void dhcpv6query(void) { int fd; if(!dodhcp || conf.duidlen <= 0) return; fd = openlisten(); if(transaction(fd, SOLICIT, 5000) < 0) goto out; if(!validip(conf.laddr)) goto out; if(transaction(fd, REQUEST, 10000) < 0) goto out; out: close(fd); }