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
*/
#define _GNU_SOURCE
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
#include <netinet/in.h>
#include <time.h>
#include <errno.h>
#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;
}
}