-
Notifications
You must be signed in to change notification settings - Fork 33
Add Interface socket option for binding to a network interface
#80
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
base: master
Are you sure you want to change the base?
Changes from all commits
f2445c0
5769bf7
932a664
125e231
d42e115
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -158,6 +158,47 @@ def self.create_by_type(param, type, proto = 0) | |||||||
| # Notify handlers of the before socket create event. | ||||||||
| self.instance.notify_before_socket_create(self, param) | ||||||||
|
|
||||||||
| # Binding to a specific interface while routing through a proxy is not | ||||||||
| # supported. The proxy comm handles its own socket creation and ignores | ||||||||
| # the interface option entirely, so we fail fast here rather than | ||||||||
| # silently binding to the wrong interface. | ||||||||
| if param.interface && !param.interface.empty? && param.proxies? | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: 'Interface option is incompatible with proxy use'), caller | ||||||||
| end | ||||||||
|
|
||||||||
| if param.interface && !param.interface.empty? && Rex::Compat.is_windows | ||||||||
| iface_ip = nil | ||||||||
| iface_found = false | ||||||||
| begin | ||||||||
| ::Socket.getifaddrs.each do |ifaddr| | ||||||||
| next unless ifaddr.name == param.interface | ||||||||
| iface_found = true | ||||||||
| if param.v6 | ||||||||
| next unless ifaddr.addr&.ipv6? | ||||||||
| else | ||||||||
| next unless ifaddr.addr&.ipv4? | ||||||||
| end | ||||||||
| iface_ip = ifaddr.addr.ip_address | ||||||||
| break | ||||||||
| end | ||||||||
| rescue ::SystemCallError, ::SocketError => e | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: "Failed to enumerate interfaces: #{e.message}"), caller | ||||||||
| end | ||||||||
| if iface_ip.nil? | ||||||||
| reason = if iface_found | ||||||||
| "Interface #{param.interface} has no #{param.v6 ? 'IPv6' : 'IPv4'} address" | ||||||||
| else | ||||||||
| "Interface #{param.interface} not found" | ||||||||
| end | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: reason), caller | ||||||||
| end | ||||||||
| param = param.dup # avoid mutating the caller's instance | ||||||||
| param.localhost = iface_ip | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this approach may need to be modified ever so slightly. If we do this, then the
Suggested change
Alternatively you'd need to use a |
||||||||
| end | ||||||||
|
|
||||||||
| # Create the socket | ||||||||
| sock = nil | ||||||||
| if param.v6 | ||||||||
|
|
@@ -185,6 +226,46 @@ def self.create_by_type(param, type, proto = 0) | |||||||
| end | ||||||||
| end | ||||||||
|
|
||||||||
| if param.interface && !param.interface.empty? | ||||||||
| if Rex::Compat.is_linux | ||||||||
| begin | ||||||||
| sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BINDTODEVICE, param.interface) | ||||||||
| rescue ::Errno::ENODEV, ::Errno::ENXIO | ||||||||
| sock.close | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: "Interface #{param.interface} not found"), caller | ||||||||
| rescue ::Errno::EPERM | ||||||||
| sock.close | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: "Binding to interface #{param.interface} requires elevated privileges"), caller | ||||||||
| rescue ::SystemCallError | ||||||||
| sock.close | ||||||||
| raise | ||||||||
| end | ||||||||
| elsif Rex::Compat.is_osx && defined?(::Socket::IP_BOUND_IF) | ||||||||
| begin | ||||||||
| idx = ::Socket.getifaddrs.find { |ifaddr| ifaddr.name == param.interface }&.ifindex | ||||||||
| if idx.nil? | ||||||||
| sock.close | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: "Interface #{param.interface} not found"), caller | ||||||||
| end | ||||||||
| sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_BOUND_IF, [idx].pack('I')) | ||||||||
| rescue ::SocketError, ::Errno::ENXIO | ||||||||
| sock.close | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: "Interface #{param.interface} not found"), caller | ||||||||
| rescue ::SystemCallError | ||||||||
| sock.close | ||||||||
| raise | ||||||||
| end | ||||||||
| elsif !Rex::Compat.is_windows | ||||||||
| sock.close | ||||||||
| raise Rex::BindFailed.new(param.localhost, param.localport, | ||||||||
| reason: 'Interface binding is not supported on this platform'), caller | ||||||||
| end | ||||||||
| end | ||||||||
|
|
||||||||
| # Configure broadcast support for all datagram sockets | ||||||||
| if type == ::Socket::SOCK_DGRAM | ||||||||
| sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true) | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind please adding a comment here summarizing why we're doing this? We/I will surely forget in the future how we landed on this solution.