-
Notifications
You must be signed in to change notification settings - Fork 80
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
New explicit and extensible server API #59
Conversation
Start working on a new, safer, more extensible, lower-level-ish API. First step, extract the methods handling the authentication, for now without touching the actual code inside of them. Don't make the methods public yet as this API isn't great yet.
Extend the type-level state machine further. Missing the success reply for now as that requires refactoring the actual proxying functions, which will be done next.
…uccess Now almost the entire process goes through the type-level state machine, with only a break for the DNS resolution left, which will be dealt with next.
Now that almost everything in the middle belongs to Socks5ServerProtocol, reunite what's left in the main Socks5Socket impl into one syntactic block.
This is the new typestate-based auth method API that provides open-ended extensibilty (i.e. a library user can add their own methods, using for example the 80h-FEh private ID range specified by the RFC). It provides a lot of safety, except for the possibility for two methods to declare the same ID. Sadly, functions in traits cannot be const, or I would've written a static assert at least in the enum macro..
Now with run_tcp_proxy and run_udp_proxy it's very clear that upgrade_to_socks5 only translates from old API to new.
Now we can finally see it in all its glory! Simple, yet explicit and therefore extensible. Helpers have been added for auth to make it look nicer.
Not relevant anymore; with the new API, we don't do the Stream thing that wasn't really adding much at all.
Thanks @valpackett I like this new design, I'll try to implement it on my side. @yuguorui I'd like your view on this, wdyt? |
@valpackett After switching my project to this PR and simply running it again, the authentication with password is broken.
while on
Any thought? |
It might be necessary to return some password check result for further usage. Allow doing it in a convenient way.
added a small fix valpackett#1 |
Just a fun demo showing how a router / frontend proxy could be built.
Use the declared AddrError type as the actual return type in this module. anyhow should not be used in library code at all.
Define a thiserror type for errors that occur when TCP connecting to targets. Prepare a ReplyError conversion for these, as the proper error replying was lost in the refactoring before.
New small thiserror based type for UDP header related errors. Not sure why ReplyErrors were used here before: all of this happens way after we've had a chance to reply with either success or error in the TCP SOCKS5 flow.
Liberate the new server API from anyhow; restore the reporting of errors to the client as reply_error.
that reports errors to the client.
I think it's good overall, sorry for not replying quickly due to the Spring Festival. |
Thank you @yuguorui , waiting for your call before merging this |
Yes, I have done the migration in my project and I did not find any issues while migrating to the new API. Both TCPConnect and UDPAssociate are working fine. https://github.com/yuguorui/rfor/commits/typestate/ |
Thanks for your feedback @yuguorui much appreciated, and thanks @valpackett for your work. I'll merge this and release a new version. |
(Fixes #15 #19 #31 & more)
This is my take on the server API redesign. Rather than introducing new "executor" traits to customize the behavior through more inversion-of-control, we go in the opposite direction and expose the
match
over the commands directly to the caller, providing easy obvious one-call default handlers:When the user code looks like this:
target_addr
before DNS resolutionTCPBind
And because this API is based on typestates, when you go to swap out
run_tcp_proxy
for something custom (or try to implementTCPBind
), you won't forget to reply to the client – the type ofproto
at that point only hasreply_success
andreply_error
as methods, and you need to callreply_success
to get access to the raw socket and begin proxying.Now, while here… authentication also needed to be rewritten. The
Authentication
trait from the previous API was… somewhat strange IMO, forcing the use ofArc
/dyn
while representing sort of "the checking business logic as a Thing" but not a method in terms of the SOCKS5 protocol.The new API makes it easy to support only password auth:
Or no auth:
Or skip the whole auth handshake (now with a clear reminder that it's not real compliant SOCKS5 anymore):
But below those helpers, if we unwrap that one layer, we encounter infinite extensibility, with custom methods that actually can be negotiated via custom method ID numbers.
If we inline
accept_password_auth
…We get to see the entire flow of the protocol. (This is still all typestate-based: after we've negotiated with only
PasswordAuthentication
there's nowhere to go but toread_username_password()
, and then we mustaccept()
orreject()
the password.)Now,
&[PasswordAuthentication]
looks kinda silly, doesn't it? But that's because we only have one method. If we'd like to use method negotiation, that's where that slice comes in: we make our method type an enum containing various methods, and we get generic method negotiation with fully static dispatch, without anydyn
overhead anywhere ever.This is how we can use that to permit allowlisted source IP addresses to use no-authentication:
StandardAuthentication
there is an enum defined by the library that includes those two methods, but it's possible to define custom enums that include fully custom methods! An example is included.Little tiny TODOs left:
SocksError