Tell the kernel not to attempt routing table lookups for sent DHCP traffic:

- Use SO_DONTROUTE for dhcp listen and send sockets (both raw and udp).

More paranoia against packets being sent to incorrect interfaces:
- Bind arp socket to a specific interface via bind().
- Use SO_BINDTODEVICE for udp send sockets.  It was already used for udp
  listen sockets.

Flatten indentation in arp_(open|close)_fd().
Use C99 initializers in packet.c.
Add more error message prints to packet.c.
This commit is contained in:
Nicholas J. Kain 2011-06-25 16:31:21 -04:00
parent c37d815754
commit 5b3aee93ab
2 changed files with 158 additions and 109 deletions

View File

@ -47,40 +47,50 @@ static int arp_packet_num;
static int arp_open_fd(struct client_state_t *cs) static int arp_open_fd(struct client_state_t *cs)
{ {
if (cs->arpFd == -1) { if (cs->arpFd != -1)
int arpfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); return 0;
if (arpfd == -1) {
log_error("arp: failed to create socket: %s", strerror(errno));
return -1;
}
int opt = 1; int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (setsockopt(arpfd, SOL_SOCKET, SO_BROADCAST, if (fd == -1) {
&opt, sizeof opt) == -1) { log_error("arp: failed to create socket: %s", strerror(errno));
log_error("arp: failed to set broadcast: %s", strerror(errno)); goto out;
close(arpfd);
return -1;
}
if (fcntl(arpfd, F_SETFL, fcntl(arpfd, F_GETFL) | O_NONBLOCK) == -1) {
log_error("arp: failed to set non-blocking: %s", strerror(errno));
close(arpfd);
return -1;
}
cs->arpFd = arpfd;
epoll_add(cs, arpfd);
} }
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) {
log_error("arp: failed to set broadcast: %s", strerror(errno));
goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
log_error("arp: failed to set non-blocking: %s", strerror(errno));
goto out_fd;
}
struct sockaddr_ll saddr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_ARP),
.sll_ifindex = client_config.ifindex,
};
if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_ll)) < 0) {
log_error("arp: bind failed: %s", strerror(errno));
goto out_fd;
}
cs->arpFd = fd;
epoll_add(cs, fd);
return 0; return 0;
out_fd:
close(fd);
out:
return -1;
} }
static int arp_close_fd(struct client_state_t *cs) static void arp_close_fd(struct client_state_t *cs)
{ {
if (cs->arpFd != -1) { if (cs->arpFd == -1)
epoll_del(cs, cs->arpFd); return;
close(cs->arpFd); epoll_del(cs, cs->arpFd);
cs->arpFd = -1; close(cs->arpFd);
return 1; cs->arpFd = -1;
}
return 0;
} }
// Returns 0 on success, -1 on failure. // Returns 0 on success, -1 on failure.

View File

@ -33,6 +33,7 @@
#include <features.h> #include <features.h>
#include <netpacket/packet.h> #include <netpacket/packet.h>
#include <net/ethernet.h> #include <net/ethernet.h>
#include <net/if.h>
#include <linux/filter.h> #include <linux/filter.h>
#include <time.h> #include <time.h>
#include <errno.h> #include <errno.h>
@ -50,39 +51,50 @@
* on success, or -1 on failure. */ * on success, or -1 on failure. */
static int create_udp_listen_socket(unsigned int ip, int port, char *inf) static int create_udp_listen_socket(unsigned int ip, int port, char *inf)
{ {
struct ifreq interface;
int fd;
struct sockaddr_in addr;
int opt = 1;
log_line("Opening listen socket on 0x%08x:%d %s", ip, port, inf); log_line("Opening listen socket on 0x%08x:%d %s", ip, port, inf);
int fd;
if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
log_error("create_udp_listen_socket: socket failed: %s", log_error("create_udp_listen_socket: socket failed: %s",
strerror(errno)); strerror(errno));
goto out; goto out;
} }
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
log_error("create_udp_listen_socket: set reuse addr failed: %s",
strerror(errno));
goto out_fd; goto out_fd;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) }
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) {
log_error("create_udp_listen_socket: set broadcast failed: %s",
strerror(errno));
goto out_fd; goto out_fd;
}
/* Restrict operations to the physical device @inf */ if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) {
strlcpy(interface.ifr_ifrn.ifrn_name, inf, IFNAMSIZ); log_error("create_udp_listen_socket: set don't route failed: %s",
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, strerror(errno));
&interface, sizeof interface) < 0)
goto out_fd; goto out_fd;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof (struct ifreq));
strlcpy(ifr.ifr_name, inf, IFNAMSIZ);
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) {
log_error("create_udp_listen_socket: set bind to device failed: %s",
strerror(errno));
goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
log_error("create_udp_listen_socket: set non-blocking failed: %s", log_error("create_udp_listen_socket: set non-blocking failed: %s",
strerror(errno)); strerror(errno));
goto out_fd; goto out_fd;
} }
memset(&addr, 0, sizeof addr); struct sockaddr_in addr = {
addr.sin_family = AF_INET; .sin_family = AF_INET,
addr.sin_port = htons(port); .sin_port = htons(port),
addr.sin_addr.s_addr = ip; .sin_addr.s_addr = ip,
};
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1)
goto out_fd; goto out_fd;
@ -95,9 +107,6 @@ static int create_udp_listen_socket(unsigned int ip, int port, char *inf)
static int create_raw_listen_socket(int ifindex) static int create_raw_listen_socket(int ifindex)
{ {
int fd;
struct sockaddr_ll sock;
/* /*
* Comment: * Comment:
* I've selected not to see LL header, so BPF doesn't see it, too. * I've selected not to see LL header, so BPF doesn't see it, too.
@ -132,35 +141,46 @@ static int create_raw_listen_socket(int ifindex)
.filter = (struct sock_filter *) filter_instr, .filter = (struct sock_filter *) filter_instr,
}; };
memset(&sock, 0, sizeof sock);
log_line("Opening raw socket on ifindex %d", ifindex); log_line("Opening raw socket on ifindex %d", ifindex);
int fd;
if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) {
log_error("socket call failed: %s", strerror(errno)); log_error("create_raw_listen_socket: socket failed: %s",
return -1; strerror(errno));
goto out;
} }
/* Ignoring error (kernel may lack support for this) */ // Ignoring error since kernel may lack support for BPF.
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog,
sizeof filter_prog) >= 0) sizeof filter_prog) >= 0)
log_line("Attached filter to raw socket fd %d", fd); log_line("Attached filter to raw socket fd %d", fd);
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) {
log_error("create_raw_listen_socket: failed to set don't route: %s",
strerror(errno));
goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
log_error("create_raw_listen_socket: set non-blocking failed: %s", log_error("create_raw_listen_socket: set non-blocking failed: %s",
strerror(errno)); strerror(errno));
close(fd); goto out_fd;
return -1;
} }
struct sockaddr_ll sock = {
sock.sll_family = AF_PACKET; .sll_family = AF_PACKET,
sock.sll_protocol = htons(ETH_P_IP); .sll_protocol = htons(ETH_P_IP),
sock.sll_ifindex = ifindex; .sll_ifindex = ifindex,
};
if (bind(fd, (struct sockaddr *)&sock, sizeof(sock)) < 0) { if (bind(fd, (struct sockaddr *)&sock, sizeof(sock)) < 0) {
log_error("bind call failed: %s", strerror(errno)); log_error("create_raw_listen_socket: bind failed: %s",
close(fd); strerror(errno));
return -1; goto out_fd;
} }
return fd; return fd;
out_fd:
close(fd);
out:
return -1;
} }
// Read a packet from a cooked socket. Returns -1 on fatal error, -2 on // Read a packet from a cooked socket. Returns -1 on fatal error, -2 on
// transient error. // transient error.
@ -283,63 +303,56 @@ int raw_packet(struct dhcpMessage *payload, uint32_t source_ip,
int source_port, uint32_t dest_ip, int dest_port, int source_port, uint32_t dest_ip, int dest_port,
uint8_t *dest_arp, int ifindex) uint8_t *dest_arp, int ifindex)
{ {
struct sockaddr_ll dest;
struct ip_udp_dhcp_packet packet;
int fd, r = -1; int fd, r = -1;
unsigned int padding;
if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) {
log_error("raw_packet: socket failed: %s", strerror(errno)); log_error("raw_packet: socket failed: %s", strerror(errno));
goto out; goto out;
} }
int opt = 1;
memset(&dest, 0, sizeof dest); if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) {
memset(&packet, 0, offsetof(struct ip_udp_dhcp_packet, data)); log_error("raw_packet: failed to set don't route: %s",
packet.data = *payload; /* struct copy */ strerror(errno));
goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
log_error("raw_packet: set non-blocking failed: %s", strerror(errno)); log_error("raw_packet: set non-blocking failed: %s", strerror(errno));
goto out_fd; goto out_fd;
} }
struct sockaddr_ll dest = {
dest.sll_family = AF_PACKET; .sll_family = AF_PACKET,
dest.sll_protocol = htons(ETH_P_IP); .sll_protocol = htons(ETH_P_IP),
dest.sll_ifindex = ifindex; .sll_ifindex = ifindex,
dest.sll_halen = 6; .sll_halen = 6,
};
memcpy(dest.sll_addr, dest_arp, 6); memcpy(dest.sll_addr, dest_arp, 6);
if (bind(fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_ll)) < 0) { if (bind(fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_ll)) < 0) {
log_error("raw_packet: bind failed: %s", strerror(errno)); log_error("raw_packet: bind failed: %s", strerror(errno));
goto out_fd; goto out_fd;
} }
/* We were sending full-sized DHCP packets (zero padded), struct ip_udp_dhcp_packet packet;
* but some badly configured servers were seen dropping them. memset(&packet, 0, offsetof(struct ip_udp_dhcp_packet, data));
* Apparently they drop all DHCP packets >576 *ethernet* octets big, packet.data = *payload;
* whereas they may only drop packets >576 *IP* octets big // Send packets that are as short as possible, since some servers are buggy
* (which for typical Ethernet II means 590 octets: 6+6+2 + 576). // and drop packets that are longer than 562 bytes.
*
* In order to work with those buggy servers,
* we truncate packets after end option byte.
*/
ssize_t endloc = get_end_option_idx(packet.data.options, ssize_t endloc = get_end_option_idx(packet.data.options,
DHCP_OPTIONS_BUFSIZE); DHCP_OPTIONS_BUFSIZE);
if (endloc == -1) { if (endloc == -1) {
log_error("raw_packet: attempt to send packet with no DHCP_END"); log_error("raw_packet: attempt to send packet with no DHCP_END");
goto out_fd; goto out_fd;
} }
padding = DHCP_OPTIONS_BUFSIZE - 1 - endloc; unsigned int padding = DHCP_OPTIONS_BUFSIZE - 1 - endloc;
packet.ip.protocol = IPPROTO_UDP; packet.ip.protocol = IPPROTO_UDP;
packet.ip.saddr = source_ip; packet.ip.saddr = source_ip;
packet.ip.daddr = dest_ip; packet.ip.daddr = dest_ip;
packet.udp.source = htons(source_port); packet.udp.source = htons(source_port);
packet.udp.dest = htons(dest_port); packet.udp.dest = htons(dest_port);
/* size, excluding IP header: */
packet.udp.len = htons(UPD_DHCP_SIZE - padding); packet.udp.len = htons(UPD_DHCP_SIZE - padding);
/* for UDP checksumming, ip.len is set to UDP packet len */ // UDP checksumming needs a temporary pseudoheader with a fake length.
packet.ip.tot_len = packet.udp.len; packet.ip.tot_len = packet.udp.len;
packet.udp.check = checksum(&packet, IP_UPD_DHCP_SIZE - padding); packet.udp.check = checksum(&packet, IP_UPD_DHCP_SIZE - padding);
/* but for sending, it is set to IP packet len */ // Set the true IP packet length for the final packet.
packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE - padding); packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE - padding);
packet.ip.ihl = sizeof packet.ip >> 2; packet.ip.ihl = sizeof packet.ip >> 2;
packet.ip.version = IPVERSION; packet.ip.version = IPVERSION;
@ -360,31 +373,57 @@ int raw_packet(struct dhcpMessage *payload, uint32_t source_ip,
int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip, int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip,
int source_port, uint32_t dest_ip, int dest_port) int source_port, uint32_t dest_ip, int dest_port)
{ {
struct sockaddr_in client; int fd, result = -1;
int opt = 1, fd, result = -1;
unsigned int padding;
if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
log_error("kernel_packet: socket failed: %s", strerror(errno));
goto out; goto out;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
log_error("kernel_packet: set reuse addr failed: %s",
strerror(errno));
goto out_fd; goto out_fd;
}
memset(&client, 0, sizeof(client)); if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) {
client.sin_family = AF_INET; log_error("kernel_packet: failed to set don't route: %s",
client.sin_port = htons(source_port); strerror(errno));
client.sin_addr.s_addr = source_ip;
if (bind(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1)
goto out_fd; goto out_fd;
}
memset(&client, 0, sizeof(client)); struct ifreq ifr;
client.sin_family = AF_INET; memset(&ifr, 0, sizeof (struct ifreq));
client.sin_port = htons(dest_port); strlcpy(ifr.ifr_name, client_config.interface, IFNAMSIZ);
client.sin_addr.s_addr = dest_ip; if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) {
log_error("kernel_packet: set bind to device failed: %s",
if (connect(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) strerror(errno));
goto out_fd; goto out_fd;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
log_error("kernel_packet: set non-blocking failed: %s",
strerror(errno));
goto out_fd;
}
struct sockaddr_in laddr = {
.sin_family = AF_INET,
.sin_port = htons(source_port),
.sin_addr.s_addr = source_ip,
};
if (bind(fd, (struct sockaddr *)&laddr, sizeof(struct sockaddr)) == -1) {
log_error("kernel_packet: bind failed: %s", strerror(errno));
goto out_fd;
}
struct sockaddr_in raddr = {
.sin_family = AF_INET,
.sin_port = htons(dest_port),
.sin_addr.s_addr = dest_ip,
};
if (connect(fd, (struct sockaddr *)&raddr, sizeof(struct sockaddr)) == -1) {
log_error("kernel_packet: connect failed: %s", strerror(errno));
goto out_fd;
}
ssize_t endloc = get_end_option_idx(payload->options, ssize_t endloc = get_end_option_idx(payload->options,
DHCP_OPTIONS_BUFSIZE); DHCP_OPTIONS_BUFSIZE);
@ -392,7 +431,7 @@ int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip,
log_error("kernel_packet: attempt to send packet with no DHCP_END"); log_error("kernel_packet: attempt to send packet with no DHCP_END");
goto out_fd; goto out_fd;
} }
padding = DHCP_OPTIONS_BUFSIZE - 1 - endloc; unsigned int padding = DHCP_OPTIONS_BUFSIZE - 1 - endloc;
result = safe_write(fd, (const char *)payload, DHCP_SIZE - padding); result = safe_write(fd, (const char *)payload, DHCP_SIZE - padding);
if (result == -1) if (result == -1)
log_error("kernel_packet: write failed: %s", strerror(errno)); log_error("kernel_packet: write failed: %s", strerror(errno));