Add DHCPv6 support with acceleration

This commit is contained in:
Samuel Thibault 2023-05-08 03:12:45 +02:00
parent a9e18411d3
commit 5dcbd68b75
5 changed files with 164 additions and 5 deletions

137
dhcp6.c
View file

@ -4,13 +4,21 @@
* GPL licenced * GPL licenced
*/ */
#define _GNU_SOURCE
#include <netinet/icmp6.h> #include <netinet/icmp6.h>
#include <netinet/ip6.h> #include <netinet/ip6.h>
#include <netinet/udp.h> #include <netinet/udp.h>
#include <netinet/in.h>
#include <time.h>
#include <errno.h>
#include "dhcp6.h" #include "dhcp6.h"
#include "l2tpns.h" #include "l2tpns.h"
#include "ipv6_u.h" #include "ipv6_u.h"
#include "cluster.h"
#include "util.h"
int dhcpv6fd;
struct dhcp6_in_option struct dhcp6_in_option
{ {
@ -538,7 +546,7 @@ void dhcpv6_process_from_ipv6(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l)
return; 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"); LOG(5, 0, 0, "not UDP DHCP packet??\n");
return; return;
@ -597,6 +605,8 @@ static int dhcpv6_format_dns_search_name(const char *strdns, uint8_t *buffer)
void dhcpv6_init(void) void dhcpv6_init(void)
{ {
uint32_t id; 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.code = htons(D6_OPT_SERVERID);
dhcp6_local_serverid.opt_hdr.len = htons(4 + sizeof(id)); dhcp6_local_serverid.opt_hdr.len = htons(4 + sizeof(id));
@ -609,4 +619,129 @@ void dhcpv6_init(void)
id = htobe32(0xFDFDFAFA); id = htobe32(0xFDFDFAFA);
memcpy(dhcp6_local_serverid.duid.u.ll.addr, &id, sizeof(id)); 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;
}
} }

View file

@ -7,6 +7,10 @@
#ifndef __DHCP6_H__ #ifndef __DHCP6_H__
#define __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_SOLICIT 1
#define DHCP6_ADVERTISE 2 #define DHCP6_ADVERTISE 2
#define DHCP6_REQUEST 3 #define DHCP6_REQUEST 3
@ -212,7 +216,10 @@ struct dhcp6_opt_ia_prefix {
} __attribute__((packed)); } __attribute__((packed));
// dhcp6.c // dhcp6.c
extern int dhcpv6fd;
void dhcpv6_process_from_ipv6(uint16_t s, uint16_t t, uint8_t *p, uint16_t l); void dhcpv6_process_from_ipv6(uint16_t s, uint16_t t, uint8_t *p, uint16_t l);
void dhcpv6_init(void); void dhcpv6_init(void);
void dhcpv6_listen(int ifidx);
void dhcpv6_process_from_kernel(uint8_t *p, size_t size_bufp);
#endif /* __DHCP6_H__ */ #endif /* __DHCP6_H__ */

View file

@ -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_fd = ppp_if_fd;
sess_local[s].ppp_if_idx = ifr.ifr_ifindex; 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)); memset(&sess_local[s].last_stats, 0, sizeof(sess_local[s].last_stats));
return 0; return 0;
@ -5447,8 +5449,8 @@ static int still_busy(void)
return 0; return 0;
} }
// the base set of fds polled: cli, cluster, tun, udp (MAX_UDPFD), control, dae, netlink, udplac, pppoedisc, pppoesess, kernel ppp // the base set of fds polled: cli, cluster, tun, udp (MAX_UDPFD), control, dae, netlink, udplac, pppoedisc, pppoesess, dhcpv6
#define BASE_FDS (9 + MAX_UDPFD) #define BASE_FDS (10 + MAX_UDPFD)
// additional polled fds // additional polled fds
#ifdef BGP #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) #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 // main loop - gets packets on tun or udp and processes them
static void mainloop(void) static void mainloop(void)
{ {
int i, j; int i, j;
uint8_t buf[65536]; 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 // 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 clockt next_cluster_ping = 0; // send initial ping immediately
struct epoll_event events[MAX_FDS]; struct epoll_event events[MAX_FDS];
int maxevent = sizeof(events)/sizeof(*events); int maxevent = sizeof(events)/sizeof(*events);
@ -5528,6 +5533,10 @@ static void mainloop(void)
e.data.ptr = &d[i++]; e.data.ptr = &d[i++];
epoll_ctl(epollfd, EPOLL_CTL_ADD, pppoesessfd, &e); 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++) for (j = 0; j < config->nbudpfd; j++)
{ {
d[i].type = FD_TYPE_UDP; d[i].type = FD_TYPE_UDP;
@ -5787,6 +5796,12 @@ static void mainloop(void)
break; break;
} }
case FD_TYPE_DHCPV6:
{
dhcpv6_process_from_kernel(p, size_bufp);
break;
}
default: default:
LOG(0, 0, 0, "Unexpected fd type returned from epoll_wait: %d\n", d->type); LOG(0, 0, 0, "Unexpected fd type returned from epoll_wait: %d\n", d->type);
} }

View file

@ -1086,6 +1086,7 @@ struct event_data {
FD_TYPE_PPPOX, FD_TYPE_PPPOX,
FD_TYPE_PPP_CHAN, FD_TYPE_PPP_CHAN,
FD_TYPE_PPP_IF, FD_TYPE_PPP_IF,
FD_TYPE_DHCPV6,
} type; } type;
int index; // for RADIUS, BGP, UDP int index; // for RADIUS, BGP, UDP
}; };

1
util.c
View file

@ -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 (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 (snoopfd != -1) close(snoopfd);
if (rand_fd != -1) close(rand_fd); if (rand_fd != -1) close(rand_fd);
if (epollfd != -1) close(epollfd); if (epollfd != -1) close(epollfd);