Hacker News new | past | comments | ask | show | jobs | submit login
A userspace WireGuard client that exposes itself as a proxy (github.com/octeep)
340 points by octeep on March 26, 2022 | hide | past | favorite | 87 comments

Plug for my own similar project: https://github.com/aramperes/onetun

Though admittedly, mine doesn't have SOCKS support, and the code is not as lean as yours!

Neat! How does yours work? The Go version of this is easy to write, because wireguard-go has helpers to drive Netstack, which is also written in Go. But yours is in Rust. (I could have dug in, but I'm being lazy).

Sure, essentially it's a TCP and UDP server that:

- receives connections and assigns a random internal port for it

- wraps the data packets in a transport(TCP/UDP) packet that's routed from the internal port to the remote

- wraps the transport in an IP packet that's routed from the address assigned the the proxy, and to the remote WireGuard address

- wraps that with WireGuard's protocol (encryption)

- sends off the encrypted packet to the public WireGuard UDP endpoint

The packet-wrapping and TCP state machine is implemented using smoltcp in Rust, which is similar to netstack in Go

The WireGuard encapsulation and state machine is implemented with boringtun, Cloudflare's implementation of the WireGuard client in Rust.

I do have a more thorough architecture explanation in the Readme: https://github.com/aramperes/onetun#architecture

smoltcp! That's what I was looking for. Thanks!

See also https://github.com/russdill/tunsocks

Basically a kitchen sink for this sort of thing using lwIP for it's IP stack

This is good stuff. There should be, ideally, one really good tool for doing plugboard-y stunnel-y type stuff with user-mode WireGuard, where you don't need root to set up the tunnels, and this could be that.

Excellent. I've been wanting something like this for a while to use with Mullvad, but had to resort to docker containers instead.

This plays well with proxychains to make proxy-naive programs use SOCKS5 proxies.

Mullvad exposes socks proxies over wireguard.

Mullvad's SOCKS proxies are only accessible if you've got the VPN active; they're an additional layer, not an alternative.

That is not true. You can route only traffic to the SOCKS proxies if you choose and nothing else.

What do you mean by that? Mullvad doesn't have a generally accessible socks proxy like PIA does, that works with or without having the VPN on. The proxy is a localhost one for the most part so you can prevent an app from going online without having your vpn client on first.

Do you have a source for this? Every page on Mullvad I've seen says you need to have the VPN active: https://mullvad.net/en/help/socks5-proxy/

> If you configure your browser, for example, to use the SOCKS5 proxy, it will direct all of your internet access via the proxy which is only accessible through Mullvad. So if you haven't turned on the app, your browser will prevent all internet access and therefore won't leak any information.

You just route with wireguard, or to access socks proxies on other locations. Then you have socks only mullvad.

if you are routing via wireguard, the VPN is active. Note, people are not talking about the mullvad app here (which is not required at all)

The OP post also needs the "VPN active" (ie: an active connection to the VPN server). Having the VPN active does not mean it's routing all your traffic through it, what traffic is routed through it is controlled by your wireguard config/routing table. Having

  AllowedIPs =
in your wireguard config will only forward traffic bound to (the IP Mullvad uses for their SOCKS5 proxies on wireguard connections) to the VPN server, which more or less achieves the same result as the OP post. You can access the SOCKS proxy without having your whole internet routed through the VPN.

An active connection but not an active system-wide network interface. The point of these tools is that the wireguard client is encapsulated together with the proxy in a simple user-mode process, with no integration into the host network stack.

I love wireguard but my one gripe with it is that its not a true data link layer. You have to give it routing information through an out of band mechanism “AllowedIPs.” One downside is that you can’t have two peers that act as general routers on the same wireguard network. With Ethernet, you can have multiple nodes in the same subnet acting as generic routers, it’s just a matter of sending IP packets to that host.

I hope someday wireguard addresses this issue and makes itself fully transparent as a data link layer.

> You have to give it routing information through an out of band mechanism “AllowedIPs.” One downside is that you can’t have two peers that act as general routers on the same wireguard network

This is a common misconception, due to that this is the way wg-quick works (unfortunately IMO; presumably to make it easier, and I guess wg-quick was never meant for people with advanced needs). On a lower level, AllowedIPs is really just "allowed IPs", and does no routing. You can have multiple active peers with overlapping AllowedIPs.

If you set up the tunnel through other means, you can make your own routes.

For example in systemd-networkd, see `RouteTable` under the `[WireguardPeer]` section of systemd.netdev(5).

(This was unfortunately broken for a brief while in systemd in Jan, but should now be fixed again: https://github.com/systemd/systemd/pull/22136. If it's not clear from the link, old and current behavior are that no routes are added unless RouteTable is explicitly set)

You should also be able to set it up manually and then add routes, policies and rules manually however you would otherwise.

(You're of course right on the protocol layer, but that is not the cause of the problem you want to solve)

Are you sure what you described works? Last I tried it didn't work, and this is explicitly stated on wireguard.com:

> In other words, when sending packets, the list of allowed IPs behaves as a sort of routing table, and when receiving packets, the list of allowed IPs behaves as a sort of access control list.

> This is what we call a Cryptokey Routing Table: the simple association of public keys and allowed IPs.

Which is, I believe, also why zx2c4 called to revert the whole systemd-networkd feature.

I would really want it to work as it would simplify my network configuration by a bit. Please share a working example if you are able to make it work, thanks!

Last I tried that I used iproute 2 to manually setup interfaces, use wg setconf to load WireGuard configurations. So I don't think it's my tool to blame.

> Are you sure what you described works? Last I tried it didn't work, and this is explicitly stated on wireguard.com:

If we're talking simply about decoupling routing from AllowedIPs, yes, using that right now and set it up several times. For redundant routers, see below.

> > In other words, when sending packets, the list of allowed IPs behaves as a sort of routing table

This does seem in conflict with my understanding... Depending on exactly what devil-details go into that "sort of", of course. Not deep enough into it to tell you, though.

> Which is, I believe, also why zx2c4 called to revert the whole systemd-networkd feature.

Rather the opposite AIUI; To allow for setting routes explicitly (which introducing the wg-quick behavior broke[0]).

What started making it click for me was this ArchWiki section[1]. The discussion under this GH issue may also provide some pointers[2]. Also [3]. IIRC I did get multiple outbound redundant routers with failover in the end. There may be WG-specific gremlins I glanced over but ascribed it to not fully grokking the Linux IP stack and issues with *tables in general at the time - the goal I had is hairy enough without Wireguard in the mix. Please report back here on your progress if you have the time :)

[0]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1003955

[1]: https://wiki.archlinux.org/title/WireGuard#systemd-networkd:...

[2]: https://github.com/systemd/systemd/issues/14176

[3]: https://www.eisfunke.com/article/docker-wireguard-systemd.ht...


EDIT: Went back to take a look and I never did get proper HA routing sorted - ended up "solving" it with a script regularly checking reachability and bringing routes up/down accordingly. No need to bring the actual WG interfaces or IP assignments up/down for that, though.

I'm not sure how you conclude that AllowedIPs does no routing.

> On a lower level, AllowedIPs is really just "allowed IPs", and does no routing.

This is contrary to what the official documentation says https://www.wireguard.com/#cryptokey-routing

> You can have multiple active peers with overlapping AllowedIPs.

You can, but the most specific CIDR wins route selection, which is exactly what *routing* does.

Key words from that link emphasized:

> Cryptokey Routing, which works by associating public keys with a list of tunnel IP addresses that are allowed inside the tunnel.

> In the server configuration, when the network interface wants to send a packet to a peer (a client), it looks at that packet's destination IP and compares it to each peer's list of allowed IPs to see which peer to send it to.

> the list of allowed IPs behaves as a sort of routing table

> This is what we call a Cryptokey Routing Table

You can just set your peers on separate wg interfaces. At least on Linux and BSD, you have tables to control routing before packets reach the interface.

So you can have two wg interfaces, each with a single but distinct peer both with CIDR (or what have you), and use ip-route/nftables as usual to pick the appropriate outgoing interface.

It makes sense if you think of each wg interface as a NIC connected to an L3 switch, and each peer to a host connected to another port on the same switch. AllowedIPs would correspond to the table+ACL in the switch.

But yeah, me saying it "does no routing" was not really correct. But that routing happens after that of the (rest of the) Linux kernel, not overlapping with, replacing, or conflicting with.

While this understanding does come from a decent amount of experience, in case I'm wildly misrepresenting things, do set it straight.

Only one peer is allowed to use for AllowedIPs

Not at all. See the links in the thread. You can try it yourself to verify if you do not believe me (and again, wg-quick does extra things here which are not inherent to Wireguard). And also again, you can specify as many wireguard interfaces as you want - no need to cram all peers into one interface.

> And also again, you can specify as many wireguard interfaces as you want

I don’t have to do this with a normal data link layer, that’s the entirety of the complaint.

i just wish i could control the routing via routing tables instead, making dynamic routing decisions possible without specialized software that is able to manipulate it.

You can. You just need to use the right tools. See my uncle comment.

just so you know, your assumption i am not using the right tools feels almost insulting to me considering i made no claim about any tooling used. i am using systemd-networkd to setup networking anywhere, i never touch wg-quick because it is no fit for my use cases. i have multiple routing tables and do policy routing and i would really like to have the "via" in the routing tables to have a meaning to wireguards crypto routing thing. i.e. i want to be able to set "AllowedIPs" based upon the routing table very similar to reverse path filtering. i know i can setup multiple interfaces with multiple keys to exchange and multiple ports to set and to make sure every client that needs to is kept in sync.... but it would be much nicer if i could handle it like an ip-ip tunnel and make routing decisions with software build for this purpose.

Wireguard is not a link layer (layer 2) tunnel; it is a network (layer 3) tunnel. It operates at the IP layer. You cannot use Wireguard with any non-IP layer3.

AllowedIPs can be disabled if you want; just set it to AllowedIPs is needed because netfilter can't "see" which public key an inbound packet is coming from, so by the time a packet gets to netfilter it's too late to accept/reject based on which peer sent it to us.

> Wireguard is not a link layer (layer 2)

Yes but UI wise it presents itself as one, since it’s acts as an interface. The fact that it is not a true data link layer is the basis of my comment.

> AllowedIPs can be disabled if you want; just set it to

Only one peer is allowed to use for AllowedIPs

> Only one peer is allowed to use for AllowedIPs

This is simply incorrect. You can have two peers with the same AllowedIP; you just have to put them on separate interfaces (wg0 and wg1 for example). This is for exactly the same reason that a routing table can only have one default entry. If you want two default entries, make two routing tables.

> Yes but UI wise it presents itself as one

No, it doesn't present itself as one.

> since it’s acts as an interface

So does /dev/net/tun, which is definitely not a layer 2 interface either.

> you just have to put them on separate interfaces

I don’t have to do this with normal data link layers. That’s the point of the complaint. Wireguard is not a true data link layer. Manually configuring multiple interfaces for something I can do with just one interface with a normal data link layer at runtime is an extra inconvenience.

> This is for exactly the same reason that a routing table can only have one default entry. If you want two default entries, make two routing tables.

Using nftables I can specify different routers to use based on arbitrarily complex packet rules. Using just one interface. I can’t do this with wireguard, it will only allow me to to route arbitrary packets to a single peer on an interface. This is an inconvenience.

This is less important although useful in some cases. The more important thing is outbound - since Wireguard is layer 3, in the routing table you can't specify a peer to send the packet to like you can with ethernet, and it has to choose one using AllowedIPs.

Why not just split the peers up over separate wg interfaces?

Sounds like you’re either already a Zerotier user or would be happy to discover their product.

tailscale supports something similar. I use it, inside CI runner jobs, to enable network access to things behind firewalls/nat. Really great solution.

"things" ?


I have tailscale running on a lot of devices.

Servers, workstations, raspberry pi, various other appliances, SFPs.

You are running tailscale on an SFP???

Yes that is what they are, But what SFP actually has the ability to be programmable to the degree that is needed to support Wireguard??


This SFP has an embedded ARM processor running Linux. It’s pretty meta, but one could imagine a wireguard control network for these. The article even describes using wireguard-go on the embedded side.

Yeah I'm a bit crazy, as someone else pointed out, it's a https://plumspace.com/ smart SFP.

WireGuard Implementation for ESP-IDF: https://github.com/trombik/esp_wireguard

This is cool! I was looking for something to integrate into an automated downloading setup. I had already setup a docker container that connected to wireguard and then the other containers would connect to the internet through that one, but doing it with socks is a lot easier and more of an out-of-the-box config. Decreasing complexity. Very nice!

This sounds pretty useful for gaining access to resources on your network, from a machine without elevated privs.

Or a new drop bear for yah know…shenanigans.

This sounds neat, I wonder if some people might intentionally open their network to the public via this tool as some kind of experiment. Could be fun.

(The below is meant to be tongue-in-cheek:)

>>> APT for your network... in a box! Market now ripe for someone to use this to deliver APT4UaaS.

Do I understand correctly that this assumes that there's only one wireguard peer?

Yes, and supporting multiple peers isn't really on my todo list. I guess you can host multiple wireproxy instances for each peers.

A Docker solution[1] for the same thing using the official client. Performance can’t be compared with native kernel mode of course, but same technique can be used for other global proxy like OpenVPN.[2]

[1] https://hub.docker.com/r/curve25519xsalsa20poly1305/wireguar...

[2] https://hub.docker.com/r/curve25519xsalsa20poly1305/openvpn/

It would be really cool if you could use something like this in-process to open a "socket" that just happens to route over wireguard or another VPN.

E.g. you could easily have a bittorrent client use a certain VPN without routing all your traffic over it, or you could have a tab container in firefox use one connection, and another container another connection.

In Linux you can also use network namespaces for this. Although you can't have your application run in multiple namespaces simultaneously, I think.

The websocket approach is a lot easier to configure, so I'm definitely going to look in to this.

This is great, thank you!

I used to run OpenVPN in a Docker container together with a SOCKS proxy for this exact use case (using a commercial VPN provider that doesn't offer SOCKS with different endpoints on a per-site/per-tab basis, without wanting to change my default route or non-browser traffic), but this is much more efficient (and safer).

Why wouldn’t you just run ssh -D to the remote machine? The effect is the same and you don’t need wire guard or wire proxy.

I made it because my friends and I use wireguard to have a private network, and they don't feel comfortable running a ssh server on their machines. It can also be used with a vpn provider like Mullvad without setting up a new nic or requiring special privs.

I want to point out a caveat to others: Proxifier != VPN

wireproxy's wg only forwards TCP and UDP. I am not sure how ICMP is handled. Other transports, though rarely used, won't be tunneled (and may leak, if not dropped).

Most importantly missing: DNS, which usually is handled over UDP (changing slowly now with DNS-over-HTTPS and some TCP resolvers). Without handling DNS requests via your proxy, you're still leaking information about yourself to the resolver you're using.

SOCKS5 supports DNS queries. Not sure if this project implements it though.

To clarify, UDP is not currently supported, but I intend to support it in the future. Incoming ICMP should be completely dropped, and outgoing ICMP is not supported.

One simple reason is that the serverside might not want to expose a shell to its clients, and instead just provide network connectivity; you can configure something like that with SSH, but it's a pain, and WireGuard is approximately as simple to set up as SSH, which is the primary reason it's so popular.

And it's crypto is "very good" (so I've heard, not an expert)

And it's fast/low overhead.

And yea, surprisingly easy, "just works"

I lurk their maillist, seems a nice group.

It is very good, it is objectively better in a lot of ways than legacy DNS protocols. But it's popularity is, I think, mostly because of ease-of-use.

Is it a pain? As far as I know, all that's needed is to insert restrict,command="/sbin/nologin",port-forwarding before the user's key in authorized_keys. You can add more security by using a separate user, but individual Unix users for each client are not^W^Wshould not be necessary for security.

Presumably you'd use it on a machine that doesn't expose SSH. That said, I really like sshuttle for this use case when SSH is available.

make it run on windows with a simple gui, subtly ask for donations and you're golden

I don't get it, can someone explain? (we use a lot wireguard but I don't get it :( )


userspace means that the code runs in userspace so, it doesn't require admin privileges. So, it is great if you are not the root. Exposing socks5 is great, so that another userspace process can use it. So, let's say you would like to run a torrent client in a restrictive environment, you can setup this and then through sock5 interface, you might be able to bypass firewall around it.

uh, now I've got it, thank! :)

It is inconvenient to rewrite config from original wireguard config file.

I think i'm going to write a convert a wireguard config into a wireproxy config file. Hopefully that will make it easier for people to use.

Suggest to try with this - https://github.com/go-ini/ini Wireguard config is ini format.

Very useful. Config looks easy to set up. Will explore. Thanks.

Very cool! It's very useful! Thanks for sharing!=]

What about UDP?

Not yet unfortunately. I'll add it in the future, but I'm preoccupied with homework right now.

i have yet to encounter a case where a udp-tunnel has a practical advantage over tcp; on the other hand, corporate/hotel/coffeeshop firewalls that block anything not tcp/443 are super commom.


What about it? WireGuard uses UDP. You don't need privileges to open up a UDP socket.

Does this SOCKS5 tunnel support UDP?

Oh! I don't think so; at least, I don't think go-socks5 does.

SOCKS5 supports UDP, but yeah it does not look like go-socks5 does.

Does Rust support it?

The Rust user-mode WireGuard proxy mentioned upthread does UDP. Netstack does UDP as well; it's just that this particular tool doesn't, yet.

By the way, I wanna if there is any way I can connect wireguard server through a proxy. Thank for answering first.

Is this reinventing OpenVPN?

No. The "userspace" here refers to the TCP/IP stack, not the VPN implementation.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact