Have sockd apply BPF filters to ARP sockets.
This commit is contained in:
parent
5212e0dfc5
commit
6750209e12
159
ndhc/arp.c
159
ndhc/arp.c
@ -25,6 +25,7 @@
|
|||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -123,95 +124,51 @@ void arp_reset_send_stats(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void arp_set_bpf_basic(int fd)
|
static int get_arp_basic_socket(void)
|
||||||
{
|
{
|
||||||
static struct sock_filter sf_arp[] = {
|
char resp;
|
||||||
// Verify that the frame has ethernet protocol type of ARP
|
int fd = request_sockd_fd("a", 1, &resp);
|
||||||
// and that the ARP hardware type field indicates Ethernet.
|
switch (resp) {
|
||||||
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12),
|
case 'A': using_arp_bpf = 1; break;
|
||||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_ARP << 16) | ARPHRD_ETHER,
|
case 'a': using_arp_bpf = 0; break;
|
||||||
1, 0),
|
default: suicide("%s: (%s) expected a or A sockd reply but got %c",
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0),
|
client_config.interface, __func__, resp);
|
||||||
// Verify that the ARP protocol type field indicates IP, the ARP
|
}
|
||||||
// hardware address length field is 6, and the ARP protocol address
|
return fd;
|
||||||
// length field is 4.
|
|
||||||
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 16),
|
|
||||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_IP << 16) | 0x0604, 1, 0),
|
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0),
|
|
||||||
// Sanity tests passed, so send all possible data.
|
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
|
|
||||||
};
|
|
||||||
static const struct sock_fprog sfp_arp = {
|
|
||||||
.len = sizeof sf_arp / sizeof sf_arp[0],
|
|
||||||
.filter = sf_arp,
|
|
||||||
};
|
|
||||||
using_arp_bpf = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_arp,
|
|
||||||
sizeof sfp_arp) != -1;
|
|
||||||
if (!using_arp_bpf)
|
|
||||||
log_warning("%s: Failed to set BPF for ARP socket: %s",
|
|
||||||
client_config.interface, strerror(errno));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void arp_set_bpf_defense(struct client_state_t *cs, int fd)
|
static int get_arp_defense_socket(struct client_state_t *cs)
|
||||||
{
|
{
|
||||||
uint32_t mac4b;
|
char buf[32];
|
||||||
uint16_t mac2b;
|
size_t buflen = 0;
|
||||||
memcpy(&mac4b, client_config.arp, 4);
|
buf[0] = 'd';
|
||||||
memcpy(&mac2b, client_config.arp+4, 2);
|
buflen += 1;
|
||||||
|
memcpy(buf + buflen, &cs->clientAddr, sizeof cs->clientAddr);
|
||||||
struct sock_filter sf_arp[] = {
|
buflen += sizeof cs->clientAddr;
|
||||||
// Verify that the frame has ethernet protocol type of ARP
|
memcpy(buf + buflen, client_config.arp, 6);
|
||||||
// and that the ARP hardware type field indicates Ethernet.
|
buflen += 6;
|
||||||
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12),
|
char resp;
|
||||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_ARP << 16) | ARPHRD_ETHER,
|
int fd = request_sockd_fd(buf, buflen, &resp);
|
||||||
1, 0),
|
switch (resp) {
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0),
|
case 'D': using_arp_bpf = 1; break;
|
||||||
// Verify that the ARP protocol type field indicates IP, the ARP
|
case 'd': using_arp_bpf = 0; break;
|
||||||
// hardware address length field is 6, and the ARP protocol address
|
default: suicide("%s: (%s) expected d or D sockd reply but got %c",
|
||||||
// length field is 4.
|
client_config.interface, __func__, resp);
|
||||||
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 16),
|
|
||||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_IP << 16) | 0x0604, 1, 0),
|
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0),
|
|
||||||
|
|
||||||
// If the ARP packet source IP does not match our IP address, then
|
|
||||||
// it can be ignored.
|
|
||||||
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 28),
|
|
||||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, cs->clientAddr, 1, 0),
|
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0),
|
|
||||||
// If the first four bytes of the ARP packet source hardware address
|
|
||||||
// does not equal our hardware address, then it's a conflict and should
|
|
||||||
// be passed along.
|
|
||||||
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 22),
|
|
||||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac4b, 1, 0),
|
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
|
|
||||||
// If the last two bytes of the ARP packet source hardware address
|
|
||||||
// do not equal our hardware address, then it's a conflict and should
|
|
||||||
// be passed along.
|
|
||||||
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 26),
|
|
||||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac2b, 1, 0),
|
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
|
|
||||||
// Packet announces our IP address and hardware address, so it requires
|
|
||||||
// no action.
|
|
||||||
BPF_STMT(BPF_RET + BPF_K, 0),
|
|
||||||
};
|
|
||||||
struct sock_fprog sfp_arp = {
|
|
||||||
.len = sizeof sf_arp / sizeof sf_arp[0],
|
|
||||||
.filter = (struct sock_filter *)sf_arp,
|
|
||||||
};
|
|
||||||
using_arp_bpf = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_arp,
|
|
||||||
sizeof sfp_arp) != -1;
|
|
||||||
}
|
}
|
||||||
|
return fd;
|
||||||
static int get_arp_socket(void)
|
|
||||||
{
|
|
||||||
return request_sockd_fd("a", 1, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
return 0;
|
return 0;
|
||||||
cs->arpFd = get_arp_socket();
|
switch (arpState) {
|
||||||
|
default: cs->arpFd = -1; arpState = AS_NONE; return -1;
|
||||||
|
case AS_COLLISION_CHECK:
|
||||||
|
case AS_GW_QUERY:
|
||||||
|
case AS_GW_CHECK: cs->arpFd = get_arp_basic_socket(); break;
|
||||||
|
case AS_DEFENSE: cs->arpFd = get_arp_defense_socket(cs); break;
|
||||||
|
}
|
||||||
if (cs->arpFd == -1) {
|
if (cs->arpFd == -1) {
|
||||||
log_error("arp: Failed to create socket: %s", strerror(errno));
|
log_error("arp: Failed to create socket: %s", strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
@ -221,6 +178,17 @@ static int arp_open_fd(struct client_state_t *cs)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int arp_min_close_fd(struct client_state_t *cs)
|
||||||
|
{
|
||||||
|
if (cs->arpFd == -1)
|
||||||
|
return 0;
|
||||||
|
epoll_del(cs->epollFd, cs->arpFd);
|
||||||
|
close(cs->arpFd);
|
||||||
|
cs->arpFd = -1;
|
||||||
|
arpState = AS_NONE;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static void arp_switch_state(struct client_state_t *cs, arp_state_t state)
|
static void arp_switch_state(struct client_state_t *cs, arp_state_t state)
|
||||||
{
|
{
|
||||||
arp_state_t prev_state = arpState;
|
arp_state_t prev_state = arpState;
|
||||||
@ -231,32 +199,14 @@ static void arp_switch_state(struct client_state_t *cs, arp_state_t state)
|
|||||||
arp_close_fd(cs);
|
arp_close_fd(cs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cs->arpFd == -1) {
|
bool force_reopen = arpState == AS_DEFENSE || prev_state == AS_DEFENSE;
|
||||||
|
if (force_reopen)
|
||||||
|
arp_min_close_fd(cs);
|
||||||
|
if (cs->arpFd == -1 || force_reopen) {
|
||||||
if (arp_open_fd(cs) == -1)
|
if (arp_open_fd(cs) == -1)
|
||||||
suicide("arp: Failed to open arpFd when changing state to %u",
|
suicide("arp: Failed to open arpFd when changing state %u -> %u",
|
||||||
arpState);
|
prev_state, arpState);
|
||||||
if (arpState != AS_DEFENSE)
|
|
||||||
arp_set_bpf_basic(cs->arpFd);
|
|
||||||
}
|
}
|
||||||
if (arpState == AS_DEFENSE) {
|
|
||||||
arp_set_bpf_defense(cs, cs->arpFd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (prev_state == AS_DEFENSE) {
|
|
||||||
arp_set_bpf_basic(cs->arpFd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int arp_min_close_fd(struct client_state_t *cs)
|
|
||||||
{
|
|
||||||
if (cs->arpFd == -1)
|
|
||||||
return 0;
|
|
||||||
epoll_del(cs->epollFd, cs->arpFd);
|
|
||||||
close(cs->arpFd);
|
|
||||||
cs->arpFd = -1;
|
|
||||||
arpState = AS_NONE;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int arp_close_fd(struct client_state_t *cs)
|
int arp_close_fd(struct client_state_t *cs)
|
||||||
@ -271,11 +221,6 @@ static int arp_reopen_fd(struct client_state_t *cs)
|
|||||||
{
|
{
|
||||||
arp_state_t prev_state = arpState;
|
arp_state_t prev_state = arpState;
|
||||||
arp_min_close_fd(cs);
|
arp_min_close_fd(cs);
|
||||||
if (arp_open_fd(cs) == -1) {
|
|
||||||
log_warning("arp: Failed to re-open fd. Something is very wrong.");
|
|
||||||
log_warning("arp: Client will still run, but functionality will be degraded.");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
arp_switch_state(cs, prev_state);
|
arp_switch_state(cs, prev_state);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
123
ndhc/sockd.c
123
ndhc/sockd.c
@ -34,6 +34,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <assert.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/signalfd.h>
|
#include <sys/signalfd.h>
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
@ -44,6 +45,7 @@
|
|||||||
#include <netinet/ip.h>
|
#include <netinet/ip.h>
|
||||||
#include <netpacket/packet.h>
|
#include <netpacket/packet.h>
|
||||||
#include <net/ethernet.h>
|
#include <net/ethernet.h>
|
||||||
|
#include <netinet/if_ether.h>
|
||||||
#include <linux/filter.h>
|
#include <linux/filter.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
@ -333,6 +335,108 @@ static int create_raw_broadcast_socket(void)
|
|||||||
return create_raw_socket(&da, NULL, NULL);
|
return create_raw_socket(&da, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool arp_set_bpf_basic(int fd)
|
||||||
|
{
|
||||||
|
static struct sock_filter sf_arp[] = {
|
||||||
|
// Verify that the frame has ethernet protocol type of ARP
|
||||||
|
// and that the ARP hardware type field indicates Ethernet.
|
||||||
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12),
|
||||||
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_ARP << 16) | ARPHRD_ETHER,
|
||||||
|
1, 0),
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
||||||
|
// Verify that the ARP protocol type field indicates IP, the ARP
|
||||||
|
// hardware address length field is 6, and the ARP protocol address
|
||||||
|
// length field is 4.
|
||||||
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 16),
|
||||||
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_IP << 16) | 0x0604, 1, 0),
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
||||||
|
// Sanity tests passed, so send all possible data.
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
|
||||||
|
};
|
||||||
|
static const struct sock_fprog sfp_arp = {
|
||||||
|
.len = sizeof sf_arp / sizeof sf_arp[0],
|
||||||
|
.filter = sf_arp,
|
||||||
|
};
|
||||||
|
int ret = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_arp,
|
||||||
|
sizeof sfp_arp) != -1;
|
||||||
|
if (ret < 0)
|
||||||
|
log_warning("%s: Failed to set BPF for basic ARP socket: %s",
|
||||||
|
client_config.interface, strerror(errno));
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool arp_set_bpf_defense(int fd, uint32_t client_addr,
|
||||||
|
uint8_t client_mac[6])
|
||||||
|
{
|
||||||
|
uint32_t mac4b;
|
||||||
|
uint16_t mac2b;
|
||||||
|
memcpy(&mac4b, client_mac, 4);
|
||||||
|
memcpy(&mac2b, client_mac + 4, 2);
|
||||||
|
|
||||||
|
struct sock_filter sf_arp[] = {
|
||||||
|
// Verify that the frame has ethernet protocol type of ARP
|
||||||
|
// and that the ARP hardware type field indicates Ethernet.
|
||||||
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12),
|
||||||
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_ARP << 16) | ARPHRD_ETHER,
|
||||||
|
1, 0),
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
||||||
|
// Verify that the ARP protocol type field indicates IP, the ARP
|
||||||
|
// hardware address length field is 6, and the ARP protocol address
|
||||||
|
// length field is 4.
|
||||||
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 16),
|
||||||
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (ETH_P_IP << 16) | 0x0604, 1, 0),
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
||||||
|
|
||||||
|
// If the ARP packet source IP does not match our IP address, then
|
||||||
|
// it can be ignored.
|
||||||
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 28),
|
||||||
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client_addr, 1, 0),
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
||||||
|
// If the first four bytes of the ARP packet source hardware address
|
||||||
|
// does not equal our hardware address, then it's a conflict and should
|
||||||
|
// be passed along.
|
||||||
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 22),
|
||||||
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac4b, 1, 0),
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
|
||||||
|
// If the last two bytes of the ARP packet source hardware address
|
||||||
|
// do not equal our hardware address, then it's a conflict and should
|
||||||
|
// be passed along.
|
||||||
|
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 26),
|
||||||
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac2b, 1, 0),
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0x7fffffff),
|
||||||
|
// Packet announces our IP address and hardware address, so it requires
|
||||||
|
// no action.
|
||||||
|
BPF_STMT(BPF_RET + BPF_K, 0),
|
||||||
|
};
|
||||||
|
struct sock_fprog sfp_arp = {
|
||||||
|
.len = sizeof sf_arp / sizeof sf_arp[0],
|
||||||
|
.filter = (struct sock_filter *)sf_arp,
|
||||||
|
};
|
||||||
|
int ret = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_arp,
|
||||||
|
sizeof sfp_arp) != -1;
|
||||||
|
if (ret < 0)
|
||||||
|
log_warning("%s: Failed to set BPF for defense ARP socket: %s",
|
||||||
|
client_config.interface, strerror(errno));
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_arp_defense_socket(uint32_t client_addr,
|
||||||
|
uint8_t client_mac[6], bool *using_bpf)
|
||||||
|
{
|
||||||
|
assert(using_bpf);
|
||||||
|
int fd = create_arp_socket();
|
||||||
|
*using_bpf = arp_set_bpf_defense(fd, client_addr, client_mac);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_arp_basic_socket(bool *using_bpf)
|
||||||
|
{
|
||||||
|
assert(using_bpf);
|
||||||
|
int fd = create_arp_socket();
|
||||||
|
*using_bpf = arp_set_bpf_basic(fd);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
// XXX: Can share with ifch
|
// XXX: Can share with ifch
|
||||||
static void setup_signals_sockd(void)
|
static void setup_signals_sockd(void)
|
||||||
{
|
{
|
||||||
@ -427,7 +531,24 @@ static size_t execute_sockd(char *buf, size_t buflen)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
case 'U': xfer_fd(create_udp_listen_socket(), 'U'); return 1;
|
case 'U': xfer_fd(create_udp_listen_socket(), 'U'); return 1;
|
||||||
case 'a': xfer_fd(create_arp_socket(), 'a'); return 1;
|
case 'a': {
|
||||||
|
bool using_bpf;
|
||||||
|
int fd = create_arp_basic_socket(&using_bpf);
|
||||||
|
xfer_fd(fd, using_bpf ? 'A' : 'a'); return 1;
|
||||||
|
}
|
||||||
|
case 'd': {
|
||||||
|
uint32_t client_addr;
|
||||||
|
uint8_t client_mac[6];
|
||||||
|
bool using_bpf;
|
||||||
|
if (buflen < 1 + sizeof client_addr + 6)
|
||||||
|
return 0;
|
||||||
|
memcpy(&client_addr, buf + 1, sizeof client_addr);
|
||||||
|
memcpy(client_mac, buf + 1 + sizeof client_addr, 6);
|
||||||
|
int fd = create_arp_defense_socket(client_addr, client_mac,
|
||||||
|
&using_bpf);
|
||||||
|
xfer_fd(fd, using_bpf ? 'D' : 'd');
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
case 's': xfer_fd(create_raw_broadcast_socket(), 's'); return 1;
|
case 's': xfer_fd(create_raw_broadcast_socket(), 's'); return 1;
|
||||||
case 'u': {
|
case 'u': {
|
||||||
uint32_t client_addr;
|
uint32_t client_addr;
|
||||||
|
Loading…
Reference in New Issue
Block a user