diff --git a/dhcp6.c b/dhcp6.c index 4454f4e..ffb738d 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -4,13 +4,21 @@ * GPL licenced */ +#define _GNU_SOURCE #include #include #include +#include +#include +#include #include "dhcp6.h" #include "l2tpns.h" #include "ipv6_u.h" +#include "cluster.h" +#include "util.h" + +int dhcpv6fd; struct dhcp6_in_option { @@ -538,7 +546,7 @@ void dhcpv6_process_from_ipv6(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) return; } - if (p_ip6_hdr_in->ip6_nxt != 17) + if (p_ip6_hdr_in->ip6_nxt != IPPROTO_UDP) { LOG(5, 0, 0, "not UDP DHCP packet??\n"); return; @@ -597,6 +605,8 @@ static int dhcpv6_format_dns_search_name(const char *strdns, uint8_t *buffer) void dhcpv6_init(void) { uint32_t id; + int on = 1; + struct sockaddr_in6 addr; dhcp6_local_serverid.opt_hdr.code = htons(D6_OPT_SERVERID); dhcp6_local_serverid.opt_hdr.len = htons(4 + sizeof(id)); @@ -609,4 +619,129 @@ void dhcpv6_init(void) id = htobe32(0xFDFDFAFA); memcpy(dhcp6_local_serverid.duid.u.ll.addr, &id, sizeof(id)); + + dhcpv6fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (dhcpv6fd < 0) + LOG(1, 0, 0, "DHCPv6: could not create UDP socket: %s\n", strerror(errno)); + +#ifdef SO_REUSEPORT + if (setsockopt(dhcpv6fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) + LOG(1, 0, 0, "DHCPv6: could not set reusing port: %s\n", strerror(errno)); +#endif + + if (setsockopt(dhcpv6fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + LOG(1, 0, 0, "DHCPv6: could not set reusing address: %s\n", strerror(errno)); + +#ifdef IPV6_RECVPKTINFO + if (setsockopt(dhcpv6fd, SOL_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) + LOG(1, 0, 0, "DHCPv6: could not request pktinfo: %s\n", strerror(errno)); +#else + if (setsockopt(dhcpv6fd, SOL_IPV6, IPV6_PKTINFO, &on, sizeof(on)) < 0) + LOG(1, 0, 0, "DHCPv6: could not request pktinfo: %s\n", strerror(errno)); +#endif +#ifdef IPV6_V6ONLY + if (setsockopt(dhcpv6fd, SOL_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) + LOG(1, 0, 0, "DHCPv6: could not set v6only: %s\n", strerror(errno)); +#endif + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(DHCP6_SERVER_PORT); + if (bind(dhcpv6fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) + LOG(1, 0, 0, "DHCPv6: could not bind to DHCPv6 server port\n"); +} + +// +// A new ppp interface was created, watch for DHCPv6 on it +void dhcpv6_listen(int ifidx) +{ + struct ipv6_mreq mreq; + + memset(&mreq, 0, sizeof(mreq)); + mreq.ipv6mr_interface = ifidx; + inet_pton(AF_INET6, DHCP6_SERVER_ADDRESS, &mreq.ipv6mr_multiaddr); + if (setsockopt(dhcpv6fd, SOL_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) + LOG(2, 0, 0, "DHCPv6: could not join DHCPv6 group: %s\n", strerror(errno)); +} + +// +// A DHCPv6 request was received on a ppp interface, receive it +void dhcpv6_process_from_kernel(uint8_t *p, size_t size_bufp) +{ + struct sockaddr_storage fromaddr; + struct sockaddr_in6 *sin6; + socklen_t fromlen = sizeof(fromaddr); + struct in6_addr toaddr; + int ifidx; + int r, s, t; + + r = recvfromto6(dhcpv6fd, p, size_bufp, 0, (struct sockaddr *) &fromaddr, &fromlen, &toaddr, &ifidx); + if (r < 0) + { + static time_t lastwarn; + time_t now = time(NULL); + if (now > lastwarn) + { + LOG(5, 0, 0, "DHCPV6: reception error: %s\n", strerror(errno)); + lastwarn = now; + } + return; + } + LOG(5, 0, 0, "Got packet on DHCP socket on if %d\n", ifidx); + + if (fromaddr.ss_family != AF_INET6) + { + LOG(5, 0, 0, "DHCPV6: got strange family %d\n", fromaddr.ss_family); + return; + } + sin6 = (struct sockaddr_in6 *) &fromaddr; + + if (ntohs(sin6->sin6_port) != DHCP6_CLIENT_PORT) + { + LOG(5, 0, 0, "DHCPV6: got strange client port %d\n", ntohs(sin6->sin6_port)); + return; + } + + for (s = 1; s < MAXSESSION; s++) + { + if (sess_local[s].ppp_if_idx != ifidx) + continue; + + t = session[s].tunnel; + + if (config->cluster_iam_master) + dhcpv6_process(s, t, &sin6->sin6_addr, p, r); + else + { + // DHCPV6 must be managed by the Master. + + // Fake UDPv6 header + struct udphdr *udp = (struct udphdr *)p - 1; + udp->source = sin6->sin6_port; + udp->dest = htons(DHCP6_SERVER_PORT); + udp->len = sizeof(*udp) + r; + // udp->check is not checked by Master anyway + r += sizeof(*udp); + + struct ip6_hdr *ip6 = (struct ip6_hdr *)udp - 1; + ip6->ip6_flow = htonl(6); + ip6->ip6_plen = htons(r); + ip6->ip6_nxt = IPPROTO_UDP; + ip6->ip6_hlim = 255; + memcpy(&ip6->ip6_src, &sin6->sin6_addr, sizeof(sin6->sin6_addr)); + memcpy(&ip6->ip6_dst, &toaddr, sizeof(toaddr)); + r += sizeof(*ip6); + + uint16_t *w = (uint16_t *)ip6 - 4; + w[0] = htons(0x0002); /* L2TP data*/ + w[1] = htons(t); + w[2] = htons(s); + w[3] = htons(PPPIPV6); /* PPP protocol */ + r += 8; + + master_forward_packet((uint8_t *) w, r, htonl(tunnel[t].ip), htons(tunnel[t].port), tunnel[t].indexudp); + } + + break; + } } diff --git a/dhcp6.h b/dhcp6.h index 54cd76d..38fa445 100644 --- a/dhcp6.h +++ b/dhcp6.h @@ -7,6 +7,10 @@ #ifndef __DHCP6_H__ #define __DHCP6_H__ +#define DHCP6_CLIENT_PORT 546 +#define DHCP6_SERVER_PORT 547 +#define DHCP6_SERVER_ADDRESS "ff02::1:2" + #define DHCP6_SOLICIT 1 #define DHCP6_ADVERTISE 2 #define DHCP6_REQUEST 3 @@ -212,7 +216,10 @@ struct dhcp6_opt_ia_prefix { } __attribute__((packed)); // dhcp6.c +extern int dhcpv6fd; void dhcpv6_process_from_ipv6(uint16_t s, uint16_t t, uint8_t *p, uint16_t l); void dhcpv6_init(void); +void dhcpv6_listen(int ifidx); +void dhcpv6_process_from_kernel(uint8_t *p, size_t size_bufp); #endif /* __DHCP6_H__ */ diff --git a/l2tpns.c b/l2tpns.c index 54efdf0..d33acc5 100644 --- a/l2tpns.c +++ b/l2tpns.c @@ -1228,6 +1228,8 @@ static int create_kernel_accel(sessionidt s) sess_local[s].ppp_if_fd = ppp_if_fd; sess_local[s].ppp_if_idx = ifr.ifr_ifindex; + dhcpv6_listen(ifr.ifr_ifindex); + memset(&sess_local[s].last_stats, 0, sizeof(sess_local[s].last_stats)); return 0; @@ -5447,8 +5449,8 @@ static int still_busy(void) return 0; } -// the base set of fds polled: cli, cluster, tun, udp (MAX_UDPFD), control, dae, netlink, udplac, pppoedisc, pppoesess, kernel ppp -#define BASE_FDS (9 + MAX_UDPFD) +// the base set of fds polled: cli, cluster, tun, udp (MAX_UDPFD), control, dae, netlink, udplac, pppoedisc, pppoesess, dhcpv6 +#define BASE_FDS (10 + MAX_UDPFD) // additional polled fds #ifdef BGP @@ -5464,14 +5466,17 @@ static int still_busy(void) #define MAX_FDS (BASE_FDS + RADIUS_FDS + EXTRA_FDS + L2TP_FDS + PPPOX_FDS + PPP_CHAN_FDS + PPP_IF_FDS) +// for the header of the forwarded MPPP/DHCP packet (see C_MPPP_FORWARD) +#define SLACK 56 + // main loop - gets packets on tun or udp and processes them static void mainloop(void) { int i, j; uint8_t buf[65536]; - uint8_t *p = buf + 32; // for the header of the forwarded MPPP packet (see C_MPPP_FORWARD) + uint8_t *p = buf + SLACK; // for the header of the forwarded MPPP packet (see C_MPPP_FORWARD) // and the forwarded pppoe session - int size_bufp = sizeof(buf) - 32; + int size_bufp = sizeof(buf) - SLACK; clockt next_cluster_ping = 0; // send initial ping immediately struct epoll_event events[MAX_FDS]; int maxevent = sizeof(events)/sizeof(*events); @@ -5528,6 +5533,10 @@ static void mainloop(void) e.data.ptr = &d[i++]; epoll_ctl(epollfd, EPOLL_CTL_ADD, pppoesessfd, &e); + d[i].type = FD_TYPE_DHCPV6; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, dhcpv6fd, &e); + for (j = 0; j < config->nbudpfd; j++) { d[i].type = FD_TYPE_UDP; @@ -5787,6 +5796,12 @@ static void mainloop(void) break; } + case FD_TYPE_DHCPV6: + { + dhcpv6_process_from_kernel(p, size_bufp); + break; + } + default: LOG(0, 0, 0, "Unexpected fd type returned from epoll_wait: %d\n", d->type); } diff --git a/l2tpns.h b/l2tpns.h index 21b5d1b..5ddd538 100644 --- a/l2tpns.h +++ b/l2tpns.h @@ -1086,6 +1086,7 @@ struct event_data { FD_TYPE_PPPOX, FD_TYPE_PPP_CHAN, FD_TYPE_PPP_IF, + FD_TYPE_DHCPV6, } type; int index; // for RADIUS, BGP, UDP }; diff --git a/util.c b/util.c index d2f5445..41730a2 100644 --- a/util.c +++ b/util.c @@ -139,6 +139,7 @@ pid_t fork_and_close() if (sess_local[i].ppp_if_fd >= 0) close(sess_local[i].ppp_if_fd); } + if (dhcpv6fd != -1) close(dhcpv6fd); if (snoopfd != -1) close(snoopfd); if (rand_fd != -1) close(rand_fd); if (epollfd != -1) close(epollfd);