Skip to content
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

(socks) proxy support #286

Open
Stebalien opened this issue Feb 27, 2018 · 28 comments · May be fixed by #3137
Open

(socks) proxy support #286

Stebalien opened this issue Feb 27, 2018 · 28 comments · May be fixed by #3137
Labels
exp/wizard Extensive knowledge (implications, ramifications) required kind/enhancement A net-new feature or improvement to an existing feature needs/triage Needs initial labeling and prioritization

Comments

@Stebalien
Copy link
Member

In some environments, users need to be able to proxy all traffic through, e.g., a corporate socks proxy. This sucks but is sometimes inevitable.

Solution:

  • Tunnel outbound connections through the socks proxy.
  • If the proxy supports bind use it.
  • Otherwise, connect to and advertise a relay address.

Problems:

  • Proxy aware transports are going to be a royal pain.
@Stebalien Stebalien added kind/enhancement A net-new feature or improvement to an existing feature exp/wizard Extensive knowledge (implications, ramifications) required labels Feb 27, 2018
@Stebalien
Copy link
Member Author

So, @rklaehn pointed out a simple solution for dialing: use the websocket transport. It should work just fine over any HTTP(s) proxy. All we need to do is expose this somehow.

Unfortunately, listening could be tricky.

@rklaehn
Copy link

rklaehn commented Jun 27, 2018

So it should work, but in my experiments I could not actually get it to work. Just to clarify: websocket is a complete transport that will work just as well as the native tcp or udp transports? So in theory you could have a swarm just communicating over ws? Or is it in some way limited?

@Stebalien
Copy link
Member Author

Yes, it's a full transport. I assumed it wouldn't work because we don't explicitly tell it to use the proxy from the environment. However, it looks like it does this by default.

So, this is a bit odd. It should just work. I'm wondering if it's a port issue. Does your proxy not support forwarding to random ports?

@cretz
Copy link

cretz commented Jul 7, 2018

I too need a transport proxy interface. In many cases in Go, proxies are implemented by accepting a net.Dialer and/or a net.Listener. There needs to be some way to downgrade a transport to these items or the logic that does things with the connections needs to be decoupled from the logic wrapping in manet dialier/listener and transports. My use case is for web sockets over Tor (which happens to be a socks proxy for dialing, but there is also a part I want to proxy for listening). However, there is enough unexposed logic in the websocket that I have to take the entire websocket transport dial/listen or none of it. I would prefer just the parts that operate on the net connections, or even better, let it take an externally provided tcp dialer/listener.

EDIT: as mentioned in libp2p/go-ws-transport#33 I instead am just proxying raw net.Dialer/net.Listener stuff instead of proxying an entire transport. With all the things that transports add, I am thinking that proxying at the transport level is less practical than at the raw level and then wrapping.

@ConorTighe
Copy link

ConorTighe commented Jul 17, 2018

Did anyone find a solution that worked for this problem?

{"error":"failed to bootstrap. context deadline exceeded","event":"bootstrapError","peerID":"QmXL3Pe6USjNf5Vks2XBi7ryngCe8NguazLyCjeeeCVVe3","system":"core","time":"2018-07-17T09:32:28.447604435Z"}

@Stebalien
Copy link
Member Author

So, I've tested this and dialing does actually work. You just need to correctly configure your environment to use a proxy: https://wiki.archlinux.org/index.php/Proxy_settings#Environment_variables

For example, if you're using SSH, you can run http_proxy=socks5://localhost:PROXY_PORT ipfs daemon and all outbound websocket connections should just use this proxy.

Note: this still doesn't help with inbound connections.

@ConorTighe
Copy link

I have my environment variables set in etc/environment and exported http_proxy in ~/.bashrc, Ive tried passing http_proxy before the daemon and before the computes daemon. Im only connecting 2 Ubuntu machines through a bridged network and have them communicate in a swarm. Im still getting the following task status:

{"meta":{"computes":{"queue":{"assigned":[{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T08:37:09+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-16T11:01:20+01:00"}],"available":[{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-16T11:43:58+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T11:08:53+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T11:17:32+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T08:25:58+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-18T10:37:48+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-18T10:19:05+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-18T10:24:42+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T10:57:44+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-16T11:01:19+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T08:37:09+01:00"}],"completed":[{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-16T11:01:22+01:00"},{"hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T08:37:12+01:00"}]},"runner":{"errors":[{"error":"UpdateResultsBytes failed: Unable to update interface: DAG.PutInterface failed: Put failed: unexpected status code returned. Expected 200, received 500","hostname":"ctighe-VirtualBox","timestamp":"2018-07-17T08:37:12+01:00"},{"error":"UpdateResultsBytes failed: Unable to update interface: DAG.PutInterface failed: Put failed: unexpected status code returned. Expected 200, received 500","hostname":"ctighe-VirtualBox","timestamp":"2018-07-16T11:01:22+01:00"}]},"seed":"word-status","tasks":{"related":["zdpuAz6fL6221SDcEdVPbr8M3KYKsL1bHjQcxX8WB8shZ6yBa","zdpuAv1doragWYqB2EKC3cL33zEiqFi3J9M1oQJBtAW7vJJHK"]}}}}

@Stebalien
Copy link
Member Author

Ah. Sorry, I forgot. While we can dial websocket addresses, go-ipfs doesn't listen on them by default. That's probably the problem here.

Note: we haven't enabled listening by default as our primary usecase for the websocket transport is allowing browsers to dial go-ipfs nodes. Unfortunately, browsers often need to dial websocket over https and IPFS nodes can't get valid certificates.

I've opened an issue for enabling it by default: ipfs/kubo#5251.

In the mean-time, try running ipfs swarm connect /dns4/ams-1.bootstrap.libp2p.io/tcp/80/ws/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd. That'll connect to to one of the bootstrappers we usually reserve for browser nodes.

@ConorTighe
Copy link

ConorTighe commented Jul 19, 2018

ipfs swarm connect /dns4/ams-1.bootstrap.libp2p.io/tcp/80/ws/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd

 Error: connect QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd failure: dial attempt failed: <peer.ID XL3Pe6> --> <peer.ID SoLer2> dial attempt failed: context deadline exceeded
 

@Stebalien
Copy link
Member Author

Damn. It looks like the bootstrappers only listen on wss (websocket over https) and go can't dial that. Unfortunately, the PR to implement that is stale.

@ConorTighe
Copy link

Any idea what the scope might be to update this?

@nickname76
Copy link

Are there any updates?

@ynkumar143
Copy link

Team, are there any updates on this?

@lidel lidel added the needs/triage Needs initial labeling and prioritization label Sep 21, 2020
@clarkmcc
Copy link

Any updates on this?

@MarcoPolo
Copy link
Collaborator

Dialing wss is now supported (libp2p/go-ws-transport#115). So this should work if you use the websocket transport

@pyhedgehog
Copy link

Are there bootstrap websocket nodes?

@aschmahmann
Copy link
Collaborator

Are there bootstrap websocket nodes?

How are you hoping to use this? If you're trying to leverage the IPFS Public DHT while only having websockets available as a transport you'll be out of luck since most of the DHT server nodes aren't listening on websockets and so your queries won't really work even if you can talk to the bootstrappers.

@parkan
Copy link

parkan commented Jan 13, 2025

it doesn't seem like HTTP_PROXY is respected:

HTTP_PROXY="socks://example:888" ipfs swarm connect /ip4/104.202.252.41/tcp/24002/ws/p2p/12D3KooWQnScgybqBcTv6f3yvgbF8pH7sLnrkrbYVB3FRyZY2NsR

this should fail as there is no proxy at example:888 but it merrily connects

@parkan
Copy link

parkan commented Jan 13, 2025

ok just in case it silently skips the proxy if it doesn't work I tried with a real proxy, no dice:

socat -v TCP-LISTEN:1080,fork SOCKS4A:localhost:0.0.0.0:0,socksport=1080
HTTP_PROXY="socks://localhost:1080" ipfs swarm connect /ip4/104.202.252.41/tcp/24002/ws/p2p/12D3KooWQnScgybqBcTv6f3yvgbF8pH7sLnrkrbYVB3FRyZY2NsR
connect 12D3KooWQnScgybqBcTv6f3yvgbF8pH7sLnrkrbYVB3FRyZY2NsR success

@aschmahmann any clues? we are absolutely dying not being able to dial out, just need to connect to a known boostd node

@marten-seemann
Copy link
Contributor

go-libp2p doesn't respect HTTP_PROXY, as a basic code search confirms: https://github.com/search?q=repo%3Alibp2p%2Fgo-libp2p%20HTTP_PROXY&type=code

@parkan
Copy link

parkan commented Jan 13, 2025

@marten-seemann am I not reading @Stebalien's comment here correctly as implying that it should work?

my initial guess was that this did not happen explicitly in go-libp2p but rather by default in some dependency, as no commit was referenced in that comment

@marten-seemann
Copy link
Contributor

Another basic code search confirms that the websocket library also doesn't respect HTTP_PROXY: https://github.com/search?q=repo%3Agorilla%2Fwebsocket%20HTTP_PROXY&type=code

@parkan
Copy link

parkan commented Jan 13, 2025

gorilla/websocket does in fact support using a proxy: https://pkg.go.dev/github.com/gorilla/websocket#Dialer.Proxy

and it seems like the var does get read in as examples of usage show: gorilla/websocket#508

@parkan
Copy link

parkan commented Jan 13, 2025

@marten-seemann ok so this is a feature of http.Transport:

https://pkg.go.dev/golang.org/x/net/http/httpproxy

which is used by gorilla/websocket as far as I can tell, so this not being observed here is extremely strange to me

@parkan
Copy link

parkan commented Jan 13, 2025

ok so this is kind of suspect:

// We prefer using raw TCP over using WebSocket.

seems like the websocket transport gets silently discarded, despite being explicitly requested, therefore proxy is not used

this happens inside a call to filterKnownUndialables, which does not seem semantically correct as "less preferred" can very much be dialable

@marten-seemann looks like you signed off on the last PR that touched this code here

EDIT: ok no this is wrong, dumping the traffic shows that it is indeed speaking websockets -- when provided only a ws address in a swarm connect command it does seem to be used (this may still be an issue for us with nodes that advertise both raw tcp and ws addresses but I'd like to sort out the proxy behavior first)

@marten-seemann
Copy link
Contributor

gorilla/websocket does in fact support using a proxy: https://pkg.go.dev/github.com/gorilla/websocket#Dialer.Proxy

Looks like you're right, if the DefaultDialer is used: https://github.com/gorilla/websocket/blob/v1.5.3/client.go#L142. Looks like go-libp2p doesn't do that though:

dialer := ws.Dialer{HandshakeTimeout: 30 * time.Second}

Not entirely sure if this means that the HTTP_PROXY will not be used, the way default values are set here is not the most obvious. Should be pretty straightforward to test out though.

seems like the websocket transport gets silently discarded, despite being explicitly requested, therefore proxy is not used

There's no such thing as "explicitly requesting" a transport if you look at the swarm API, but you're right, libp2p chooses the best transport when dialing. And that means preferring plain TCP over WebSocket, if available.

go-libp2p was never designed to work with HTTP_PROXY, and it's probably a lot of work to make it actually work. Also, you're limiting yourself to a single (not very widely supported) transport, so the value of doing that work is limited as well. There are better ways to connect a node behind a firewall / NAT to the rest of the network anyway.

@parkan
Copy link

parkan commented Jan 13, 2025

thank you, I have just arrived at the same conclusion (see edit above)

I would absolutely prefer not to use ws addresses here (since it requires boost node operators to configure them explicitly and possibly exclusively) but no better solution has come to mind as we can't open arbitrary tcp streams to the outside -- I am very open to ideas

all we need to do is dial SPs to push deal proposals to their boost

@aschmahmann
Copy link
Collaborator

Not entirely sure if this means that the HTTP_PROXY will not be used, the way default values are set here is not the most obvious. Should be pretty straightforward to test out though.

It won't be used. Looks like this behavior was broken in libp2p/go-ws-transport@0ea4a8a. Seems like easy enough functionality to restore. I've put up a small PR (#3136) that just re-adds the environment variable checking. If anyone feels the need for something more generic (or to not support the environment variables by default) we can also pass the proxy function as an option although the proxies supported will have to be left up to what gorilla/websocket supports.

go-libp2p was never designed to work with HTTP_PROXY, and it's probably a lot of work to make it actually work.

It certainly seems fair to say that enabling proxies across all supported transports and other network components might be a pain. However, IMO it seems reasonable to enable it for people who need it and let them pass along feature requests / needs if we need to expand support. Starting with proxy support for WebSockets doesn't seem unreasonable, am I missing something?

@parkan can you check if the linked PR is enough for your use case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
exp/wizard Extensive knowledge (implications, ramifications) required kind/enhancement A net-new feature or improvement to an existing feature needs/triage Needs initial labeling and prioritization
Projects
None yet
Development

Successfully merging a pull request may close this issue.