Add Interface socket option for binding to a network interface#80
Add Interface socket option for binding to a network interface#80karandesai2005 wants to merge 6 commits intorapid7:masterfrom
Interface socket option for binding to a network interface#80Conversation
Adds an 'Interface' option to Rex::Socket::Parameters so callers can
bind a socket to a specific network interface by name:
Rex::Socket::Udp.create('Interface' => 'eth0', ...)
Rex::Socket::Tcp.create('Interface' => 'eth0', ...)
Rex::Socket::TcpServer.create('Interface' => 'eth0', ...)
This is needed for broadcast-capable sockets (e.g. DHCP server) where
binding by IP address alone is insufficient to receive broadcast frames.
Changes:
- Add 'Interface' param to Rex::Socket::Parameters
- Call setsockopt(SO_BINDTODEVICE) in Comm::Local after bind, before
connect/listen (Linux only; raises BindFailed on other platforms)
- Raise Rex::BindFailed if Interface is combined with a proxy
- Raise Rex::BindFailed on invalid interface name (ENODEV) or
insufficient permissions (EPERM)
- Add RSpec tests for all new behaviors
See: rapid7/metasploit-framework#21114
- Adds reason: keyword to all BindFailed raises for clearer errors - Adds macOS support using IP_BOUND_IF (guarded by defined?) - Improves developer experience when debugging interface binding issues Addresses review feedback on PR rapid7#80.
|
I've pushed an update addressing both points:
Would love your thoughts on whether this covers the macOS case, and any suggestions for Windows support as well. |
- Adds clear BindFailed reason for Windows - Keeps fallback for other unsupported platforms Addresses maintainer feedback
|
pushed an update addressing your feedback:
Also re-ran the full test suite with elevated permissions:
|
|
@zeroSteiner following up on our Slack discussion — pushed the Platform matrix is now complete:
Also did a hardening pass:
Manually verified on Linux (all three cases pass), Windows approach 178 tests, 0 failures. Ready for final review whenever you get a chance. |
- Resolve interface name to IP via Socket.getifaddrs on Windows, overriding param.localhost so existing bind() handles the rest - Replace Socket.if_nametoindex on macOS with getifaddrs + ifindex since if_nametoindex is not available in all Ruby builds - Add rescue SystemCallError to Linux and macOS setsockopt blocks to prevent socket leaks on unexpected errors - Distinguish between interface not found vs interface has no IPv4 address in the Windows getifaddrs lookup - Add rescue for getifaddrs enumeration failures on Windows
549a3a5 to
125e231
Compare
lib/rex/socket/parameters.rb
Outdated
| self.timeout = hash['Timeout'].to_i | ||
| end | ||
|
|
||
| self.interface = hash['Interface'].to_s.strip if hash['Interface'] |
There was a problem hiding this comment.
| self.interface = hash['Interface'].to_s.strip if hash['Interface'] | |
| self.interface = hash['Interface'].to_s.strip if hash['Interface'] && !hash['Interface'].strip.empty? |
lib/rex/socket/comm/local.rb
Outdated
| ::Socket.getifaddrs.each do |ifaddr| | ||
| next unless ifaddr.name == param.interface | ||
| iface_found = true | ||
| next unless ifaddr.addr&.ipv4? |
There was a problem hiding this comment.
The parameters has a #v6 attribute
rex-socket/lib/rex/socket/parameters.rb
Lines 206 to 215 in 146bc6b
When that's set the address should be IPv6 and we'd want to select a v6 address not an v4 address. At this time, it's safe to assume that if it's not IPv6 that it's IPv4 because in practice we only support AF_INET and AF_INET6 nothing like AF_UNIX but maybe someday.
- Guard interface param against whitespace-only strings - Add comment explaining why Interface + proxy raises immediately - Use param.dup before mutating localhost to avoid side effects on the caller's instance - Select IPv6 address when param.v6 is true, IPv4 otherwise
|
@smcintyre-r7 addressed all four points:
178 tests, 0 failures. |
smcintyre-r7
left a comment
There was a problem hiding this comment.
Found a bug while testing, .is_osx should be .is_macosx.
Co-authored-by: Spencer McIntyre <58950994+smcintyre-r7@users.noreply.github.com>
Summary
Adds an
Interfaceoption toRex::Socket::Parametersso callers canbind a socket to a specific network interface by name:
This is needed for broadcast-capable sockets (e.g. the DHCP server module)
where binding by IP address alone is insufficient — the socket must be bound
to a specific interface to send and receive broadcast frames correctly.
Changes
lib/rex/socket/parameters.rb— addsinterfaceattribute, parsed fromthe
'Interface'hash key, whitespace-stripped,nilby defaultlib/rex/socket/comm/local.rb— two additions insidecreate_by_type:Rex::BindFailedimmediately ifInterfaceis combinedwith a proxy (incompatible by design)
setsockopt(SOL_SOCKET, SO_BINDTODEVICE, interface)after thenormal bind block, before
SO_BROADCAST— Linux only. Non-Linuxplatforms raise
Rex::BindFailed(macOSIP_BOUND_IFis not availablein the current Ruby stdlib and can be added as a follow-up)
spec/rex/socket/parameters_spec.rb— new#interfacedescribe blockspec/rex/socket/comm/local_spec.rb— newInterface optiondescribeblock covering proxy guard, invalid interface name, loopback success
(root-guarded), and non-Linux platform
Testing
Notes
IP_BOUND_IFavailability across Ruby versions is confirmed