summaryrefslogtreecommitdiff
path: root/accel-pppd/ipv6/dhcpv6.c
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/ipv6/dhcpv6.c')
-rw-r--r--accel-pppd/ipv6/dhcpv6.c551
1 files changed, 551 insertions, 0 deletions
diff --git a/accel-pppd/ipv6/dhcpv6.c b/accel-pppd/ipv6/dhcpv6.c
new file mode 100644
index 0000000..c431fbe
--- /dev/null
+++ b/accel-pppd/ipv6/dhcpv6.c
@@ -0,0 +1,551 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include "triton.h"
+#include "mempool.h"
+#include "log.h"
+#include "ppp.h"
+#include "ipdb.h"
+#include "events.h"
+
+#include "memdebug.h"
+
+#include "dhcpv6.h"
+
+#define BUF_SIZE 65536
+#define MAX_DNS_COUNT 3
+
+static int conf_verbose;
+static int conf_pref_lifetime = -1;
+static int conf_valid_lifetime = -1;
+static struct in6_addr conf_dns[MAX_DNS_COUNT];
+static int conf_dns_count;
+static void *conf_dnssl;
+static int conf_dnssl_size;
+
+struct dhcpv6_pd
+{
+ struct ppp_pd_t pd;
+ struct dhcpv6_opt_clientid *clientid;
+ struct dhcpv6_opt_serverid serverid;
+ uint32_t iaid;
+};
+
+static struct triton_md_handler_t dhcpv6_hnd;
+static struct triton_context_t dhcpv6_ctx;
+
+static uint8_t *buf;
+static void *pd_key;
+
+static void ev_ppp_started(struct ppp_t *ppp)
+{
+ struct ipv6_mreq mreq;
+ struct dhcpv6_pd *pd;
+ time_t t, t0;
+ struct tm tm;
+
+ if (!ppp->ipv6)
+ return;
+
+ time(&t);
+ localtime_r(&t, &tm);
+
+ tm.tm_year = 100;
+ tm.tm_mon = 0;
+ tm.tm_mday = 1;
+ tm.tm_hour = 0;
+ tm.tm_min = 0;
+ tm.tm_sec = 0;
+
+ t0 = mktime(&tm);
+
+ pd = _malloc(sizeof(*pd));
+ memset(pd, 0, sizeof(*pd));
+
+ pd->pd.key = &pd_key;
+
+ pd->serverid.hdr.code = htons(D6_OPTION_SERVERID);
+ pd->serverid.hdr.len = htons(16);
+ pd->serverid.duid.type = htons(DUID_LLT);
+ pd->serverid.duid.u.llt.htype = htons(27);
+ pd->serverid.duid.u.llt.time = htonl(t - t0);
+ *(uint64_t *)pd->serverid.duid.u.llt.addr = ppp->ipv6->intf_id;
+
+ list_add_tail(&pd->pd.entry, &ppp->pd_list);
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.ipv6mr_interface = ppp->ifindex;
+ mreq.ipv6mr_multiaddr.s6_addr32[0] = htonl(0xff020000);
+ mreq.ipv6mr_multiaddr.s6_addr32[3] = htonl(0x010002);
+
+ if (setsockopt(dhcpv6_hnd.fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) {
+ log_ppp_error("dhcpv6: failed to join to All_DHCP_Relay_Agents_and_Servers\n");
+ return;
+ }
+}
+
+static struct dhcpv6_pd *find_pd(struct ppp_t *ppp)
+{
+ struct ppp_pd_t *pd;
+
+ list_for_each_entry(pd, &ppp->pd_list, entry) {
+ if (pd->key == &pd_key)
+ return container_of(pd, struct dhcpv6_pd, pd);
+ }
+
+ return NULL;
+}
+
+static void ev_ppp_finished(struct ppp_t *ppp)
+{
+ struct dhcpv6_pd *pd = find_pd(ppp);
+
+ if (!pd)
+ return;
+
+ list_del(&pd->pd.entry);
+
+ if (pd->clientid)
+ _free(pd->clientid);
+
+ _free(pd);
+}
+
+static void dhcpv6_send(struct dhcpv6_packet *reply)
+{
+ struct sockaddr_in6 addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(DHCPV6_CLIENT_PORT);
+ addr.sin6_addr.s6_addr32[0] = htons(0xfe80);
+ *(uint64_t *)(addr.sin6_addr.s6_addr + 8) = reply->ppp->ipv6->peer_intf_id;
+ addr.sin6_scope_id = reply->ppp->ifindex;
+
+ sendto(dhcpv6_hnd.fd, reply->hdr, reply->endptr - (void *)reply->hdr, 0, (struct sockaddr *)&addr, sizeof(addr));
+ printf("sendto: %s %i\n", strerror(errno), errno);
+}
+
+static void build_addr(struct ipv6db_addr_t *a, uint64_t intf_id, struct in6_addr *addr)
+{
+ memcpy(addr, &a->addr, sizeof(*addr));
+
+ if (a->prefix_len <= 64)
+ *(uint64_t *)(addr->s6_addr + 8) = intf_id;
+ else
+ *(uint64_t *)(addr->s6_addr + 8) |= intf_id & ((1 << (128 - a->prefix_len)) - 1);
+}
+
+static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, int code)
+{
+ struct dhcpv6_packet *reply;
+ struct dhcpv6_option *opt, *opt1, *opt2, *opt3, *opt4;
+ struct dhcpv6_opt_ia_na *ia_na;
+ struct dhcpv6_opt_ia_addr *ia_addr;
+ struct dhcpv6_opt_status *status;
+ struct ipv6db_addr_t *a;
+ struct in6_addr addr, *addr_ptr;
+ int i, j, f = 0, f1;
+ uint16_t *ptr;
+
+ reply = dhcpv6_packet_alloc_reply(req, code);
+ if (!reply)
+ return;
+
+ list_for_each_entry(opt, &req->opt_list, entry) {
+ if (ntohs(opt->hdr->code) == D6_OPTION_IA_NA) {
+ opt1 = dhcpv6_option_alloc(reply, D6_OPTION_IA_NA, sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_hdr));
+ memcpy(opt1->hdr + 1, opt->hdr + 1, ntohs(opt1->hdr->len));
+ if (list_empty(&req->ppp->ipv6->addr_list) || f) {
+ opt3 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_STATUS_CODE, sizeof(struct dhcpv6_opt_status) - sizeof(struct dhcpv6_opt_hdr));
+ status = (struct dhcpv6_opt_status *)opt3->hdr;
+ status->code = htons(D6_STATUS_NoAddrsAvail);
+ } else {
+ if (code == D6_REPLY) {
+ ia_na = (struct dhcpv6_opt_ia_na *)opt->hdr;
+ pd->iaid = ia_na->iaid;
+ }
+
+ f = 1;
+
+ list_for_each_entry(a, &req->ppp->ipv6->addr_list, entry) {
+ opt2 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAADDR, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
+ ia_addr = (struct dhcpv6_opt_ia_addr *)opt2->hdr;
+
+ build_addr(a, req->ppp->ipv6->peer_intf_id, &ia_addr->addr);
+
+ ia_addr->pref_lifetime = htonl(conf_pref_lifetime);
+ ia_addr->valid_lifetime = htonl(conf_valid_lifetime);
+ }
+
+ list_for_each_entry(opt2, &opt->opt_list, entry) {
+ if (ntohs(opt2->hdr->code) == D6_OPTION_IAADDR) {
+ ia_addr = (struct dhcpv6_opt_ia_addr *)opt2->hdr;
+ f1 = 0;
+ list_for_each_entry(a, &req->ppp->ipv6->addr_list, entry) {
+ build_addr(a, req->ppp->ipv6->peer_intf_id, &addr);
+ if (memcmp(&addr, &ia_addr->addr, sizeof(addr)))
+ continue;
+ f1 = 1;
+ break;
+ }
+
+ if (!f1) {
+ opt3 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAADDR, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
+ memcpy(opt3->hdr->data, opt2->hdr->data, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
+ opt4 = dhcpv6_nested_option_alloc(reply, opt3, D6_OPTION_STATUS_CODE, sizeof(struct dhcpv6_opt_status) - sizeof(struct dhcpv6_opt_hdr));
+ status = (struct dhcpv6_opt_status *)opt4->hdr;
+ status->code = htons(D6_STATUS_NotOnLink);
+ }
+ }
+ }
+ }
+ } else if (ntohs(opt->hdr->code) == D6_OPTION_IA_TA) {
+ opt1 = dhcpv6_option_alloc(reply, D6_OPTION_IA_TA, sizeof(struct dhcpv6_opt_ia_ta) - sizeof(struct dhcpv6_opt_hdr));
+ memcpy(opt1->hdr + 1, opt->hdr + 1, ntohs(opt1->hdr->len));
+ opt3 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_STATUS_CODE, sizeof(struct dhcpv6_opt_status) - sizeof(struct dhcpv6_opt_hdr));
+ status = (struct dhcpv6_opt_status *)opt3->hdr;
+ status->code = htons(D6_STATUS_NoAddrsAvail);
+ } else if (ntohs(opt->hdr->code) == D6_OPTION_ORO) {
+ for (i = ntohs(opt->hdr->len) / 2, ptr = (uint16_t *)opt->hdr->data; i; i--, ptr++) {
+ if (ntohs(*ptr) == D6_OPTION_DNS_SERVERS) {
+ opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS, conf_dns_count * sizeof(addr));
+ for (j = 0, addr_ptr = (struct in6_addr *)opt1->hdr->data; j < conf_dns_count; j++, addr_ptr++)
+ memcpy(addr_ptr, conf_dns + j, sizeof(addr));
+ } else if (ntohs(*ptr) == D6_OPTION_DOMAIN_LIST) {
+ opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DOMAIN_LIST, conf_dnssl_size);
+ memcpy(opt1->hdr->data, conf_dnssl, conf_dnssl_size);
+ }
+ }
+ }
+ }
+
+ opt3 = dhcpv6_option_alloc(reply, D6_OPTION_STATUS_CODE, sizeof(struct dhcpv6_opt_status) - sizeof(struct dhcpv6_opt_hdr));
+ status = (struct dhcpv6_opt_status *)opt3->hdr;
+ status->code = htons(D6_STATUS_Success);
+
+ if (conf_verbose) {
+ log_ppp_info2("send ");
+ dhcpv6_packet_print(reply, log_ppp_info2);
+ }
+
+ dhcpv6_send(reply);
+
+ dhcpv6_packet_free(reply);
+}
+
+static void dhcpv6_recv_solicit(struct dhcpv6_packet *req)
+{
+ struct dhcpv6_pd *pd = find_pd(req->ppp);
+
+ if (!pd)
+ return;
+
+ if (!req->clientid) {
+ log_ppp_error("dhcpv6: no Client-ID option\n");
+ return;
+ }
+
+ if (req->serverid) {
+ log_ppp_error("dhcpv6: unexpected Server-ID option\n");
+ return;
+ }
+
+ req->serverid = &pd->serverid;
+
+ if (!pd->clientid) {
+ pd->clientid = _malloc(sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len));
+ memcpy(pd->clientid, req->clientid, sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len));
+ } else if (pd->clientid->hdr.len != req->clientid->hdr.len || memcmp(pd->clientid, req->clientid, sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len))) {
+ log_ppp_warn("dhcpv6: Client-ID option was changed\n");
+ return;
+ }
+
+ dhcpv6_send_reply(req, pd, D6_ADVERTISE);
+}
+
+static void dhcpv6_recv_request(struct dhcpv6_packet *req)
+{
+ struct dhcpv6_pd *pd = find_pd(req->ppp);
+
+ if (!pd)
+ return;
+
+ if (!req->clientid) {
+ log_ppp_error("dhcpv6: no Client-ID option\n");
+ return;
+ }
+
+ if (!req->serverid) {
+ log_ppp_error("dhcpv6: no Server-ID option\n");
+ return;
+ }
+
+ if (!pd->clientid) {
+ pd->clientid = _malloc(sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len));
+ memcpy(pd->clientid, req->clientid, sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len));
+ } else if (pd->clientid->hdr.len != req->clientid->hdr.len || memcmp(pd->clientid, req->clientid, sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len))) {
+ log_ppp_warn("dhcpv6: Client-ID option was changed\n");
+ return;
+ }
+
+ dhcpv6_send_reply(req, pd, D6_REPLY);
+}
+
+static void dhcpv6_recv_renew(struct dhcpv6_packet *pkt)
+{
+
+}
+
+static void dhcpv6_recv_rebind(struct dhcpv6_packet *pkt)
+{
+
+}
+
+static void dhcpv6_recv_release(struct dhcpv6_packet *pkt)
+{
+
+}
+
+static void dhcpv6_recv_decline(struct dhcpv6_packet *pkt)
+{
+
+}
+
+static void dhcpv6_recv_packet(struct dhcpv6_packet *pkt)
+{
+ if (conf_verbose) {
+ log_ppp_info2("recv ");
+ dhcpv6_packet_print(pkt, log_ppp_info2);
+ }
+
+ switch (pkt->hdr->type) {
+ case D6_SOLICIT:
+ dhcpv6_recv_solicit(pkt);
+ break;
+ case D6_REQUEST:
+ dhcpv6_recv_request(pkt);
+ break;
+ case D6_RENEW:
+ dhcpv6_recv_renew(pkt);
+ break;
+ case D6_REBIND:
+ dhcpv6_recv_rebind(pkt);
+ break;
+ case D6_RELEASE:
+ dhcpv6_recv_release(pkt);
+ break;
+ case D6_DECLINE:
+ dhcpv6_recv_decline(pkt);
+ break;
+ }
+
+ dhcpv6_packet_free(pkt);
+}
+
+static int dhcpv6_read(struct triton_md_handler_t *h)
+{
+ int n;
+ struct sockaddr_in6 addr;
+ socklen_t len = sizeof(addr);
+ struct dhcpv6_packet *pkt;
+ struct ppp_t *ppp;
+
+ while (1) {
+ n = recvfrom(h->fd, buf, BUF_SIZE, 0, &addr, &len);
+ if (n == -1) {
+ if (errno == EAGAIN)
+ return 0;
+ log_error("dhcpv6: read: %s\n", strerror(errno));
+ }
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&addr.sin6_addr))
+ continue;
+
+ if (addr.sin6_port != ntohs(DHCPV6_CLIENT_PORT))
+ continue;
+
+ pkt = dhcpv6_packet_parse(buf, n);
+ if (!pkt || !pkt->clientid) {
+ continue;
+ }
+
+ pthread_rwlock_rdlock(&ppp_lock);
+ list_for_each_entry(ppp, &ppp_list, entry) {
+ if (ppp->state != PPP_STATE_ACTIVE)
+ continue;
+
+ if (!ppp->ipv6)
+ continue;
+
+ if (ppp->ifindex != addr.sin6_scope_id)
+ continue;
+
+ if (ppp->ipv6->peer_intf_id != *(uint64_t *)(addr.sin6_addr.s6_addr + 8))
+ continue;
+
+ pkt->ppp = ppp;
+
+ triton_context_call(ppp->ctrl->ctx, (triton_event_func)dhcpv6_recv_packet, pkt);
+ break;
+ }
+ pthread_rwlock_unlock(&ppp_lock);
+ }
+
+ return 0;
+}
+
+static void dhcpv6_close(struct triton_context_t *ctx)
+{
+ triton_md_unregister_handler(&dhcpv6_hnd);
+ close(dhcpv6_hnd.fd);
+ triton_context_unregister(ctx);
+}
+
+static struct triton_md_handler_t dhcpv6_hnd = {
+ .read = dhcpv6_read,
+};
+
+static struct triton_context_t dhcpv6_ctx = {
+ .close = dhcpv6_close,
+};
+
+static void add_dnssl(const char *val)
+{
+ int n = strlen(val);
+ const char *ptr;
+ uint8_t *buf;
+
+ if (val[n - 1] == '.')
+ n++;
+ else
+ n += 2;
+
+ if (n > 255) {
+ log_error("dnsv6: dnssl '%s' is too long\n", val);
+ return;
+ }
+
+ if (!conf_dnssl)
+ conf_dnssl = _malloc(n);
+ else
+ conf_dnssl = _realloc(conf_dnssl, conf_dnssl_size + n);
+
+ buf = conf_dnssl + conf_dnssl_size;
+
+ while (1) {
+ ptr = strchr(val, '.');
+ if (!ptr)
+ ptr = strchr(val, 0);
+ if (ptr - val > 63) {
+ log_error("dnsv6: dnssl '%s' is invalid\n", val);
+ return;
+ }
+ *buf = ptr - val;
+ memcpy(buf + 1, val, ptr - val);
+ buf += 1 + (ptr - val);
+ val = ptr + 1;
+ if (!*ptr || !*val) {
+ *buf = 0;
+ break;
+ }
+ }
+
+ conf_dnssl_size += n;
+}
+
+static void load_dns(void)
+{
+ struct conf_sect_t *s = conf_get_section("dnsv6");
+ struct conf_option_t *opt;
+
+ if (!s)
+ return;
+
+ conf_dns_count = 0;
+
+ if (conf_dnssl)
+ _free(conf_dnssl);
+ conf_dnssl = NULL;
+ conf_dnssl_size = 0;
+
+ list_for_each_entry(opt, &s->items, entry) {
+ if (!strcmp(opt->name, "dnssl")) {
+ add_dnssl(opt->val);
+ continue;
+ }
+
+ if (!strcmp(opt->name, "dns") || !opt->val) {
+ if (conf_dns_count == MAX_DNS_COUNT)
+ continue;
+
+ if (inet_pton(AF_INET6, opt->val ? opt->val : opt->name, &conf_dns[conf_dns_count]) == 0) {
+ log_error("dnsv6: faild to parse '%s'\n", opt->name);
+ continue;
+ }
+ conf_dns_count++;
+ }
+ }
+}
+
+static void load_config(void)
+{
+ const char *opt;
+
+ opt = conf_get_opt("dhcpv6", "verbose");
+ if (opt)
+ conf_verbose = atoi(opt);
+
+ load_dns();
+}
+
+static void init(void)
+{
+ struct sockaddr_in6 addr;
+ int sock;
+
+ load_config();
+
+ sock = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (!sock) {
+ log_error("dhcpv6: socket: %s\n", strerror(errno));
+ return;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(DHCPV6_SERV_PORT);
+
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) {
+ log_error("dhcpv6: bind: %s\n", strerror(errno));
+ close(sock);
+ return;
+ }
+
+ fcntl(sock, F_SETFL, O_NONBLOCK);
+
+ dhcpv6_hnd.fd = sock;
+
+ buf = malloc(BUF_SIZE);
+
+ triton_context_register(&dhcpv6_ctx, NULL);
+ triton_md_register_handler(&dhcpv6_ctx, &dhcpv6_hnd);
+ triton_md_enable_handler(&dhcpv6_hnd, MD_MODE_READ);
+ triton_context_wakeup(&dhcpv6_ctx);
+
+ triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config);
+ triton_event_register_handler(EV_PPP_STARTED, (triton_event_func)ev_ppp_started);
+ triton_event_register_handler(EV_PPP_FINISHED, (triton_event_func)ev_ppp_finished);
+}
+
+DEFINE_INIT(10, init);