diff options
author | Kozlov Dmitry <xeb@mail.ru> | 2011-08-28 00:37:34 +0400 |
---|---|---|
committer | Kozlov Dmitry <xeb@mail.ru> | 2011-08-28 00:37:34 +0400 |
commit | 6459c6085f7324404717418448fa8c04ffd46c20 (patch) | |
tree | 9c610e3aa4497463caf0c534f725d059229e0db0 /accel-pppd/ipv6 | |
parent | 2ae178c12aced47699cbb252f6a67defb0d0bbe7 (diff) | |
download | accel-ppp-6459c6085f7324404717418448fa8c04ffd46c20.tar.gz accel-ppp-6459c6085f7324404717418448fa8c04ffd46c20.zip |
ipv6: initial dhcpv6 support
Diffstat (limited to 'accel-pppd/ipv6')
-rw-r--r-- | accel-pppd/ipv6/CMakeLists.txt | 7 | ||||
-rw-r--r-- | accel-pppd/ipv6/dhcpv6.c | 551 | ||||
-rw-r--r-- | accel-pppd/ipv6/dhcpv6.h | 175 | ||||
-rw-r--r-- | accel-pppd/ipv6/dhcpv6_packet.c | 438 | ||||
-rw-r--r-- | accel-pppd/ipv6/nd.c | 458 |
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 00000000..9f8c3d1d --- /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 00000000..c431fbee --- /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 00000000..6c5d164c --- /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 00000000..9a709963 --- /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 00000000..67ba102e --- /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); |