-
Notifications
You must be signed in to change notification settings - Fork 49
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
SOAX relay for remote measurement #286
base: main
Are you sure you want to change the base?
Conversation
|
||
The relay will use the SOAX package ID and password to connect to the SOAX server, appending the country, sessionid, and sessionlength from the user's input. | ||
|
||
### Setup and Running |
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.
This is too complicated. You can just use go run
directly and pass the full package name. See the docs for the other tools.
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.
I simplified the readme accordingly
|
||
The relay will forward these parameters to SOAX, allowing users to benefit from sticky sessions without direct access to SOAX credentials. | ||
|
||
### Security Considerations |
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.
Also, SOCKS5 is plaintext....
x/examples/soax-relay/README.md
Outdated
server: | ||
address: "127.0.0.1" | ||
port: "1080" |
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.
"server" is ambiguous. Perhaps "listen_address"? And we can simplify with a single string
server: | |
address: "127.0.0.1" | |
port: "1080" | |
listen_address: 127.0.0.1:1080 |
x/examples/soax-relay/README.md
Outdated
upstream: | ||
prefix: "package-xxxxx-" | ||
password: "YourSOAXPassword" | ||
address: "proxy.soax.com:5000" |
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.
Let's simplify this and be explicit about the soax info, using the names they use:
upstream: | |
prefix: "package-xxxxx-" | |
password: "YourSOAXPassword" | |
address: "proxy.soax.com:5000" | |
soax: | |
package_id: "XXXXXX" | |
package_key: "YourSOAXPassword" |
I'd omit the server address too, it's easier.
x/examples/soax-relay/README.md
Outdated
foo: "bar" | ||
jane: "password123" | ||
|
||
udp_timeout: 1m |
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.
udp_timeout: 1m | |
# Optional | |
udp_timeout: 1m |
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.
timeout param is removed
x/examples/soax-relay/main.go
Outdated
viper.SetConfigType("yaml") | ||
viper.AddConfigPath(".") | ||
|
||
if err := viper.ReadInConfig(); err != nil { |
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.
No globals please. This hurts readability and maintainability. Can you change the code so it doesn't rely on globals? You can use https://pkg.go.dev/github.com/dvln/viper#New instead.
x/examples/soax-relay/main.go
Outdated
|
||
server := socks5.NewServer( | ||
socks5.WithAuthMethods([]socks5.Authenticator{customAuth}), | ||
socks5.WithLogger(socks5.NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))), |
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.
Not required, but you could use slog with https://pkg.go.dev/log/slog#NewLogLogger
x/examples/soax-relay/main.go
Outdated
|
||
switch network { | ||
case "tcp", "tcp4", "tcp6": | ||
streamEndpoint := transport.StreamDialerEndpoint{Dialer: &transport.TCPDialer{}, Address: soaxAddress} |
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.
Please create the endpoint outside the handler. You only need to create it once, no need to create it for every request
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.
I am going to move the socks5 client creation outside of the handler as well.
x/examples/soax-relay/main.go
Outdated
} | ||
|
||
// Valid checks if the provided prefix and password are valid | ||
func (s *StaticCredentialStore) Valid(prefix, password, userAddr string) bool { |
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.
prefix -> username
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.
Validate is now removed entirely
x/examples/soax-relay/main.go
Outdated
} | ||
|
||
// CredentialStore is an interface to validate prefix-based credentials | ||
type CredentialStore interface { |
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.
Too many layers here. I don't see much value in the CredentialStore interface and the StaticCredentialStore. The CustomCredentialAuthenticator can hold the authentication logic. It's an integral part of the authenticator.
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.
I removed these extra layers and did a simple map lookup inside Authenticator
x/examples/soax-relay/config.yaml
Outdated
foo: bar | ||
exampleuser: examplepass | ||
|
||
udp_timeout: 1m |
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.
UDP timeout is not needed. Remove.
The TCP connection for the UDP Association is how we determine when to close it.
x/examples/soax-relay/main.go
Outdated
} | ||
|
||
// Start a goroutine to close the connection after a timeout | ||
go func() { |
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.
Replace this timeout with a loop that reads from the client TCP connection and closes the target TCP connection and the client UDP connection when the client TCP connection is closed. The "loop" can be an io.Copy directly into io.Discard.
x/examples/soax-relay/main.go
Outdated
// Construct the upstream username by concatenating the base username with the suffix | ||
soaxUsername := soaxpackage + suffix // e.g., "package-189365-country-ir-seesionid-..." | ||
|
||
switch network { |
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.
Handle UDP here as well. There's no need to implement an AssociateHandler, since the library already provides that.
x/examples/soax-relay/main.go
Outdated
return exists && storedPassword == password | ||
} | ||
|
||
func udpAssociateHandler(ctx context.Context, writer io.Writer, request *socks5.Request) error { |
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.
Let's remove this and handle UDP in the Server dialer instead.
I'm not comfortable exposing the internals of our SOCKS5 library for now and that will require some more thought.
Using the dialer approach will keep the code simpler, even though it's doing an extra relay of the packets.
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.
@fortuna this is an important part of the implementation. I am basically implementing a custom udpAssociateHandler
which relays the UDP associate command coming from the client (to proxy server) to the upstream soax server and relays the bind address communicated by soax server back to the client.
I initially thought I can use WithDialAndRequest
to relay UDP as well but this handler only handle outgoing TCP connections (and not even the UDP associate TCP connection). I guess you are suggesting that I use a packetDialer similar to SteamDialer in the WithDialAndRequest
for udp
networks right? If so, unfortunately that approach won't work since WithDialAndRequest
won't be called for udp
.
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.
The library already implements the relay:
https://github.com/things-go/go-socks5/blob/503e7866821a419fe3766751f6d5671390072c14/handle.go#L219
The dial to the target happens here:
https://github.com/things-go/go-socks5/blob/503e7866821a419fe3766751f6d5671390072c14/handle.go#L242
It's directly from Server.dial
:
https://github.com/things-go/go-socks5/blob/503e7866821a419fe3766751f6d5671390072c14/handle.go#L183
Which is set by WithDialAndRequest
. That applies to both tcp and udp.
You will need to take the credentials from the request, create the SOCKS5 dialer, then dial.
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.
Ah no! It doesn't actually use Server.dialWithRequest
!
So, yeah, we need to provide the relay.
transport/socks5/packet_listener.go
Outdated
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.
Revert the transport changes. See below.
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.
@fortuna please refer to my above comment.
x/examples/soax-relay/main.go
Outdated
} | ||
|
||
streamEndpoint := transport.StreamDialerEndpoint{Dialer: &transport.TCPDialer{}, Address: config.SOAX.Address} | ||
client, err := csocks5.NewClient(&streamEndpoint) |
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.
You can't reuse the client, because you need to set the credentials for each dial. You now have a race condition.
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.
@fortuna these two lines were actually inside WithDialAndRequest
(so every dial will override the credentials for that dial). I need to revert back.
x/examples/soax-relay/main.go
Outdated
|
||
// Construct the upstream username by concatenating the base username with the suffix | ||
soaxUsername := config.SOAX.PackageID + suffix // e.g., "package-123456-country-ir-seesionid-..." | ||
err = client.SetCredentials([]byte(soaxUsername), []byte(config.SOAX.PackageKey)) |
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.
This is a race condition, since it will change the credential for everyone using the client
// the soax package name with the suffix | ||
soaxUsername := config.SOAX.PackageID + suffix | ||
|
||
streamEndpoint := transport.StreamDialerEndpoint{Dialer: &transport.TCPDialer{}, Address: config.SOAX.Address} |
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.
Reuse the streamEndpoint with TCP to make it clear it's the same and that it doesn't change on every request.
} | ||
|
||
server := socks5.NewServer( | ||
socks5.WithAuthMethods([]socks5.Authenticator{customAuth}), |
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.
New Approach:
- Let's use WithAssociateMiddleware.
- We have access to the Request there, so we can process the credentials and create the SOCKS5 client, which we can store in the Context
- This is not released yet, so you will need to depend on a specific commit.
- In the Dial function (specified with
WithDial
), retrieve the client from the Context and dial.
@@ -3,7 +3,7 @@ module github.com/Jigsaw-Code/outline-sdk/x | |||
go 1.21 | |||
|
|||
require ( | |||
github.com/Jigsaw-Code/outline-sdk v0.0.17-0.20240726212635-470a9290ec57 | |||
github.com/Jigsaw-Code/outline-sdk v0.0.17-0.20240920003233-01a5556214fd |
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.
Let's change this to version 0.0.17 and re-run go mod tidy
. I believe we can revert the change to the socks5
package.
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.
@fortuna I will get back to exploring and implementing the suggested alternative approach in a bit. That would be a requirement for reverting the changes to socks5
package (which exposes method to send associate command). I am right now focusing on getting a set of remote measurements for the upcoming event and get back on this PR afterwards.
This PR adds an example for running a socks5 proxy that forwards requests to soax server socks5 server. It helps with temporarily sharing access with others to let them perform measurements on a given network.