[GH-ISSUE #6133] ARP probe failing due to gratuitous arp reply #3193

Open
opened 2026-05-05 09:48:49 -06:00 by gitea-mirror · 2 comments
Owner

Originally created by @brianvanderburg2 on GitHub (Dec 17, 2023).
Original GitHub issue: https://github.com/netblue30/firejail/issues/6133

Description

Setup: A bridge is configured with a VLAN subinterface from the main interface bound to it. T When using firejail, the bridge is used as the interface with an IP range to choose from.

enp9s0 (main interface)
bm-home (vlan subinterface for home/internet traffic)
bhome (bridge, bm-home is placed into this bridge.)
no IPs are assigned to the bridges or VLAN/main interfaces

Firejail is launched to create a veth pair to put into bhome, then processes launched under the firejail proccess have an IP/are able to access the internet. The bridge is used to allow other processes/virtual machines/macvlans/etc to also be able to access the internet when desired. This has worked perfectly fine until recently.

Profile:

net bhome
iprange 192.168.1.30,192.168.1.39
defaultgw 192.168.1.1
netmask 255.255.255.0
dns 8.8.8.8
dns 8.8.4.4

Recently, I get an error indicating all the IPs are in use. I checked the wireshark capture and i notice:

Send arp probe
I get a gratuitous arp from a different address on the network
Send arp probe for next ip
same
Send arp probe for next ip
same

When I don't get the gratuitous arp response, then it assignes that IP address Only one device in our network has that response so I asked them to turn it off, and when they do, it works with no problems.

image

Steps to Reproduce

Unsure. It seems specific to this situation

Expected behavior

After getting no ARP responses matching the IPs of the ARP probe, it should use the IP for the jail

Actual behavior

It performs an ARP probe of two random IPs, then the entire specified range, and fails with the error "Error: cannot assign an IP address; it looks like all of them are in use."

Behavior without a profile

This works, however doesn't use a new network namespace with veth interface connected to the bridge and assigned an iP, so doesn't affect/reflect on this issue

Additional context

Environment

Debian Bullesye
Firejail 0.9.72

Checklist

  • [ x] The issues is caused by firejail (i.e. running the program by path (e.g. /usr/bin/vlc) "fixes" it).
  • I can reproduce the issue without custom modifications (e.g. globals.local). (Using the profile I use is a custom modification)
  • The program has a profile. (If not, request one in https://github.com/netblue30/firejail/issues/1139)
  • The profile (and redirect profile if exists) hasn't already been fixed upstream.
  • I have performed a short search for similar issues (to avoid opening a duplicate).
    • I'm aware of browser-allow-drm yes/browser-disable-u2f no in firejail.config to allow DRM/U2F in browsers.
  • I used --profile=PROFILENAME to set the right profile. (Only relevant for AppImages)

I think the issue is here:

Send ARP probe:
image

Received gratuitous ARP
image

Code:
image

If I'm reading this correctly:

if packet length is to small, continue
if proto type is not arp continue
copy arp header
if arp code is not 2 (reply) continue
compare target mac received to probe sender mac, if not the same continue
(the probe sender mac is 02:ba:15:00:00:10, the grat arp target mac is 02:ba:15:00:00:10, so it moves to next step)
compare target ip received to probe sent IP, if not continue
(the probe sent IP source 0.0.0.0, the grat arp had a target ip 0.0.0.0 soe it moves to the next step)
return -1 (ARP probe failed)

(for probe responses, shouldbe be checking target or source, ie wouldn't the arp reply of an 'already-used' IP address have it's IP/MAC in the ARP sender field instead of target field
Suspect maybe it should be:

if (memcmp(ifr.ifr_hwaddr.sa_data, hdr.sender_mac, 6) != 0)

and

memcpy(&ip, hdr.sender_ip, 4)

instead

Originally created by @brianvanderburg2 on GitHub (Dec 17, 2023). Original GitHub issue: https://github.com/netblue30/firejail/issues/6133 ### Description Setup: A bridge is configured with a VLAN subinterface from the main interface bound to it. T When using firejail, the bridge is used as the interface with an IP range to choose from. enp9s0 (main interface) bm-home (vlan subinterface for home/internet traffic) bhome (bridge, bm-home is placed into this bridge.) no IPs are assigned to the bridges or VLAN/main interfaces Firejail is launched to create a veth pair to put into bhome, then processes launched under the firejail proccess have an IP/are able to access the internet. The bridge is used to allow other processes/virtual machines/macvlans/etc to also be able to access the internet when desired. This has worked perfectly fine until recently. Profile: ``` net bhome iprange 192.168.1.30,192.168.1.39 defaultgw 192.168.1.1 netmask 255.255.255.0 dns 8.8.8.8 dns 8.8.4.4 ``` Recently, I get an error indicating all the IPs are in use. I checked the wireshark capture and i notice: Send arp probe I get a gratuitous arp from a different address on the network Send arp probe for next ip same Send arp probe for next ip same When I don't get the gratuitous arp response, then it assignes that IP address Only one device in our network has that response so I asked them to turn it off, and when they do, it works with no problems. ![image](https://github.com/netblue30/firejail/assets/2142408/be020e88-d209-4b6e-a1e1-260eb70fa628) ### Steps to Reproduce Unsure. It seems specific to this situation ### Expected behavior After getting no ARP responses matching the IPs of the ARP probe, it should use the IP for the jail ### Actual behavior It performs an ARP probe of two random IPs, then the entire specified range, and fails with the error "Error: cannot assign an IP address; it looks like all of them are in use." ### Behavior without a profile This works, however doesn't use a new network namespace with veth interface connected to the bridge and assigned an iP, so doesn't affect/reflect on this issue ### Additional context ### Environment Debian Bullesye Firejail 0.9.72 ### Checklist <!-- Note: Items are checked with an "x", like so: - [x] This is a checked item. --> - [ x] The issues is caused by firejail (i.e. running the program by path (e.g. `/usr/bin/vlc`) "fixes" it). - [ ] I can reproduce the issue without custom modifications (e.g. globals.local). (Using the profile I use is a custom modification) - [ ] The program has a profile. (If not, request one in `https://github.com/netblue30/firejail/issues/1139`) - [ ] The profile (and redirect profile if exists) hasn't already been fixed [upstream](https://github.com/netblue30/firejail/tree/master/etc). - [ ] I have performed a short search for similar issues (to avoid opening a duplicate). - [ ] I'm aware of `browser-allow-drm yes`/`browser-disable-u2f no` in `firejail.config` to allow DRM/U2F in browsers. - [ ] I used `--profile=PROFILENAME` to set the right profile. (Only relevant for AppImages) I think the issue is here: Send ARP probe: ![image](https://github.com/netblue30/firejail/assets/2142408/d550f419-2b14-4634-8d67-1aa0905056ba) Received gratuitous ARP ![image](https://github.com/netblue30/firejail/assets/2142408/38a58a37-69e9-4173-89fe-45520249c329) Code: ![image](https://github.com/netblue30/firejail/assets/2142408/e3bc08a5-a1a4-4aa5-890d-54b38299ade6) If I'm reading this correctly: if packet length is to small, continue if proto type is not arp continue copy arp header if arp code is not 2 (reply) continue compare target mac received to probe sender mac, if not the same continue (the probe sender mac is 02:ba:15:00:00:10, the grat arp target mac is 02:ba:15:00:00:10, so it moves to next step) compare target ip received to probe sent IP, if not continue (the probe sent IP source 0.0.0.0, the grat arp had a target ip 0.0.0.0 soe it moves to the next step) return -1 (ARP probe failed) (for probe responses, shouldbe be checking target or source, ie wouldn't the arp reply of an 'already-used' IP address have it's IP/MAC in the ARP sender field instead of target field Suspect maybe it should be: if (memcmp(ifr.ifr_hwaddr.sa_data, hdr.sender_mac, 6) != 0) and memcpy(&ip, hdr.sender_ip, 4) instead
gitea-mirror added the
networking
label 2026-05-05 09:48:49 -06:00
Author
Owner

@osevan commented on GitHub (Dec 17, 2023):

Is this related with

https://github.com/netblue30/firejail/discussions/6102

?

<!-- gh-comment-id:1859331004 --> @osevan commented on GitHub (Dec 17, 2023): Is this related with https://github.com/netblue30/firejail/discussions/6102 ?
Author
Owner

@brianvanderburg2 commented on GitHub (Dec 18, 2023):

I don't think so. In your log file the network is already established and enters the jail. In mine, it fails at assigning an IP address and does not even enter the sandbox.

Looking through the code and packet dump it makes sense.

My system sends:
sender mac: 02:ba:15:00:00:10
sender ip: 0.0.0.0
target mac: 00:00:00:00:00:00
target ip: 192.168.1.30

This other system on the network send a reply:
sender mac: f0:d4:15:45:5d:5d
sender ip: 192.168.1.122
target mac: 02:ba:15:00:00:10
target ip: 0.0.0.0

The code is checking if the target values of the reply are the same as the sender of the probe, which they are, so returns -1, and arp_random/arp_sequential then test the result, which is not 0, so returns 0 for the "ip" which then causes arp_assign to fail.

image

This seems to confirm it should compare the received frame's sender IP with the probe frame's target IP, and if the same, treat as duplicate. In the code, it compares the received frame's target IP (hdr.target_ip) with the probe frame's sender IP (srcaddr) Here I don't think the MAC even matters, another ARP being received with the same sender IP that we are trying to use would mean a duplicate

It also recommends if receiving an ARP request during probing with a target ip matching the IP we are trying to use and a source mac not equal to ours, to treat as duplicate as another station may be probing to use the IP at the same time as we are

I don't have build tools on my system right now, but I'm thinking this is what the function should look like

int arp_check(const char *dev, uint32_t destaddr) {
	// RFC 5227 - using a source IP address of 0 for probing
	uint32_t srcaddr = 0;

	if (strlen(dev) > IFNAMSIZ) {
		fprintf(stderr, "Error: invalid network device name %s\n", dev);
		exit(1);
	}

	if (arg_debug)
		printf("Trying %d.%d.%d.%d ...\n", PRINT_IP(destaddr));

	// find interface address
	int sock;
	if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
		errExit("socket");

	srcaddr = htonl(srcaddr);
	destaddr = htonl(destaddr);

	// Find interface MAC address
	struct ifreq ifr;
	memset(&ifr, 0, sizeof (ifr));
	strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1);
	if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0)
		errExit("ioctl");
	close(sock);

	// configure layer2 socket address information
	struct sockaddr_ll addr;
	memset(&addr, 0, sizeof(addr));
	if ((addr.sll_ifindex = if_nametoindex(dev)) == 0)
		errExit("if_nametoindex");
	addr.sll_family = AF_PACKET;
	memcpy (addr.sll_addr, ifr.ifr_hwaddr.sa_data, 6);
	addr.sll_halen = ETH_ALEN;

	// build the arp packet header
	ArpHdr hdr;
	memset(&hdr, 0, sizeof(hdr));
	hdr.htype = htons(1);
	hdr.ptype = htons(ETH_P_IP);
	hdr.hlen = 6;
	hdr.plen = 4;
	hdr.opcode = htons(1); //ARPOP_REQUEST
	memcpy(hdr.sender_mac, ifr.ifr_hwaddr.sa_data, 6);
	memcpy(hdr.sender_ip, (uint8_t *)&srcaddr, 4);
	memcpy(hdr.target_ip, (uint8_t *)&destaddr, 4);

	// build ethernet frame
	uint8_t frame[ETH_FRAME_LEN]; // includes eth header, vlan, and crc
	memset(frame, 0, sizeof(frame));
	frame[0] = frame[1] = frame[2] = frame[3] = frame[4] = frame[5] = 0xff;
	memcpy(frame + 6, ifr.ifr_hwaddr.sa_data, 6);
	frame[12] = ETH_P_ARP / 256;
	frame[13] = ETH_P_ARP % 256;
	memcpy (frame + 14, &hdr, sizeof(hdr));

	// open layer2 socket
	if ((sock = socket(PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
		errExit("socket");

	if (sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr)) <= 0)
		errExit("send");
	fflush(0);

	// send two probes at 0.5 seconds interva;
	int  cnt = checkcfg(CFG_ARP_PROBES);
	uint8_t framerx[ETH_FRAME_LEN]; // includes eth header, vlan, and crc
	fd_set fds;
	FD_ZERO(&fds);
	FD_SET(sock, &fds);
	int maxfd = sock;
	struct timeval ts;
	gettimeofday(&ts, NULL);
	double timerend = ts.tv_sec + ts.tv_usec / 1000000.0 + 0.5;
	while (1) {
		gettimeofday(&ts, NULL);
		double now = ts.tv_sec + ts.tv_usec / 1000000.0;
		double timeout = timerend - now;
		ts.tv_sec = timeout;
		ts.tv_usec = (timeout - ts.tv_sec) * 1000000;
		int nready = select(maxfd + 1,  &fds, (fd_set *) 0, (fd_set *) 0, &ts);
		if (nready < 0)
			errExit("select");
		else if (nready == 0) { // timeout
			if (--cnt <= 0) {
				close(sock);
				return 0;
			}
			if (sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr)) <= 0)
				errExit("send");
			gettimeofday(&ts, NULL);
			timerend = ts.tv_sec + ts.tv_usec / 1000000.0 + 0.5;
			fflush(0);
		}
		else {
			// read the incoming packet
			int len = recvfrom(sock, framerx, ETH_FRAME_LEN, 0, NULL, NULL);
			if (len < 0) {
				perror("recvfrom");
				close(sock);
				return -1;
			}

			// parse the incoming packet
			if ((unsigned int) len < 14 + sizeof(ArpHdr))
				continue;
			if (framerx[12] != (ETH_P_ARP / 256) || framerx[13] != (ETH_P_ARP % 256))
				continue;
			memcpy(&hdr, framerx + 14, sizeof(ArpHdr));
			if (hdr.opcode == htons(1))
				continue;
			if (hdr.opcode == htons(2)) {
				// check my mac and my address

				// Does this even matter at this part?
				//if (memcmp(ifr.ifr_hwaddr.sa_data, hdr.target_mac, 6) != 0)
				//	continue;

				// Here, I think we should be testing the sender IP of the received ARP reply with the dstaddr 
				// we were originally probing for. If equal, then another host send a reply claiming to the be
				// the IP address we want to use
				uint32_t ip;
				//memcpy(&ip, hdr.target_ip, 4);
				memcpy(&ip, hdr.sender_ip, 4);
				//if (ip != srcaddr) {
				if (ip != dstaddr) {
					continue;
				}
				close(sock);
				return -1;
			}
		}
	}

	__builtin_unreachable();
}

<!-- gh-comment-id:1859423844 --> @brianvanderburg2 commented on GitHub (Dec 18, 2023): I don't think so. In your log file the network is already established and enters the jail. In mine, it fails at assigning an IP address and does not even enter the sandbox. Looking through the code and packet dump it makes sense. My system sends: sender mac: 02:ba:15:00:00:10 sender ip: 0.0.0.0 target mac: 00:00:00:00:00:00 target ip: 192.168.1.30 This other system on the network send a reply: sender mac: f0:d4:15:45:5d:5d sender ip: 192.168.1.122 target mac: 02:ba:15:00:00:10 target ip: 0.0.0.0 The code is checking if the target values of the reply are the same as the sender of the probe, which they are, so returns -1, and arp_random/arp_sequential then test the result, which is not 0, so returns 0 for the "ip" which then causes arp_assign to fail. ![image](https://github.com/netblue30/firejail/assets/2142408/9a037355-2ec3-442f-b3e4-02230377f890) This seems to confirm it should compare the received frame's sender IP with the probe frame's target IP, and if the same, treat as duplicate. In the code, it compares the received frame's target IP (hdr.target_ip) with the probe frame's sender IP (srcaddr) Here I don't think the MAC even matters, another ARP being received with the same sender IP that we are trying to use would mean a duplicate It also recommends if receiving an ARP request during probing with a target ip matching the IP we are trying to use and a source mac not equal to ours, to treat as duplicate as another station may be probing to use the IP at the same time as we are I don't have build tools on my system right now, but I'm thinking this is what the function should look like ```c int arp_check(const char *dev, uint32_t destaddr) { // RFC 5227 - using a source IP address of 0 for probing uint32_t srcaddr = 0; if (strlen(dev) > IFNAMSIZ) { fprintf(stderr, "Error: invalid network device name %s\n", dev); exit(1); } if (arg_debug) printf("Trying %d.%d.%d.%d ...\n", PRINT_IP(destaddr)); // find interface address int sock; if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) errExit("socket"); srcaddr = htonl(srcaddr); destaddr = htonl(destaddr); // Find interface MAC address struct ifreq ifr; memset(&ifr, 0, sizeof (ifr)); strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1); if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) errExit("ioctl"); close(sock); // configure layer2 socket address information struct sockaddr_ll addr; memset(&addr, 0, sizeof(addr)); if ((addr.sll_ifindex = if_nametoindex(dev)) == 0) errExit("if_nametoindex"); addr.sll_family = AF_PACKET; memcpy (addr.sll_addr, ifr.ifr_hwaddr.sa_data, 6); addr.sll_halen = ETH_ALEN; // build the arp packet header ArpHdr hdr; memset(&hdr, 0, sizeof(hdr)); hdr.htype = htons(1); hdr.ptype = htons(ETH_P_IP); hdr.hlen = 6; hdr.plen = 4; hdr.opcode = htons(1); //ARPOP_REQUEST memcpy(hdr.sender_mac, ifr.ifr_hwaddr.sa_data, 6); memcpy(hdr.sender_ip, (uint8_t *)&srcaddr, 4); memcpy(hdr.target_ip, (uint8_t *)&destaddr, 4); // build ethernet frame uint8_t frame[ETH_FRAME_LEN]; // includes eth header, vlan, and crc memset(frame, 0, sizeof(frame)); frame[0] = frame[1] = frame[2] = frame[3] = frame[4] = frame[5] = 0xff; memcpy(frame + 6, ifr.ifr_hwaddr.sa_data, 6); frame[12] = ETH_P_ARP / 256; frame[13] = ETH_P_ARP % 256; memcpy (frame + 14, &hdr, sizeof(hdr)); // open layer2 socket if ((sock = socket(PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) errExit("socket"); if (sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr)) <= 0) errExit("send"); fflush(0); // send two probes at 0.5 seconds interva; int cnt = checkcfg(CFG_ARP_PROBES); uint8_t framerx[ETH_FRAME_LEN]; // includes eth header, vlan, and crc fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); int maxfd = sock; struct timeval ts; gettimeofday(&ts, NULL); double timerend = ts.tv_sec + ts.tv_usec / 1000000.0 + 0.5; while (1) { gettimeofday(&ts, NULL); double now = ts.tv_sec + ts.tv_usec / 1000000.0; double timeout = timerend - now; ts.tv_sec = timeout; ts.tv_usec = (timeout - ts.tv_sec) * 1000000; int nready = select(maxfd + 1, &fds, (fd_set *) 0, (fd_set *) 0, &ts); if (nready < 0) errExit("select"); else if (nready == 0) { // timeout if (--cnt <= 0) { close(sock); return 0; } if (sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr)) <= 0) errExit("send"); gettimeofday(&ts, NULL); timerend = ts.tv_sec + ts.tv_usec / 1000000.0 + 0.5; fflush(0); } else { // read the incoming packet int len = recvfrom(sock, framerx, ETH_FRAME_LEN, 0, NULL, NULL); if (len < 0) { perror("recvfrom"); close(sock); return -1; } // parse the incoming packet if ((unsigned int) len < 14 + sizeof(ArpHdr)) continue; if (framerx[12] != (ETH_P_ARP / 256) || framerx[13] != (ETH_P_ARP % 256)) continue; memcpy(&hdr, framerx + 14, sizeof(ArpHdr)); if (hdr.opcode == htons(1)) continue; if (hdr.opcode == htons(2)) { // check my mac and my address // Does this even matter at this part? //if (memcmp(ifr.ifr_hwaddr.sa_data, hdr.target_mac, 6) != 0) // continue; // Here, I think we should be testing the sender IP of the received ARP reply with the dstaddr // we were originally probing for. If equal, then another host send a reply claiming to the be // the IP address we want to use uint32_t ip; //memcpy(&ip, hdr.target_ip, 4); memcpy(&ip, hdr.sender_ip, 4); //if (ip != srcaddr) { if (ip != dstaddr) { continue; } close(sock); return -1; } } } __builtin_unreachable(); } ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/firejail#3193
No description provided.