-
Notifications
You must be signed in to change notification settings - Fork 53
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
feat: http local proxy implementation #50
Conversation
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.
Thanks a lot for your contribution. It's exciting to see the first circumvention tool by an external contributor using our sdk!
Co-authored-by: Vinicius Fortuna <[email protected]>
Co-authored-by: Vinicius Fortuna <[email protected]>
|
||
prx.address = prx.l.Addr().String() | ||
default: | ||
prx.l, err = net.Listen("tcp", prx.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.
Just pass the input address here. No need for all the logic above. The standard library already handles the different cases.
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.
Thank you for noticing that.
I thought that it might be a problem if the proxy is started on :54321
because it'll not be a localhost address (like localhost:54321
). But it causes problem only in case it's not a localhost address in browser settings, so in fact it's ok.
The only question I have is, maybe we should enforce listening on localhost by default? In another way, a proxy will be available to other devices on the network.
} | ||
}() | ||
|
||
// sleep some time to check if server failed to start |
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.
Remove this wait. It's a race condition, and the caller can implement it if they really want it. Also, most of the errors should happen on the Listen.
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.
Make sense, caller can just check an error after some time, so better remove it.
But can you tell me more about race condition thing? I think I'm missing something. Isn't it ok to send a message to channel from one goroutine and read from another?
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.
Imagine it takes around 100ms for the error to happen. You may or may not return the error, depending on the mood of the machine. You may get different results when running the same code multiple times.
// If address is not configured and the server is not started, it returns an empty string. | ||
GetAddr() string | ||
// StartServer starts the proxy server. If no address is specified, it will listen on a random localhost port | ||
StartServer(addr string) 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 make StartServer a standalone function that returns a Proxy object. (Similar how Listen returns a Listener). That way it will never be in an invalid state.
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.
Sounds nice. But I'm not sure about "never be in an invalid state" part. So, I can see StartServer
(or better StartProxy
) function returning a Proxy
with interface like this:
type Proxy interface {
Addr() string // removing "Get" like in Listener
Stop() error
GetError() error // not sure if it should be "Error()", because it will implement an "error" interface
}
I can't come up with an example when things ca go wrong, but it seems for me that they can, and the proxy can broke for some reason. Will it be what you meant or is there something else I should do about never being in invalid state?
UPD: just in case, does this StartProxy definition looks good to you?
func StartServer(d transport.StreamDialer, addr string) (Proxy, 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.
Yes, something like that.
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 figure it's taking a lot more time for me to review this a bit each day with lots of context switches. I think the code is ok, I'll merge and then send another PR with the refactorings I have in mind so we can move forward more quickly,
} | ||
}() | ||
|
||
// sleep some time to check if server failed to start |
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.
Imagine it takes around 100ms for the error to happen. You may or may not return the error, depending on the mood of the machine. You may get different results when running the same code multiple times.
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 can move to x/examples/local-proxy
"github.com/Jigsaw-Code/outline-sdk/x/local-proxy/proxy/httpproxywrapper" | ||
) | ||
|
||
func makeStreamDialer(transportConfig string) (transport.StreamDialer, 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.
You can now use MakeStreamDialer
in x/examples/internal/config
"github.com/go-httpproxy/httpproxy" | ||
) | ||
|
||
func BasicAuth(usersPasswords map[string]string) func(ctx *httpproxy.Context, authType, user, pass 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.
This is never used. Delete
if err = proxy.StopServer(); err != nil { | ||
log.Println("Failed to stop server: ", err.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.
if err = proxy.StopServer(); err != nil { | |
log.Println("Failed to stop server: ", err.Error()) | |
} | |
if err = listener.Close(); err != nil { | |
log.Println("Failed to stop server: ", err.Error()) | |
} |
// If address is not configured and the server is not started, it returns an empty string. | ||
GetAddr() string | ||
// StartServer starts the proxy server. If no address is specified, it will listen on a random localhost port | ||
StartServer(addr string) 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.
Yes, something like that.
proxy := httpproxy.NewConnectHandler(dialer) | ||
proxy.OnError = httpproxy.OnError | ||
|
||
if err := proxy.StartServer(*addrFlag); err != nil { | ||
log.Fatal("Proxy server failed") | ||
} |
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, I lost the comment. I'd like to see the code like this:
proxy := httpproxy.NewConnectHandler(dialer) | |
proxy.OnError = httpproxy.OnError | |
if err := proxy.StartServer(*addrFlag); err != nil { | |
log.Fatal("Proxy server failed") | |
} | |
listener := net.Listen("tcp", *addrFlag) | |
if err := http.Serve(listener, httpproxy.NewHandler(dialer)); err != nil { | |
log.Fatal("Proxy server failed") | |
} |
This way it's clear what is provided by the code. Also, the caller may want to provide a different listener (e.g. using TLS).
You can stop the server by calling listener.Close()
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.
We can provide convenience functions later. But we need the http.Handler building block.
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 get this merged so we can iterate
Thank you for your work on this and all the time you've put in. Indeed, it's incredibly difficult to review PR with such a difference in time zones. |
Implementation of a local HTTP proxy that can use
transport.StreamDialer
interface.This is considered useful because many applications (e.g., browsers) can use proxy servers out of the box, and local proxy servers could be used to easily wrap a secure tunnel without additional user privileges.
To demo this functionality you may run local HTTP proxy as follows:
$ KEY=ss://ENCRYPTION_KEY@HOST:PORT/ $ go run github.com/Jigsaw-Code/outline-sdk/x/local-proxy@latest -transport "$KEY" -addr localhost:54321
or, to test before merging, being in
outline-sdk/x
folderKEY=ss://ENCRYPTION_KEY@HOST:PORT/ go run ./local-proxy -transport "$KEY" -addr localhost:54321
The main question to discuss is how should proxy interface look like. So far I come up with this:
Will be happy to receive any comments