Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

various improvements and bug fixes for Hurd #1275

Merged
merged 5 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ DayOfTheWeek, Month DD, YYYY / The Tcpdump Group
Add a new test program for pcap_activate().
Haiku:
Report non-existent devices correctly.
Hurd:
Support network capture devices too.
Fix a few device activation bugs.
Count and timestamp packets better.
Add kernel filtering, fix userland filtering.

DayOfTheWeek, Month DD, YYYY / The Tcpdump Group
Summary for 1.10.5 libpcap release (so far!)
Expand Down
4 changes: 2 additions & 2 deletions pcap-filter.manmisc.in
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
.\"
.TH PCAP-FILTER @MAN_MISC_INFO@ "13 June 2023"
.TH PCAP-FILTER @MAN_MISC_INFO@ "12 February 2024"
.SH NAME
pcap-filter \- packet filter syntax
.br
Expand Down Expand Up @@ -896,7 +896,7 @@ and
.B ^
operators are currently only supported for filtering in the kernel on
particular operating systems (for example: FreeBSD, Linux with 3.7 and later
kernels, NetBSD); on all other systems (for example: AIX, illumos, Solaris,
kernels, NetBSD); on all other systems (for example: AIX, Hurd, illumos, Solaris,
OpenBSD), if
those operators are used, filtering will be done in user mode, which
will increase the overhead of capturing packets and may cause more
Expand Down
180 changes: 147 additions & 33 deletions pcap-hurd.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ struct pcap_hurd {
struct pcap_stat stat;
device_t mach_dev;
mach_port_t rcv_port;
int filtering_in_kernel;
};

/* Accept all packets. */
static struct bpf_insn filter[] = {
{ NETF_IN | NETF_OUT | NETF_BPF, 0, 0, 0 },
{ BPF_RET | BPF_K, 0, 0, MAXIMUM_SNAPLEN },
Expand All @@ -39,14 +41,87 @@ static struct bpf_insn filter[] = {
* believe we are computing wrong here. */
#define FILTER_COUNT (sizeof(filter) / (sizeof(short)))

static int
PCAP_WARN_UNUSED_RESULT
pcap_device_set_filter(pcap_t *p, filter_array_t filter_array,
const mach_msg_type_number_t filter_count)
{
kern_return_t kr;
struct pcap_hurd *ph = p->priv;
kr = device_set_filter(ph->mach_dev, ph->rcv_port,
MACH_MSG_TYPE_MAKE_SEND, 0,
filter_array, filter_count);
if (! kr)
return 0;
pcapint_fmt_errmsg_for_errno(p->errbuf, PCAP_ERRBUF_SIZE, errno,
"device_set_filter");
return PCAP_ERROR;
}

static int
pcap_setfilter_hurd(pcap_t *p, struct bpf_program *program)
{
if (! program || pcapint_install_bpf_program(p, program) < 0) {
pcapint_strlcpy(p->errbuf, "setfilter: invalid program",
sizeof(p->errbuf));
return PCAP_ERROR;
}

/*
* The bytecode is valid and the copy in p->fcode can be used for
* userland filtering if kernel filtering does not work out.
*
* The kernel BPF implementation supports neither BPF_MOD nor BPF_XOR,
* it also fails to reject unsupported bytecode properly, so the check
* must be done here.
*/
struct pcap_hurd *ph = p->priv;
for (u_int i = 0; i < program->bf_len; i++) {
u_short c = program->bf_insns[i].code;
if (BPF_CLASS(c) == BPF_ALU &&
(BPF_OP(c) == BPF_MOD || BPF_OP(c) == BPF_XOR))
goto userland;
}

/*
* The kernel takes an array of 16-bit Hurd network filter commands, no
* more than NET_MAX_FILTER elements. The first four commands form a
* header that says "BPF bytecode follows", the rest is a binary copy
* of 64-bit instructions of the required BPF bytecode.
*/
mach_msg_type_number_t cmdcount = 4 + 4 * program->bf_len;
if (cmdcount > NET_MAX_FILTER)
goto userland;

filter_t cmdbuffer[NET_MAX_FILTER];
memcpy(cmdbuffer, filter, sizeof(struct bpf_insn));
memcpy(cmdbuffer + 4, program->bf_insns,
program->bf_len * sizeof(struct bpf_insn));
if (pcap_device_set_filter(p, cmdbuffer, cmdcount))
goto userland;
ph->filtering_in_kernel = 1;
return 0;

userland:
/*
* Could not install a new kernel filter for a reason, so replace any
* previous kernel filter with one that accepts all packets and lets
* userland filtering do the job. If that fails too, something is
* badly broken and even userland filtering would not work correctly,
* so expose the failure.
*/
ph->filtering_in_kernel = 0;
return pcap_device_set_filter(p, (filter_array_t)filter, FILTER_COUNT);
}

static int
pcap_read_hurd(pcap_t *p, int cnt _U_, pcap_handler callback, u_char *user)
{
struct net_rcv_msg *msg;
struct pcap_hurd *ph;
struct pcap_pkthdr h;
struct timespec ts;
int ret, wirelen, caplen;
int wirelen, caplen;
u_char *pkt;
kern_return_t kr;

Expand All @@ -62,40 +137,57 @@ pcap_read_hurd(pcap_t *p, int cnt _U_, pcap_handler callback, u_char *user)
kr = mach_msg(&msg->msg_hdr, MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0,
p->bufsize, ph->rcv_port, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
clock_gettime(CLOCK_REALTIME, &ts);

if (kr) {
if (kr == MACH_RCV_INTERRUPTED)
goto retry;

snprintf(p->errbuf, sizeof(p->errbuf), "mach_msg: %s",
pcap_strerror(kr));
pcapint_fmt_errmsg_for_errno(p->errbuf, PCAP_ERRBUF_SIZE, kr,
"mach_msg");
return PCAP_ERROR;
}

ph->stat.ps_recv++;

/* XXX Ethernet support only */
/*
* wirelen calculation assumes the following:
* msg->packet_type.msgt_name == MACH_MSG_TYPE_BYTE
* msg->packet_type.msgt_size == 8
* msg->packet_type.msgt_number is a size in bytes
*/
wirelen = ETH_HLEN + msg->net_rcv_msg_packet_count
- sizeof(struct packet_header);
pkt = p->buffer + offsetof(struct net_rcv_msg, packet)
+ sizeof(struct packet_header) - ETH_HLEN;
memmove(pkt, p->buffer + offsetof(struct net_rcv_msg, header),
ETH_HLEN);

/*
* It seems, kernel device filters treat the K in BPF_MOD as a Boolean:
* so long as it is positive, the Mach message will contain the entire
* packet and wirelen will be set accordingly. Thus the caplen value
* for the callback needs to be calculated for every packet no matter
* which type of filtering is in effect.
*
* For the userland filtering this calculated value is not an input:
* buflen always equals wirelen and a userland program can examine the
* entire packet, same way as a kernel program. It is not an output
* either: pcapint_filter() returns either zero or MAXIMUM_SNAPLEN.
* The same principle applies to kernel filtering.
*/
caplen = (wirelen > p->snapshot) ? p->snapshot : wirelen;
ret = bpf_filter(p->fcode.bf_insns, pkt, wirelen, caplen);

if (!ret)
goto out;
if (! ph->filtering_in_kernel &&
! pcapint_filter(p->fcode.bf_insns, pkt, wirelen, wirelen))
return 0;

clock_gettime(CLOCK_REALTIME, &ts);
h.ts.tv_sec = ts.tv_sec;
h.ts.tv_usec = ts.tv_nsec / 1000;
h.len = wirelen;
h.caplen = caplen;
callback(user, &h, pkt);

out:
return 1;
}

Expand All @@ -111,8 +203,8 @@ pcap_inject_hurd(pcap_t *p, const void *buf, int size)
(io_buf_ptr_t)buf, size, &count);

if (kr) {
snprintf(p->errbuf, sizeof(p->errbuf), "device_write: %s",
pcap_strerror(kr));
pcapint_fmt_errmsg_for_errno(p->errbuf, PCAP_ERRBUF_SIZE, kr,
"device_write");
return -1;
}

Expand Down Expand Up @@ -155,9 +247,13 @@ pcap_activate_hurd(pcap_t *p)
struct pcap_hurd *ph;
mach_port_t master;
kern_return_t kr;
int ret = PCAP_ERROR;

ph = p->priv;

if (p->snapshot <= 0 || p->snapshot > MAXIMUM_SNAPLEN)
p->snapshot = MAXIMUM_SNAPLEN;

/* Try devnode first */
master = file_name_lookup(p->opt.device, O_READ | O_WRITE, 0);

Expand All @@ -168,8 +264,10 @@ pcap_activate_hurd(pcap_t *p)
kr = get_privileged_ports(NULL, &master);

if (kr) {
snprintf(p->errbuf, sizeof(p->errbuf),
"get_privileged_ports: %s", pcap_strerror(kr));
pcapint_fmt_errmsg_for_errno(p->errbuf,
PCAP_ERRBUF_SIZE, kr, "get_privileged_ports");
if (kr == EPERM)
ret = PCAP_ERROR_PERM_DENIED;
goto error;
}

Expand All @@ -180,36 +278,28 @@ pcap_activate_hurd(pcap_t *p)
mach_port_deallocate(mach_task_self(), master);

if (kr) {
snprintf(p->errbuf, sizeof(p->errbuf), "device_open: %s",
pcap_strerror(kr));
pcapint_fmt_errmsg_for_errno(p->errbuf, PCAP_ERRBUF_SIZE, kr,
"device_open");
if (kr == ED_NO_SUCH_DEVICE) /* not ENODEV */
ret = PCAP_ERROR_NO_SUCH_DEVICE;
goto error;
}

kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
&ph->rcv_port);

if (kr) {
snprintf(p->errbuf, sizeof(p->errbuf), "mach_port_allocate: %s",
pcap_strerror(kr));
goto error;
}

kr = device_set_filter(ph->mach_dev, ph->rcv_port,
MACH_MSG_TYPE_MAKE_SEND, 0,
(filter_array_t)filter, FILTER_COUNT);

if (kr) {
snprintf(p->errbuf, sizeof(p->errbuf), "device_set_filter: %s",
pcap_strerror(kr));
pcapint_fmt_errmsg_for_errno(p->errbuf, PCAP_ERRBUF_SIZE, kr,
"mach_port_allocate");
goto error;
}

p->bufsize = sizeof(struct net_rcv_msg);
p->buffer = malloc(p->bufsize);

if (p->buffer == NULL) {
snprintf(p->errbuf, sizeof(p->errbuf), "malloc: %s",
pcap_strerror(errno));
pcapint_fmt_errmsg_for_errno(p->errbuf, PCAP_ERRBUF_SIZE,
errno, "malloc");
goto error;
}

Expand All @@ -233,14 +323,14 @@ pcap_activate_hurd(pcap_t *p)

p->read_op = pcap_read_hurd;
p->inject_op = pcap_inject_hurd;
p->setfilter_op = pcapint_install_bpf_program;
p->setfilter_op = pcap_setfilter_hurd;
p->stats_op = pcap_stats_hurd;

return 0;

error:
pcap_cleanup_hurd(p);
return PCAP_ERROR;
return ret;
}

pcap_t *
Expand All @@ -260,12 +350,36 @@ pcapint_create_interface(const char *device _U_, char *ebuf)
return p;
}

int
pcapint_platform_finddevs(pcap_if_list_t *alldevsp _U_, char *errbuf _U_)
static int
can_be_bound(const char *name)
{
/*
* On Hurd lo appears in the list of interfaces, but the call to
* device_open() fails with: "(os/device) no such device".
*/
if (! strcmp(name, "lo"))
return 0;
return 1;
}

static int
get_if_flags(const char *name _U_, bpf_u_int32 *flags, char *errbuf _U_)
{
/*
* This would apply to the loopback interface if it worked. Ethernet
* interfaces appear up and running regardless of the link status.
*/
*flags |= PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE;
return 0;
}

int
pcapint_platform_finddevs(pcap_if_list_t *devlistp, char *errbuf)
{
return pcapint_findalldevs_interfaces(devlistp, errbuf, can_be_bound,
get_if_flags);
}

/*
* Libpcap version string.
*/
Expand Down