Skip to content

hs.socket: allow listening on a specific interface - #3899

Open
anandghegde wants to merge 1 commit into
Hammerspoon:masterfrom
anandghegde:feat/socket-listen-interface
Open

anandghegde wants to merge 1 commit into
Hammerspoon:masterfrom
anandghegde:feat/socket-listen-interface

Conversation

@anandghegde

Copy link
Copy Markdown

Fixes #3634.

Both hs.socket and hs.socket.udp server sockets currently bind to every interface, with no way to narrow that down. As @cmsj noted on the issue, CocoaAsyncSocket already exposes the APIs for this:

CocoaAsyncSocket supports binding to either interface or address, and I agree we should do this.

What changed

listen() on both extensions takes an optional interface string:

-- only reachable from this machine
server = hs.socket.server(9001, "loopback", callback)

-- only on the bridge interface to a VM
hs.socket.udp.new(callback):listen(9001, "bridge100")

The interface can be a name ("en1", "lo0"), the corresponding IP address ("192.168.4.34"), or the special strings "localhost"/"loopback" — whatever GCDAsyncSocket accepts.

  • socket_listen() now calls -acceptOnInterface:port:error: instead of -acceptOnPort:error:
  • socketudp_listen() now calls -bindToPort:interface:error: instead of -bindToPort:error:
  • hs.socket.server() / hs.socket.udp.server() take the same optional argument between the port and the callback

Backwards compatibility

Passing no interface is unchanged. In CocoaAsyncSocket, -acceptOnPort:error: is literally return [self acceptOnInterface:nil port:port error:errPtr];, and -bindToPort:error: is return [self bindToPort:port interface:nil error:errPtr];, so the nil path is the same code as before.

The server() constructors shuffle their arguments when the second one is a function, so hs.socket.server(port, fn) keeps working.

Unix domain sockets aren't bound to an interface, so hs.socket:listen(path, interface) logs a warning and ignores the interface rather than failing.

Tests

Five new tests in test_socket.lua / test_udpsocket.lua, registered in Hammerspoon Tests/HSsocket.m:

  • testTcpListenerSocketInterfaceAttributes / testUdpListenerSocketInterfaceAttributes — a listener bound to "loopback" reports localHost == "127.0.0.1" rather than "0.0.0.0"
  • testTcpListenerSocketInterfaceConnect — a client can still connect to a loopback-bound server and the server reads what it wrote
  • testTcpListenerSocketBadInterface / testUdpListenerSocketBadInterface — an unknown interface returns nil and leaves the socket unbound; a Unix domain socket ignores the interface

Local run of the HSsocket suite: 46 passed, 1 failed. The failure is testUdpReusePort, which also fails on an unmodified master here — it broadcasts to 255.255.255.255, which this machine doesn't allow.

hs.socket:listen() and hs.socket.udp:listen() take an optional interface
argument, so a server socket can be bound to one interface instead of
every one. Useful for talking to a VM over a bridge interface, or for
keeping a socket reachable only from this machine.

CocoaAsyncSocket already has -acceptOnInterface:port:error: and
-bindToPort:interface:error:, both of which are the single-argument
variants when the interface is nil, so omitting it behaves exactly as
before. Unix domain sockets aren't bound to an interface, so passing one
alongside a path logs a warning and is ignored.

hs.socket.server() and hs.socket.udp.server() grow the same optional
argument between the port and the callback.

Fixes Hammerspoon#3634

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request does not contain a valid label. Please add one of the following labels: ['pr-fix', 'pr-change', 'pr-feature', 'pr-maintenance']

@anandghegde

Copy link
Copy Markdown
Author

CI is asking for one of the pr-fix / pr-change / pr-feature / pr-maintenance labels, and I can't set labels as an outside contributor. This one is pr-feature — could a maintainer add it so the check goes green? Happy to rebase if anything has moved in the meantime.

@anandghegde

Copy link
Copy Markdown
Author

The required pr-feature label is still missing. I attempted to add it, but GitHub denied label-management permission for this fork PR. Could a maintainer add pr-feature so the automated check can pass?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hs.socket and hs.socket.udp listens on all interfaces

1 participant