summaryrefslogtreecommitdiff
path: root/accel-pppd/ipv6
diff options
context:
space:
mode:
authorKozlov Dmitry <xeb@mail.ru>2011-08-28 00:37:34 +0400
committerKozlov Dmitry <xeb@mail.ru>2011-08-28 00:37:34 +0400
commit6459c6085f7324404717418448fa8c04ffd46c20 (patch)
tree9c610e3aa4497463caf0c534f725d059229e0db0 /accel-pppd/ipv6
parent2ae178c12aced47699cbb252f6a67defb0d0bbe7 (diff)
downloadaccel-ppp-xebd-6459c6085f7324404717418448fa8c04ffd46c20.tar.gz
accel-ppp-xebd-6459c6085f7324404717418448fa8c04ffd46c20.zip
ipv6: initial dhcpv6 support
Diffstat (limited to 'accel-pppd/ipv6')
-rw-r--r--accel-pppd/ipv6/CMakeLists.txt7
-rw-r--r--accel-pppd/ipv6/dhcpv6.c551
-rw-r--r--accel-pppd/ipv6/dhcpv6.h175
-rw-r--r--accel-pppd/ipv6/dhcpv6_packet.c438
-rw-r--r--accel-pppd/ipv6/nd.c458
5 files changed, 1629 insertions, 0 deletions
diff --git a/accel-pppd/ipv6/CMakeLists.txt b/accel-pppd/ipv6/CMakeLists.txt
new file mode 100644
index 0000000..9f8c3d1
--- /dev/null
+++ b/accel-pppd/ipv6/CMakeLists.txt
@@ -0,0 +1,7 @@
+ADD_LIBRARY(ipv6_dhcp SHARED dhcpv6.c dhcpv6_packet.c)
+ADD_LIBRARY(ipv6_nd SHARED nd.c)
+
+INSTALL(TARGETS ipv6_dhcp ipv6_nd
+ LIBRARY DESTINATION lib/accel-ppp
+)
+
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);
diff --git a/accel-pppd/ipv6/dhcpv6.h b/accel-pppd/ipv6/dhcpv6.h
new file mode 100644
index 0000000..6c5d164
--- /dev/null
+++ b/accel-pppd/ipv6/dhcpv6.h
@@ -0,0 +1,175 @@
+#ifndef __DHCPV6_H
+#define __DHCPV6_H
+
+#include <stdint.h>
+#include <netinet/in.h>
+
+#include "list.h"
+
+#define __packed __attribute__((packed))
+
+#define DHCPV6_CLIENT_PORT 546
+#define DHCPV6_SERV_PORT 547
+
+#define D6_OPTION_CLIENTID 1
+#define D6_OPTION_SERVERID 2
+#define D6_OPTION_IA_NA 3
+#define D6_OPTION_IA_TA 4
+#define D6_OPTION_IAADDR 5
+#define D6_OPTION_ORO 6
+#define D6_OPTION_PREFERENCE 7
+#define D6_OPTION_ELAPSED_TIME 8
+#define D6_OPTION_RELAY_MSG 9
+#define D6_OPTION_AUTH 11
+#define D6_OPTION_UNICAST 12
+#define D6_OPTION_STATUS_CODE 13
+#define D6_OPTION_RAPID_COMMIT 14
+#define D6_OPTION_USER_CLASS 15
+#define D6_OPTION_VENDOR_CLASS 16
+#define D6_OPTION_VENDOR_SPECIFIC 17
+#define D6_OPTION_INTERFACE_ID 18
+#define D6_OPTION_RECONF_MSG 19
+#define D6_OPTION_RECONF_ACCEPT 20
+#define D6_OPTION_DNS_SERVERS 23
+#define D6_OPTION_DOMAIN_LIST 24
+
+#define D6_SOLICIT 1
+#define D6_ADVERTISE 2
+#define D6_REQUEST 3
+#define D6_CONFIRM 4
+#define D6_RENEW 5
+#define D6_REBIND 6
+#define D6_REPLY 7
+#define D6_RELEASE 8
+#define D6_DECLINE 9
+#define D6_RECONFIGURE 10
+#define D6_INFORMATION_REQUEST 11
+#define D6_RELAY_FORM 12
+#define D6_RELAY_REPL 13
+
+#define D6_STATUS_Success 0
+#define D6_STATUS_UnspecFail 1
+#define D6_STATUS_NoAddrsAvail 2
+#define D6_STATUS_NoBinding 3
+#define D6_STATUS_NotOnLink 4
+#define D6_STATUS_UseMulticast 5
+
+#define DUID_LLT 1
+#define DUID_EN 2
+#define DUID_LL 3
+
+struct dhcpv6_opt_hdr
+{
+ uint16_t code;
+ uint16_t len;
+ uint8_t data[0];
+} __packed;
+
+struct dhcpv6_msg_hdr
+{
+ uint32_t type:8;
+ uint32_t trans_id:24;
+ uint8_t data[0];
+} __packed;
+
+struct dhcpv6_duid
+{
+ uint16_t type;
+ union {
+ struct {
+ uint16_t htype;
+ uint32_t time;
+ uint8_t addr[0];
+ } __packed llt;
+ struct {
+ uint32_t enterprise;
+ uint8_t id[0];
+ } en;
+ struct {
+ uint16_t htype;
+ uint8_t addr[0];
+ } ll;
+ uint8_t raw[0];
+ } u;
+} __packed;
+
+struct dhcpv6_opt_clientid
+{
+ struct dhcpv6_opt_hdr hdr;
+ struct dhcpv6_duid duid;
+} __packed;
+
+struct dhcpv6_opt_serverid
+{
+ struct dhcpv6_opt_hdr hdr;
+ struct dhcpv6_duid duid;
+} __packed;
+
+struct dhcpv6_opt_ia_na
+{
+ struct dhcpv6_opt_hdr hdr;
+ uint32_t iaid;
+ uint32_t T1;
+ uint32_t T2;
+} __packed;
+
+struct dhcpv6_opt_ia_ta
+{
+ struct dhcpv6_opt_hdr hdr;
+ uint32_t iaid;
+} __packed;
+
+
+struct dhcpv6_opt_ia_addr
+{
+ struct dhcpv6_opt_hdr hdr;
+ struct in6_addr addr;
+ uint32_t pref_lifetime;
+ uint32_t valid_lifetime;
+} __packed;
+
+struct dhcpv6_opt_oro
+{
+ struct dhcpv6_opt_hdr hdr;
+ uint16_t opt[0];
+} __packed;
+
+struct dhcpv6_opt_status
+{
+ struct dhcpv6_opt_hdr hdr;
+ uint16_t code;
+ char msg[0];
+} __packed;
+
+
+struct dhcpv6_option
+{
+ struct list_head entry;
+
+ struct dhcpv6_opt_hdr *hdr;
+
+ struct dhcpv6_option *parent;
+ struct list_head opt_list;
+};
+
+struct ppp_t;
+struct dhcpv6_packet
+{
+ struct ppp_t *ppp;
+
+ struct dhcpv6_msg_hdr *hdr;
+ struct dhcpv6_opt_clientid *clientid;
+ struct dhcpv6_opt_serverid *serverid;
+
+ struct list_head opt_list;
+ void *endptr;
+};
+
+struct dhcpv6_packet *dhcpv6_packet_parse(const void *buf, size_t size);
+void dhcpv6_packet_free(struct dhcpv6_packet *pkt);
+void dhcpv6_packet_print(struct dhcpv6_packet *pkt, void (*print)(const char *fmt, ...));
+struct dhcpv6_packet *dhcpv6_packet_alloc_reply(struct dhcpv6_packet *req, int type);
+struct dhcpv6_option *dhcpv6_option_alloc(struct dhcpv6_packet *pkt, int code, int len);
+struct dhcpv6_option *dhcpv6_nested_option_alloc(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt, int code, int len);
+
+#endif
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 <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#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, ...))
+{
+
+}
+
diff --git a/accel-pppd/ipv6/nd.c b/accel-pppd/ipv6/nd.c
new file mode 100644
index 0000000..67ba102
--- /dev/null
+++ b/accel-pppd/ipv6/nd.c
@@ -0,0 +1,458 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include "log.h"
+#include "ppp.h"
+#include "events.h"
+#include "mempool.h"
+#include "ipdb.h"
+
+#include "memdebug.h"
+
+#define MAX_DNS_COUNT 3
+
+static int conf_init_ra = 3;
+static int conf_init_ra_interval = 1;
+static int conf_ra_interval = 60;
+static int conf_router_lifetime = 300;
+static int conf_rdnss_lifetime = 300;
+static struct in6_addr conf_dns[MAX_DNS_COUNT];
+static int conf_dns_count;
+static void *conf_dnssl;
+static int conf_dnssl_size;
+static int conf_managed;
+
+#undef ND_OPT_ROUTE_INFORMATION
+#define ND_OPT_ROUTE_INFORMATION 24
+struct nd_opt_route_info_local /* route information */
+{
+ uint8_t nd_opt_ri_type;
+ uint8_t nd_opt_ri_len;
+ uint8_t nd_opt_ri_prefix_len;
+ uint8_t nd_opt_ri_flags_reserved;
+ uint32_t nd_opt_ri_lifetime;
+ struct in6_addr nd_opt_ri_prefix;
+};
+
+#undef ND_OPT_RDNSS_INFORMATION
+#define ND_OPT_RDNSS_INFORMATION 25
+struct nd_opt_rdnss_info_local
+{
+ uint8_t nd_opt_rdnssi_type;
+ uint8_t nd_opt_rdnssi_len;
+ uint16_t nd_opt_rdnssi_pref_flag_reserved;
+ uint32_t nd_opt_rdnssi_lifetime;
+ struct in6_addr nd_opt_rdnssi[0];
+};
+
+#undef ND_OPT_DNSSL_INFORMATION
+#define ND_OPT_DNSSL_INFORMATION 31
+struct nd_opt_dnssl_info_local
+{
+ uint8_t nd_opt_dnssli_type;
+ uint8_t nd_opt_dnssli_len;
+ uint16_t nd_opt_dnssli_pref_flag_reserved;
+ uint32_t nd_opt_dnssli_lifetime;
+ uint8_t nd_opt_dnssli[0];
+};
+
+struct ipv6_nd_handler_t
+{
+ struct ppp_t *ppp;
+ struct ppp_pd_t pd;
+ struct triton_md_handler_t hnd;
+ struct triton_timer_t timer;
+ int ra_sent;
+};
+
+static void *pd_key;
+
+#define BUF_SIZE 1024
+static mempool_t buf_pool;
+
+static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *addr)
+{
+ void *buf = mempool_alloc(buf_pool), *endptr;
+ struct nd_router_advert *adv = buf;
+ struct nd_opt_prefix_info *pinfo;
+ //struct nd_opt_route_info_local *rinfo;
+ struct nd_opt_rdnss_info_local *rdnssinfo;
+ struct in6_addr *rdnss_addr;
+ struct nd_opt_dnssl_info_local *dnsslinfo;
+ //struct nd_opt_mtu *mtu;
+ struct ipv6db_addr_t *a;
+ int i;
+
+ if (!buf) {
+ log_emerg("out of memory\n");
+ return;
+ }
+
+ memset(adv, 0, sizeof(*adv));
+ adv->nd_ra_type = ND_ROUTER_ADVERT;
+ adv->nd_ra_curhoplimit = 64;
+ adv->nd_ra_router_lifetime = htons(conf_router_lifetime);
+ adv->nd_ra_flags_reserved =
+ conf_managed ? ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER : 0;
+ //adv->nd_ra_reachable = 0;
+ //adv->nd_ra_retransmit = 0;
+
+ pinfo = (struct nd_opt_prefix_info *)(adv + 1);
+ list_for_each_entry(a, &h->ppp->ipv6->addr_list, entry) {
+ if (a->prefix_len == 128)
+ continue;
+
+ memset(pinfo, 0, sizeof(*pinfo));
+ pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
+ pinfo->nd_opt_pi_len = 4;
+ pinfo->nd_opt_pi_prefix_len = a->prefix_len;
+ pinfo->nd_opt_pi_flags_reserved = ND_OPT_PI_FLAG_ONLINK | ((a->flag_auto || !conf_managed) ? ND_OPT_PI_FLAG_AUTO : 0);
+ pinfo->nd_opt_pi_valid_time = 0xffffffff;
+ pinfo->nd_opt_pi_preferred_time = 0xffffffff;
+ memcpy(&pinfo->nd_opt_pi_prefix, &a->addr, 8);
+ pinfo++;
+ }
+
+ /*rinfo = (struct nd_opt_route_info_local *)pinfo;
+ list_for_each_entry(a, &h->ppp->ipv6->route_list, entry) {
+ memset(rinfo, 0, sizeof(*rinfo));
+ rinfo->nd_opt_ri_type = ND_OPT_ROUTE_INFORMATION;
+ rinfo->nd_opt_ri_len = 3;
+ rinfo->nd_opt_ri_prefix_len = a->prefix_len;
+ rinfo->nd_opt_ri_lifetime = 0xffffffff;
+ memcpy(&rinfo->nd_opt_ri_prefix, &a->addr, 8);
+ rinfo++;
+ }*/
+
+ if (conf_dns_count) {
+ rdnssinfo = (struct nd_opt_rdnss_info_local *)pinfo;
+ memset(rdnssinfo, 0, sizeof(*rdnssinfo));
+ rdnssinfo->nd_opt_rdnssi_type = ND_OPT_RDNSS_INFORMATION;
+ rdnssinfo->nd_opt_rdnssi_len = 1 + 2 * conf_dns_count;
+ rdnssinfo->nd_opt_rdnssi_lifetime = htonl(conf_rdnss_lifetime);
+ rdnss_addr = (struct in6_addr *)rdnssinfo->nd_opt_rdnssi;
+ for (i = 0; i < conf_dns_count; i++) {
+ memcpy(rdnss_addr, &conf_dns[i], sizeof(*rdnss_addr));
+ rdnss_addr++;
+ }
+ } else
+ rdnss_addr = (struct in6_addr *)pinfo;
+
+ if (conf_dnssl) {
+ dnsslinfo = (struct nd_opt_dnssl_info_local *)rdnss_addr;
+ memset(dnsslinfo, 0, sizeof(*dnsslinfo));
+ dnsslinfo->nd_opt_dnssli_type = ND_OPT_DNSSL_INFORMATION;
+ dnsslinfo->nd_opt_dnssli_len = 1 + (conf_dnssl_size - 1) / 8 + 1;
+ dnsslinfo->nd_opt_dnssli_lifetime = htonl(conf_rdnss_lifetime);
+ memcpy(dnsslinfo->nd_opt_dnssli, conf_dnssl, conf_dnssl_size);
+ memset(dnsslinfo->nd_opt_dnssli + conf_dnssl_size, 0, (dnsslinfo->nd_opt_dnssli_len - 1) * 8 - conf_dnssl_size);
+ endptr = (void *)dnsslinfo + dnsslinfo->nd_opt_dnssli_len * 8;
+ } else
+ endptr = rdnss_addr;
+
+ sendto(h->hnd.fd, buf, endptr - buf, 0, (struct sockaddr *)addr, sizeof(*addr));
+
+ mempool_free(buf);
+}
+
+static void send_ra_timer(struct triton_timer_t *t)
+{
+ struct ipv6_nd_handler_t *h = container_of(t, typeof(*h), timer);
+ struct sockaddr_in6 addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_addr.s6_addr32[0] = htonl(0xff020000);
+ addr.sin6_addr.s6_addr32[3] = htonl(0x1);
+ addr.sin6_scope_id = h->ppp->ifindex;
+
+ if (h->ra_sent++ == conf_init_ra) {
+ h->timer.period = conf_ra_interval * 1000;
+ triton_timer_mod(t, 0);
+ }
+
+ ipv6_nd_send_ra(h, &addr);
+}
+
+static int ipv6_nd_read(struct triton_md_handler_t *_h)
+{
+ struct ipv6_nd_handler_t *h = container_of(_h, typeof(*h), hnd);
+ struct icmp6_hdr *icmph = mempool_alloc(buf_pool);
+ int n;
+ struct sockaddr_in6 addr;
+ socklen_t addr_len = sizeof(addr);
+
+ if (!icmph) {
+ log_emerg("out of memory\n");
+ return 0;
+ }
+
+ while (1) {
+ n = recvfrom(h->hnd.fd, icmph, BUF_SIZE, 0, &addr, &addr_len);
+ if (n == -1) {
+ if (errno == EAGAIN)
+ break;
+ log_ppp_error("ipv6_nd: recvmsg: %s\n", strerror(errno));
+ continue;
+ }
+
+ if (n < sizeof(*icmph)) {
+ log_ppp_warn("ipv6_nd: received short icmp packet (%i)\n", n);
+ continue;
+ }
+
+ if (icmph->icmp6_type != ND_ROUTER_SOLICIT) {
+ log_ppp_warn("ipv6_nd: received unexcpected icmp packet (%i)\n", icmph->icmp6_type);
+ continue;
+ }
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&addr.sin6_addr)) {
+ log_ppp_warn("ipv6_nd: received icmp packet from non link-local address\n");
+ continue;
+ }
+
+ /*if (*(uint64_t *)(addr.sin6_addr.s6_addr + 8) != *(uint64_t *)(h->ppp->ipv6_addr.s6_addr + 8)) {
+ log_ppp_warn("ipv6_nd: received icmp packet from unknown address\n");
+ continue;
+ }*/
+
+ ipv6_nd_send_ra(h, &addr);
+ }
+
+ mempool_free(icmph);
+
+ return 0;
+}
+
+static int ipv6_nd_start(struct ppp_t *ppp)
+{
+ int sock;
+ struct icmp6_filter filter;
+ struct sockaddr_in6 addr;
+ struct ipv6_mreq mreq;
+ int val;
+ struct ipv6_nd_handler_t *h;
+
+ sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+
+ if (sock < 0) {
+ log_ppp_error("socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6): %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_addr.s6_addr32[0] = htons(0xfe80);
+ *(uint64_t *)(addr.sin6_addr.s6_addr + 8) = ppp->ipv6->intf_id;
+ addr.sin6_scope_id = ppp->ifindex;
+
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) {
+ log_ppp_error("ipv6_nd: bind: %s %i\n", strerror(errno), errno);
+ goto out_err;
+ }
+
+ val = 2;
+ if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val))) {
+ log_ppp_error("ipv6_nd: setsockopt(IPV6_CHECKSUM): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ val = 255;
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val))) {
+ log_ppp_error("ipv6_nd: setsockopt(IPV6_UNICAST_HOPS): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val))) {
+ log_ppp_error("ipv6_nd: setsockopt(IPV6_MULTICAST_HOPS): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ /*val = 1;
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val))) {
+ log_ppp_error("ipv6_nd: setsockopt(IPV6_HOPLIMIT): %s\n", strerror(errno));
+ goto out_err;
+ }*/
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+
+ if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter))) {
+ log_ppp_error("ipv6_nd: setsockopt(ICMP6_FILTER): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ 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(0x2);
+
+ if (setsockopt(sock, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) {
+ log_ppp_error("ipv6_nd: failed to join ipv6 allrouters\n");
+ goto out_err;
+ }
+
+ fcntl(sock, F_SETFL, O_NONBLOCK);
+
+ h = _malloc(sizeof(*h));
+ memset(h, 0, sizeof(*h));
+ h->ppp = ppp;
+ h->pd.key = &pd_key;
+ h->hnd.fd = sock;
+ h->hnd.read = ipv6_nd_read;
+ h->timer.expire = send_ra_timer;
+ h->timer.period = conf_init_ra_interval * 1000;
+ list_add_tail(&h->pd.entry, &ppp->pd_list);
+
+ triton_md_register_handler(ppp->ctrl->ctx, &h->hnd);
+ triton_md_enable_handler(&h->hnd, MD_MODE_READ);
+
+ triton_timer_add(ppp->ctrl->ctx, &h->timer, 0);
+
+ return 0;
+
+out_err:
+ close(sock);
+ return -1;
+}
+
+static struct ipv6_nd_handler_t *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, typeof(struct ipv6_nd_handler_t), pd);
+ }
+
+ return NULL;
+}
+
+static void ev_ppp_started(struct ppp_t *ppp)
+{
+ if (!ppp->ipv6)
+ return;
+
+ ipv6_nd_start(ppp);
+}
+
+static void ev_ppp_finishing(struct ppp_t *ppp)
+{
+ struct ipv6_nd_handler_t *h = find_pd(ppp);
+
+ if (!h)
+ return;
+
+ if (h->timer.tpd)
+ triton_timer_del(&h->timer);
+
+ triton_md_unregister_handler(&h->hnd);
+ close(h->hnd.fd);
+
+ list_del(&h->pd.entry);
+
+ _free(h);
+}
+
+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_config(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 init(void)
+{
+ buf_pool = mempool_create(BUF_SIZE);
+
+ load_config();
+
+ conf_managed = triton_module_loaded("ipv6_dhcp");
+
+ 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_FINISHING, (triton_event_func)ev_ppp_finishing);
+}
+
+DEFINE_INIT(5, init);