NetworkManager/shared/n-dhcp4/src/n-dhcp4-socket.c
Thomas Haller cf7662bc52 n-dhcp4/socket: use SO_REUSEADDR on UDP socket
Otherwise, other applications cannot bind to port 0.0.0.0:68 at the same time.
This is for example what dhclient wants to do. So even when running
dhclient on another, unrelated interface, it would fail to bind the UDP
socket and quit.

Note that also systemd-networkd's DHCPv4 client sets this socket option.
Presumably for the same reasons.

Signed-off-by: Thomas Haller <thaller@redhat.com>

https://github.com/nettools/n-dhcp4/pull/12
(cherry picked from commit 53b74bc614)
2019-12-11 09:25:05 +01:00

661 lines
26 KiB
C

/*
* DHCP specific low-level socket helpers
*/
#include <c-stdaux.h>
#include <errno.h>
#include <linux/filter.h>
#include <sys/socket.h> /* needed by linux/if.h */
#include <linux/if.h>
#include <linux/if_packet.h>
#include <linux/netdevice.h>
#include <linux/udp.h>
#include <netinet/ip.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "n-dhcp4-private.h"
#include "util/packet.h"
#include "util/socket.h"
/**
* n_dhcp4_c_socket_packet_new() - create a new DHCP4 client packet socket
* @sockfdp: return argumnet for the new socket
* @ifindex: interface index to bind to
*
* Create a new AF_PACKET/SOCK_DGRAM socket usable to listen to and send DHCP client
* packets before an IP address has been configured.
*
* Only unfragmented DHCP packets from a server to a client destined for the given
* ifindex is returned.
*
* Return: 0 on success, or a negative error code on failure.
*/
int n_dhcp4_c_socket_packet_new(int *sockfdp, int ifindex) {
_c_cleanup_(c_closep) int sockfd = -1;
struct sock_filter filter[] = {
/*
* IP
*
* Check
* - UDP
* - Unfragmented
* - Large enough to fit the DHCP header
*
* Leave X the size of the IP header, for future indirect reads.
*/
BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- IP protocol */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, frag_off)), /* A <- Flags */
BPF_STMT(BPF_ALU + BPF_AND + BPF_K, ntohs(IP_MF | IP_OFFMASK)), /* A <- A & (IP_MF | IP_OFFMASK) */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* fragmented packet ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */
BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
BPF_STMT(BPF_ALU + BPF_SUB + BPF_X, 0), /* A -= X */
BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct udphdr) + sizeof(NDhcp4Message), 1, 0), /* packet >= DHCPPacket ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
/*
* UDP
*
* Check
* - DHCP client port
*
* Leave X the size of IP and UDP headers, for future indirect reads.
*/
BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct udphdr, dest)), /* A <- UDP destination port */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_NETWORK_CLIENT_PORT, 1, 0), /* UDP destination port == DHCP client port ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */
BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */
BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
/*
* DHCP
*
* Check
* - BOOTREPLY (from server to client)
* - DHCP magic cookie
*/
BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
};
struct sock_fprog fprog = {
.filter = filter,
.len = sizeof(filter) / sizeof(filter[0]),
};
struct sockaddr_ll addr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_ifindex = ifindex,
};
int r, on = 1;
sockfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (sockfd < 0)
return -errno;
r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
if (r < 0)
return -errno;
/* We need the flag that tells us if the checksum is correct. */
r = setsockopt(sockfd, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on));
if (r < 0)
return -errno;
r = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if (r < 0)
return -errno;
*sockfdp = sockfd;
sockfd = -1;
return 0;
}
/**
* n_dhcp4_c_socket_udp_new() - create a new DHCP4 client UDP socket
* @sockfdp: return argumnet for the new socket
* @ifindex: interface index to bind to
* @client_addr: client address to bind to
* @server_addr: server address to connect to
*
* Create a new AF_INET/SOCK_DGRAM socket usable to listen to and send DHCP client
* packets.
*
* The client address given in @addr must be configured on the interface @ifindex
* before the socket is created.
*
* Return: 0 on success, or a negative error code on failure.
*/
int n_dhcp4_c_socket_udp_new(int *sockfdp,
int ifindex,
const struct in_addr *client_addr,
const struct in_addr *server_addr) {
_c_cleanup_(c_closep) int sockfd = -1;
struct sock_filter filter[] = {
/*
* IP/UDP
*
* Set X to the size of IP and UDP headers, for future indirect reads.
*/
BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */
BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */
BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */
BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
/*
* DHCP
*
* Check
* - BOOTREPLY (from server to client)
* - DHCP magic cookie
*/
BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
};
struct sock_fprog fprog = {
.filter = filter,
.len = sizeof(filter) / sizeof(filter[0]),
};
struct sockaddr_in saddr = {
.sin_family = AF_INET,
.sin_addr = *client_addr,
.sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
};
struct sockaddr_in daddr = {
.sin_family = AF_INET,
.sin_addr = *server_addr,
.sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
};
int r, tos = IPTOS_CLASS_CS6, on = 1;
sockfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (sockfd < 0)
return -errno;
r = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (r < 0)
return -errno;
r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
if (r < 0)
return -errno;
r = socket_bind_if(sockfd, ifindex);
if (r)
return r;
r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
if (r < 0)
return -errno;
r = setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (r < 0)
return -errno;
r = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (r < 0)
return -errno;
r = connect(sockfd, (struct sockaddr*)&daddr, sizeof(daddr));
if (r < 0)
return -errno;
*sockfdp = sockfd;
sockfd = -1;
return 0;
}
/**
* n_dhcp4_s_socket_packet_new() - create a new DHCP4 server packet socket
* @sockfdp: return argumnet for the new socket
*
* Create a new AF_PACKET/SOCK_DGRAM socket usable to send DHCP packets to clients
* before they have an IP address configured, on the given interface.
*
* Return: 0 on success, or a negative error code on failure.
*/
int n_dhcp4_s_socket_packet_new(int *sockfdp) {
_c_cleanup_(c_closep) int sockfd = -1;
sockfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (sockfd < 0)
return -errno;
*sockfdp = sockfd;
sockfd = -1;
return 0;
}
/**
* n_dhcp4_s_socket_udp_new() - create a new DHCP4 server UDP socket
* @sockfdp: return argumnet for the new socket
* @ifindex: intercafe index to bind to
*
* Create a new AF_INET/SOCK_DGRAM socket usable to listen to DHCP server packets,
* on the given interface.
*
* Return: 0 on success, or a negative error code on failure.
*/
int n_dhcp4_s_socket_udp_new(int *sockfdp, int ifindex) {
_c_cleanup_(c_closep) int sockfd = -1;
struct sock_filter filter[] = {
/*
* IP/UDP
*
* Set X to the size of IP and UDP headers, for future indirect reads.
*/
BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */
BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */
BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */
BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
/*
* DHCP
*
* Check
* - BOOTREQUEST (from client to server)
* - DHCP magic cookie
*/
BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREQUEST, 1, 0), /* op == BOOTREQUEST ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
};
struct sock_fprog fprog = {
.filter = filter,
.len = sizeof(filter) / sizeof(filter[0]),
};
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr = { INADDR_ANY },
.sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
};
int r, tos = IPTOS_CLASS_CS6, on = 1;
sockfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (sockfd < 0)
return -errno;
r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
if (r < 0)
return -errno;
r = socket_bind_if(sockfd, ifindex);
if (r)
return r;
r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
if (r < 0)
return -errno;
r = setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (r < 0)
return -errno;
r = setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
if (r < 0)
return -errno;
r = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if (r < 0)
return -errno;
*sockfdp = sockfd;
sockfd = -1;
return 0;
}
static int n_dhcp4_socket_packet_send(int sockfd,
int ifindex,
const struct sockaddr_in *src_paddr,
const unsigned char *dest_haddr,
unsigned char halen,
const struct sockaddr_in *dest_paddr,
NDhcp4Outgoing *message) {
struct packet_sockaddr_ll haddr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_ifindex = ifindex,
.sll_halen = halen,
};
const void *buf;
size_t n_buf, len;
int r;
c_assert(halen <= sizeof(haddr.sll_addr));
memcpy(haddr.sll_addr, dest_haddr, halen);
n_buf = n_dhcp4_outgoing_get_raw(message, &buf);
r = packet_sendto_udp(sockfd, buf, n_buf, &len, src_paddr, &haddr, dest_paddr);
if (r < 0) {
if (r == -EAGAIN || r == -ENOBUFS)
return N_DHCP4_E_DROPPED;
else if (r == -ENETDOWN || r == -ENXIO)
return N_DHCP4_E_DOWN;
else
return r;
} else if (len != n_buf) {
return N_DHCP4_E_DROPPED;
}
return 0;
}
/**
* n_dhcp4_c_socket_packet_send() - XXX
*/
int n_dhcp4_c_socket_packet_send(int sockfd,
int ifindex,
const unsigned char *dest_haddr,
unsigned char halen,
NDhcp4Outgoing *message) {
struct sockaddr_in src_paddr = {
.sin_family = AF_INET,
.sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
.sin_addr = { INADDR_ANY },
};
struct sockaddr_in dest_paddr = {
.sin_family = AF_INET,
.sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
.sin_addr = { INADDR_BROADCAST }
};
return n_dhcp4_socket_packet_send(sockfd,
ifindex,
&src_paddr,
dest_haddr,
halen,
&dest_paddr,
message);
}
/**
* n_dhcp4_c_socket_udp_send() - XXX
*/
int n_dhcp4_c_socket_udp_send(int sockfd,
NDhcp4Outgoing *message) {
const void *buf;
size_t n_buf;
ssize_t len;
n_buf = n_dhcp4_outgoing_get_raw(message, &buf);
len = send(sockfd, buf, n_buf, 0);
if (len < 0) {
if (errno == EAGAIN || errno == ENOBUFS)
return N_DHCP4_E_DROPPED;
else if (errno == ENETDOWN || errno == ENXIO)
return N_DHCP4_E_DOWN;
else
return -errno;
} else if ((size_t)len != n_buf)
return N_DHCP4_E_DROPPED;
return 0;
}
/**
* n_dhcp4_c_socket_udp_broadcast() - XXX
*/
int n_dhcp4_c_socket_udp_broadcast(int sockfd, NDhcp4Outgoing *message) {
struct sockaddr_in sockaddr_dest = {
.sin_family = AF_INET,
.sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
.sin_addr = { INADDR_BROADCAST },
};
const void *buf;
size_t n_buf;
ssize_t len;
n_buf = n_dhcp4_outgoing_get_raw(message, &buf);
len = sendto(sockfd,
buf,
n_buf,
0,
(struct sockaddr*)&sockaddr_dest,
sizeof(sockaddr_dest));
if (len < 0) {
if (errno == EAGAIN || errno == ENOBUFS)
return N_DHCP4_E_DROPPED;
else if (errno == ENETDOWN || errno == ENXIO)
return N_DHCP4_E_DOWN;
else
return -errno;
} else if ((size_t)len != n_buf)
return N_DHCP4_E_DROPPED;
return 0;
}
/**
* n_dhcp4_s_socket_packet_send() - XXX
*/
int n_dhcp4_s_socket_packet_send(int sockfd,
int ifindex,
const struct in_addr *src_inaddr,
const unsigned char *dest_haddr,
unsigned char halen,
const struct in_addr *dest_inaddr,
NDhcp4Outgoing *message) {
struct sockaddr_in src_paddr = {
.sin_family = AF_INET,
.sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
.sin_addr = *src_inaddr,
};
struct sockaddr_in dest_paddr = {
.sin_family = AF_INET,
.sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
.sin_addr = *dest_inaddr,
};
return n_dhcp4_socket_packet_send(sockfd,
ifindex,
&src_paddr,
dest_haddr,
halen,
&dest_paddr,
message);
}
/**
* n_dhcp4_s_socket_udp_send() - XXX
*/
int n_dhcp4_s_socket_udp_send(int sockfd,
const struct in_addr *inaddr_src,
const struct in_addr *inaddr_dest,
NDhcp4Outgoing *message) {
struct sockaddr_in sockaddr_dest = {
.sin_family = AF_INET,
.sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
.sin_addr = *inaddr_dest,
};
struct iovec iov = {};
union {
struct cmsghdr align; /* ensure correct stack alignment */
char buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
} control = {};
struct in_pktinfo pktinfo = {
.ipi_spec_dst = *inaddr_src,
};
struct msghdr msg = {
.msg_name = (void*)&sockaddr_dest,
.msg_namelen = sizeof(sockaddr_dest),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = &control.buf,
.msg_controllen = sizeof(control.buf),
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
ssize_t len;
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo));
iov.iov_len = n_dhcp4_outgoing_get_raw(message, (const void **)&iov.iov_base);
len = sendmsg(sockfd, &msg, 0);
if (len < 0) {
if (errno == EAGAIN || errno == ENOBUFS)
return N_DHCP4_E_DROPPED;
else if (errno == ENETDOWN || errno == ENXIO)
return N_DHCP4_E_DOWN;
else
return -errno;
} else if ((size_t)len != iov.iov_len)
return N_DHCP4_E_DROPPED;
return 0;
}
int n_dhcp4_s_socket_udp_broadcast(int sockfd,
const struct in_addr *inaddr_src,
NDhcp4Outgoing *message) {
return n_dhcp4_s_socket_udp_send(sockfd,
inaddr_src,
&(const struct in_addr){INADDR_BROADCAST},
message);
}
int n_dhcp4_c_socket_packet_recv(int sockfd,
uint8_t *buf,
size_t n_buf,
NDhcp4Incoming **messagep) {
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
size_t len;
int r;
r = packet_recv_udp(sockfd, buf, n_buf, &len);
if (r < 0) {
if (r == -ENETDOWN)
return N_DHCP4_E_DOWN;
else if (r == -EAGAIN)
return N_DHCP4_E_AGAIN;
else
return -errno;
} else if (len == 0) {
return N_DHCP4_E_MALFORMED;
}
r = n_dhcp4_incoming_new(&message, buf, len);
if (r)
return r;
*messagep = message;
message = NULL;
return 0;
}
static int n_dhcp4_socket_udp_recv(int sockfd,
uint8_t *buf,
size_t n_buf,
NDhcp4Incoming **messagep,
struct in_pktinfo *pktinfo) {
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
struct iovec iov = {
.iov_base = buf,
.iov_len = n_buf,
};
uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsgbuf,
.msg_controllen = sizeof(cmsgbuf),
};
ssize_t len;
int r;
len = recvmsg(sockfd, &msg, MSG_TRUNC);
if (len < 0) {
if (errno == ENETDOWN)
return N_DHCP4_E_DOWN;
else if (errno == EAGAIN)
return N_DHCP4_E_AGAIN;
else
return -errno;
} else if (len == 0 || (size_t)len > n_buf) {
return N_DHCP4_E_MALFORMED;
}
r = n_dhcp4_incoming_new(&message, buf, len);
if (r)
return r;
if (pktinfo) {
struct cmsghdr *cmsg;
cmsg = CMSG_FIRSTHDR(&msg);
c_assert(cmsg);
c_assert(cmsg->cmsg_level == IPPROTO_IP);
c_assert(cmsg->cmsg_type == IP_PKTINFO);
c_assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo)));
memcpy(pktinfo, (void*)CMSG_DATA(cmsg), sizeof(struct in_pktinfo));
}
*messagep = message;
message = NULL;
return 0;
}
int n_dhcp4_c_socket_udp_recv(int sockfd,
uint8_t *buf,
size_t n_buf,
NDhcp4Incoming **messagep) {
return n_dhcp4_socket_udp_recv(sockfd, buf, n_buf, messagep, NULL);
}
int n_dhcp4_s_socket_udp_recv(int sockfd,
uint8_t *buf,
size_t n_buf,
NDhcp4Incoming **messagep,
struct sockaddr_in *dest) {
struct in_pktinfo pktinfo = {};
int r;
r = n_dhcp4_socket_udp_recv(sockfd, buf, n_buf, messagep, &pktinfo);
if (r)
return r;
if (dest) {
dest->sin_family = AF_INET;
dest->sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT);
dest->sin_addr.s_addr = pktinfo.ipi_addr.s_addr;
}
return 0;
}