dhcp: [BUG] Binding to an interface on MacOS does not function correctly
When picking an interface on MacOS, the server will correctly listen for packets, but when doing an offer by replying to broadcast address, the system will route that to the Gateway interface only.
This is likely due to:
// BindToInterface emulates linux's SO_BINDTODEVICE option for a socket by using
// IP_RECVIF.
func BindToInterface(fd int, ifname string) error {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return err
}
return syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVIF, iface.Index)
}
I am hardly an expert in stuff this low in the stack, though from the looks of it this is just filtering the received traffic, but does nothing to direct the outbound traffic.
I have also found this, perhaps that might be the solution: 🤷‍♂ :
https://stackoverflow.com/a/57013928
There is IP_BOUND_IF socket option
int idx = if_nametoindex("en0");setsockopt(sockfd, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx))
Spent quite a few hours with Wireshark trying to figure out why the server was not sending an offer to the client, all the while not raising any alarms.
Will add a minimum viable example to illustrate the issue soon.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 16
I can rebase and send a proper PR with these changes sometime this week. I still can’t test it but now that the poc at least works it’ll be quicker to test. Thanks a bunch for your research @NonLogicalDev
IP_RECVIFis settable for both V4 and V6 sockets, like a few other ipv4-level socket options, eg IP_TOS or IP_TTL (you need to set them at theIPPROTO_IPlevel, but it’s still accepted if the socket is bound to ipv6). And the darwin-xnu code it doesn’t have the “option settable only for IPv4” block that you pointed out forIP_BOUND_IF:There’s 2 paths here, we want the socket not to receive packets that are sent to other interfaces, in addition to sending them on the correct one. So to test it you need a darwin machine with 2 interfaces and sending dhcp messages to both, and check that the dhcp server only receives packets for the interface it is bound to. Afaict from the documentation, the receive path would still need
IP_RECVIFOtherwiseIP_BOUND_IFshould be enough to ensure we send on the correct interface yesYeah I think that would be reasonable, try both and return success if at least one succeeds
Hey @pmazzini / @Natolumin Appologies for admittedly long time to get this tested. I did test it though and is seems to be working with a small caveat:
In this snippet: https://github.com/Natolumin/dhcp/blob/darwin_bindtointerface/interfaces/bindtodevice_darwin.go
This is actually slightly wrong:
The
unix.SOL_SOCKET, unix.SO_TYPEreturn the socket type, not proto or domain. It will actually returnunix.SOCK_STREAMorunix.SOCK_DGRAM.AFAIK there is no way that I know of to tease out the domain information out of socket FD on darwin. =[ The documentation is either missing or it is just not supported via socket options.
Here is Linux for reference: (https://man7.org/linux/man-pages/man7/socket.7.html)
And darwin: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getsockopt.2.html
Which only mentions SO_TYPE (which as I established does not return what we want).
The good news though, if I change the code by taking out the switch statement and only leaving the IPPROTO_IP socket option (essentially hardcoding the IPV4 only operation).
Diff:
Then it WORKS!!! I tested it and my RPI I connected to a USB Ethernet adapter got its IP just fine. So that can totally be used as a starting point for a more production ready code, but we still need to figure out how to correctly apply either:
unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)orunix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)Which is going to be the hard part… Unless there is someone with Darwin Fu who knows how to tease this information out of the file descriptor.
Update:
Found this:
https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/bsd/netinet6/ip6_output.c?ts=4#L2359
https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/bsd/netinet6/ip6_output.c?ts=4#L2779
Perhaps if someone can find if that
in6p->inp_vflagis in any shape or form exposed via some syscall.Here is the gist that details my test harness: https://gist.github.com/NonLogicalDev/f7d1afdac2097f40997a194df60d59e1
I found some time to write a patch to try the IP_BOUND_IF option. Could you try this branch: https://github.com/Natolumin/dhcp/tree/darwin_bindtointerface ?
Note while trying this I found that client4/nclient4 don’t even build on darwin so yeah, our testing isn’t great there
Here are some instructions for using the fork if you ever need them, I hope they’re correct (feel free to ignore them if you know how to do this):
I think you should be able to test it with the following:
Then build your downstream application as usual.
If you use modules in your downstream binary, you’ll need to checkout the fork somewhere and create a go.mod file in the fork with
go mod init, then add a replace directive in the go.mod of your own application:@Natolumin I do have a few MacOS machines a linux host, and a few RaspberryPIs lying around. I would gladly participate, and learn more about DHCP in the process.