Have sockd apply BPF filters to ARP sockets.

This commit is contained in:
Nicholas J. Kain 2014-04-05 21:39:27 -04:00
parent 5212e0dfc5
commit 6750209e12
2 changed files with 175 additions and 109 deletions

View File

@ -25,6 +25,7 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.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[] = {
// 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,
};
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));
char resp;
int fd = request_sockd_fd("a", 1, &resp);
switch (resp) {
case 'A': using_arp_bpf = 1; break;
case 'a': using_arp_bpf = 0; break;
default: suicide("%s: (%s) expected a or A sockd reply but got %c",
client_config.interface, __func__, resp);
}
return fd;
}
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;
uint16_t mac2b;
memcpy(&mac4b, client_config.arp, 4);
memcpy(&mac2b, client_config.arp+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, 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;
char buf[32];
size_t buflen = 0;
buf[0] = 'd';
buflen += 1;
memcpy(buf + buflen, &cs->clientAddr, sizeof cs->clientAddr);
buflen += sizeof cs->clientAddr;
memcpy(buf + buflen, client_config.arp, 6);
buflen += 6;
char resp;
int fd = request_sockd_fd(buf, buflen, &resp);
switch (resp) {
case 'D': using_arp_bpf = 1; break;
case 'd': using_arp_bpf = 0; break;
default: suicide("%s: (%s) expected d or D sockd reply but got %c",
client_config.interface, __func__, resp);
}
static int get_arp_socket(void)
{
return request_sockd_fd("a", 1, NULL);
return fd;
}
static int arp_open_fd(struct client_state_t *cs)
{
if (cs->arpFd != -1)
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) {
log_error("arp: Failed to create socket: %s", strerror(errno));
return -1;
@ -221,6 +178,17 @@ static int arp_open_fd(struct client_state_t *cs)
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)
{
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);
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)
suicide("arp: Failed to open arpFd when changing state to %u",
arpState);
if (arpState != AS_DEFENSE)
arp_set_bpf_basic(cs->arpFd);
suicide("arp: Failed to open arpFd when changing state %u -> %u",
prev_state, arpState);
}
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)
@ -271,11 +221,6 @@ static int arp_reopen_fd(struct client_state_t *cs)
{
arp_state_t prev_state = arpState;
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);
return 0;
}

View File

@ -34,6 +34,7 @@
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/signalfd.h>
#include <sys/epoll.h>
@ -44,6 +45,7 @@
#include <netinet/ip.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <netinet/if_ether.h>
#include <linux/filter.h>
#include <pwd.h>
#include <grp.h>
@ -333,6 +335,108 @@ static int create_raw_broadcast_socket(void)
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
static void setup_signals_sockd(void)
{
@ -427,7 +531,24 @@ static size_t execute_sockd(char *buf, size_t buflen)
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 'u': {
uint32_t client_addr;