Expose API: bind remotePort on IPv4 and not just on IPv6
The web example shows an APi request such as this
curl http://[::2]/expose -d '{"Action":0,"LocalPort":31336,"RemotePort":31337,"Protocol":"tcp","Dynamic":false}'
It binds to[ ::2]:31337 on the EXIT. What's the correct API call to bind to 127.0.0.1:31337 or even 0.0.0.0:31337?
I tried curl http://127.0.0.2 and curl http://172.16.0.1 but to no avail.
What web example are you referring to? To my knowledge we don't have anything like that curl command documented.
It looks like that might be a manual (and undocumented/unsupported) way of doing what the wiretap expose command does, not sure why you'd want to do that rather than just using the wiretap binary as intended.
The wiretap expose command provides functionality similar to SSH remote port forwarding (-R); once configured, it binds to the specified remote port on the EXIT node (probably on interface 0.0.0.0, but I can't remember for sure) and redirects any traffic that hits that port to whatever local port (on the client node) was specified. It definitely shouldn't be binding the remote port to [::2], that's the internal Wiretap API address and that would be useless.
There currently isn't any way to specify what interface to bind either the local or remote port to (which is why I'm pretty sure they just bind to 0.0.0.0 under the hood), but if you can provide a use-case where that would be important we can consider that as a feature request.
I used the curl example from https://github.com/sandialabs/wiretap/wiki/How-it-Works plus some info Luke sent me in 2023 as well as reading the source code.
The ORIGIN (you call it the SERVER) runs WireGuard. The EXIT (you call it the CLIENT) runs wiretap. The vision from 2023 was to trigger the remote port forward from the ORIGIN (where there is no wiretap binary) with a request to ::2 (see WIki entry from above).
curl http://[::2]/expose -d '{"Action":0,"LocalPort":31336,"RemotePort":31337,"Protocol":"tcp","Dynamic":false}'
curl http://[::2]/expose -d '{"Action":1}'| jq -r
Wiretap binds on the EXIT to ::: not to 0.0.0.0 (that's not to bad. but the wiretap source then internally does some checks i think for IPv6 and a connection to EXIT:31337 triggers wiretap to send (via WG) to ORIGIN these IPv6 packets:
21:56:46.016189 IP6 ::2.42119 > fd:16::2.31336: Flags [S], seq 1256821628, win 25600, options [mss 1280,sackOK,TS val 1800197639 ecr 0,nop,wscale 7], length 0
Thus it forces me to bind to fd:16::2 on ORIGIN (rather than 172.16.0.2, or, 0.0.0.0). I propose a BIND=<IP here> in the curl request to allow the ORIGIN to specify to which IP the EXIT should bind to.
I hope this makes sense.
Thanks for the clarification. One correction, the Server is defined as the machine running wiretap serve (so has to have the binary on it), so your EXIT is the Server and the ORIGIN would be the Client (which runs WireGuard and needs root privs).
If I understand correctly, the problem you're describing is that expose'd ports are automatically bound locally (on the Client/ORIGIN) to the Wiretap interface/IP created by wg-quick, rather than one of the "real-world" pre-existing host interfaces (like eth0 or similar)?
Or did I get that backwards due to the confusion between server and client terminology?
Your definition of "server" confuses me because in my world the "server" is where "clients" connect to. E.g. the connection is from CLIENT to the SERVER. Servers are globally reachable. Clients are not. Multiple clients connect to Servers. In this scenario, multiple wiretap "clients" (you call them SERVERS) connect to my single WireGuard Server (you call this Client). In your world it's the other way around. I'll try to use your wording but please bear with me :>
SERVER (where wiretap runs, aka EXIT) makes a WG connection to the central WireGuard "HUB" (you call it CLIENT. I call it ORIGIN) where multiple wiretaps connect to from various locations around the world.
There is no wiretap binary on CLIENT. Only WireGuard. CLIENT uses curl to request a reverse port from SERVER/EXIT. The reverse port should listen on SERVER/EXIT and tunnel the port back to CLIENT/ORIGIN (e.g. "reverse"), using the WG tunnel.
On EXIT, where there is WIRETAP running, it binds to ::: port 31337.
A connection to EXIT:31337 is then forwarded to the CLIENT/ORIGIN to IPv6 fd:16::2 port 31336.
I propose a solution whereas CLIENT/ORIGIN can instruct SERVER/EXIT to bind to 0.0.0.0 instead and forward the packets to 172.16.0.2 instead of fd:16::2. That's the example that I used above but it wrongly binds to ::: and not 0.0.0.0 (or a user-specified IP).
hope this makes sense now :>
No problem, I totally agree it's confusing, and one of these days I'm going to pick better terms for them and update all the code and documentation to avoid this kind of confusion. Exit and Origin actually might be good candidates for new names...
So you want the Server/EXIT /expose API to have an option to bind on the IPv4 0.0.0.0:31337 socket (address and port) instead of the IPv6 [::]:31337 socket, AND optionally forward traffic it receives to the Client/ORIGIN's IPv4 172.16.0.2:31336 socket instead of the IPv6 [fd:16::2]:31336 socket? And ideally let the user specify arbitrary IPs for both of those options?
If I got that right, can you describe a use case where you would need to be able to do that to accomplish a particular task? That will help with feature prioritization.
clarification and using the remote port forward example from above:
If the /expose binds to an IPv4 (0.0.0.0:31337) then WT can assume to forward the stream back to the IPv4 address of the ORIGHI (172.16.0.2:31336).
If the /expose binds to IPv6 ([::]:31337) then WT can assume to forward the stream back to the IPv6 address of the ORIGIN ([fd:16::2]:31336)
It would be an absolute delight if the /expose can also be configured to forward :31337 back to a custom IP (say 1.2.3.4). E.g. a connection to <EXIT-IP>:31337 would be forwarded to 1.2.3.4:31336 (rather then 172.16.0.2:31336 or [fd:16::2]:31337).
Use case for IPv4 (let me know if you also need a use case for reverse-port in general)
- Many tools do not support IPv6 (for example,
netcat -vnlp 31336only binds to IPv4) - Most internal networks use IPv4.
- TCP use case: Start a socks5h proxy on ORIGIN:31336 and forwards 0.0.0.0:1080 from the EXIT to the ORIGIN's socks5h. It is especially important to circumvent IDS and HTTPS proxies or GreatFirewallofChina: The WG/WT connection is already allowed. Now while working on the EXIT or on any host within the EXIT's network, I can use the port forward on EXIT-IP:1080 to reach my socks5h at ORIGIN:31336 and transfer data via my own socks5h proxy (which is not subject to IDS/GFW)
- UDP use case: DNS traffic. I can set a reverse forward from EXIT:53 back to 172.16.0.2:5353 (or back to 1.1.1.1:53). While working on the EXIT or on any host within the EXIT's network: It would allow me to use "my resolver on my ORIGIN that I control" rather than the default resolve on the EXIT's network (which might be subject to restrictions and filtering).
Got it. As a temporary solution I think you can use the --disable-ipv6 flag when running the configure command, and that will cause Wiretap to only use IPv4 addresses for everything.
We'll look into adding options to specify custom IPs to bind and forward to. That will give this feature more parity with how SSH -R works, which would be good in general, In the unlikely even that's not doable, I'll see if we can add a flag to the expose command to force it use IPv4 even if IPv6 is available so you don't have to generate a separate config just to do that.
Thank you for your workaround suggestion. I'm using --disable-ipv6 as well as -0 172.16.0.0 on the EXIT/SERVER.
The -0 is needed because the default API [::2] is not reachable when IPv6 is used. It works great with both parameters.
I picked 172.16.0.0 for the API as this is the "network address" (similar to a broadcast address) and normally not useable anyhow (and thus an ideal IPv4 equivalent to [::2]).