Hi,
On 10/13/25 06:50, Amos Jeffries wrote:
On 13/10/2025 05:00, Joan Lledó wrote:
From: Joan Lledó
* The Hurd module relies on libpcap to work with BPF filters.
* Addresses and routes are configured via ioctls to the stack.
* Only IPv4 over Ethernet is supported so far.
Might be better to go the other way and "only" support IPv6.
IPv4 addresses are a subset of IPv6 address space, and both can be
passed around as generic struct sockddr_storage objects or struct
sockaddr* pointers. Any code that needs more detail than "its an IP" can
use their *_family member to select logic paths.
Yeah, it was just a decision to make it easier for me, like "I'll worry
about IPv6 later on". I thought IPv6 would need more time to investigate
and decided to stick to IPv4 which I'm more familiar with.
+#ifdef INET
+int
+if_route(unsigned char cmd, const struct rt *rt) {
+ int err;
+ struct dhcpcd_ctx *ctx;
+ struct kroute krt;
+
+ if_copykrt(&krt, rt);
+
+ ctx = rt->rt_ifp->ctx;
+
+ err = if_ioctl(ctx,
+ cmd == RTM_DELETE ? SIOCDELRT : SIOCADDRT, &krt, sizeof(krt));
+
+ return err;
+}
It is not clear why the above is IPv4-specific. Seems to me the logic is
shared by all IP versions (past, present, or future).
Yeah, I think this particular function is valid for IPv6 and probably
won't need any change when I add support for IPv6.
+
+int
+if_address(unsigned char cmd, const struct ipv4_addr *ia) {
Is "ipv4_addr" actually needed?
anything it provides that the standrd in_addr or sockaddr_in types do
not?
Perhape consider making this a struct sockaddr_storage or struct
addrinfo parameter. The logic of this function can be IP-version
agnostic if care is taken regarding the memory allocations.
This is defined by dhcpcd and platforms are supposed to overwrite it if
needed:
https://github.com/NetworkConfiguration/dhcpcd/blob/a8cdc28cc96f799a3c5c3b4f0b858ebeb2c5d97d/src/if.h#L260
So we can't change the function signature.
+ int err;
+ struct ifreq ifr;
+ struct sockaddr_in *sin;
+ struct dhcpcd_ctx *ctx;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ia->iface->name, sizeof(ifr.ifr_name));
+ sin = (struct sockaddr_in *)&ifr.ifr_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_addr = ia->addr;
+ sin->sin_len = sizeof(struct sockaddr_in);
+
+ ctx = ia->iface->ctx;
+
+ err = if_ioctl(ctx,
+ cmd == RTM_DELADDR ? SIOCDIFADDR : SIOCSIFADDR, &ifr,
sizeof(ifr));
+
+ return err;
+}
+
+int
+if_addrflags(__unused const struct interface *ifp, __unused const
struct in_addr *addr,
+ __unused const char *alias) {
+ /* TODO */
+ return 0;
+}
+#endif
+
+#ifdef INET6
+int
+if_address6(__unused unsigned char cmd, __unused const struct
ipv6_addr *ia) {
+ /* TODO: We could add custom IOCTLs for this */
FWIW, It may be better to refactor the existing IOCTLs to handle struct
ifreq with non-IPv4 sin_family.
The struct ifreq is defined with sockaddr members. In modern IP code
"struct sockaddr" should be treated as a way to access the *_family
member of a struct sockaddr_storage, sockaddr_in, or sockaddr_in6 to
determine which one the rest of the 'char data[]' represents.
Be aware that all IP-type interfaces can nowdays be assigned multiple
IPs. IPv4 used to only allow one address, but has been extended for
compatibility with IPv6.
For example;
2: enp2s0: ...
inet 192.168.1.7/24 brd 192.168.1.255 scope global dynamic
inet 10.0.1.7/24 brd 10.0.1.255 scope global dynamic
inet6 fd58:bad4:.../64 scope global dynamic
inet6 fe80::.../64 scope link
(note 2x IPv4 and 2x IPv6)
Of course, I'm open to suggestions on how to proceed.
+/*
+ * Initialize the given interface.
+ * For now, we assume the interface is ethernet and try to get its
MAC address
+ */
+int
+if_init(struct interface *ifp) {
+ mach_port_t master;
+ kern_return_t kr;
+ mach_msg_type_number_t count = 2;
+ device_t mach_dev;
+ int net_address[2];
+
+ /* Nothing to do for loopback */
+ if (ifp->flags & IFF_LOOPBACK)
+ return 0;
+
+ ifp->hwtype = ARPHRD_ETHER;
+
+ /* Open the device. Try devnode first */
+ master = file_name_lookup(ifp->name, O_READ | O_WRITE, 0);
+
+ if (master != MACH_PORT_NULL)
+ kr = device_open(master, D_WRITE | D_READ, "eth", &mach_dev);
+ else {
+ /* If unsuccessful, try Mach device */
+ kr = get_privileged_ports(NULL, &master);
+
+ if (kr) {
+ logerrx("%s: cannot open device: %s", __func__, ifp->name);
+ return -1;
+ }
+
+ kr = device_open(master, D_READ | D_WRITE, ifp->name,
&mach_dev);
+ }
+
+ mach_port_deallocate(mach_task_self(), master);
+
+ if (kr) {
+ logerrx("%s: cannot open device: %s", __func__, ifp->name);
+ return -1;
+ }
+
+ /* Get the MAC address */
+ kr = device_get_status (mach_dev, NET_ADDRESS, net_address, &count);
+ if (kr) {
+ logerrx("%s: cannot get device MAC address: %s", __func__,
ifp->name);
+ goto eexit;
+ }
+
+ /* We expect 6 bytes. Don't allow partial or incomplete addresses */
+ if (count * sizeof (int) < ETH_HWADDR_LEN) {
+ logerrx("%s: insufficient MAC address data: %d bytes",
__func__, count * sizeof(int));
+ goto eexit;
+ }
+
+ net_address[0] = (int)ntohl((uint32_t)net_address[0]);
+ net_address[1] = (int)ntohl((uint32_t)net_address[1]);
I expect this will have issues on 64-bit build where "int" is more than
32-bits long.
I think in that case the whole MAC would fit in one integer and the
second integer would be unused, but I don't think it would fail.
Also, modern networking has EUI-64 that may occur in place of the EUI-48
(aka "MAC" or "L2" address). It would be good to check for such instead
of assuming EUI-48/MAC is always given.
This is not supported by lwip which assumes 48bit MACs
+
+ /* Set MAC hardware address */
+ ifp->hwaddr[0] = GET_HWADDR_BYTE(net_address, 0);
+ ifp->hwaddr[1] = GET_HWADDR_BYTE(net_address, 1);
+ ifp->hwaddr[2] = GET_HWADDR_BYTE(net_address, 2);
+ ifp->hwaddr[3] = GET_HWADDR_BYTE(net_address, 3);
+ ifp->hwaddr[4] = GET_HWADDR_BYTE(net_address, 4);
+ ifp->hwaddr[5] = GET_HWADDR_BYTE(net_address, 5);
+ ifp->hwlen = ETH_HWADDR_LEN;
+
+ return 0;
+
+eexit:
+ device_close(mach_dev);
+ mach_port_deallocate(mach_task_self(), mach_dev);
+ return -1;
+}
+
Thanks for your comments!