sing/protocol/socks/listener.go
2022-04-11 12:46:23 +08:00

148 lines
3.7 KiB
Go

package socks
import (
"io"
"net"
"net/netip"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/transport/tcp"
)
type Handler interface {
tcp.Handler
UDPConnectionHandler
}
type Listener struct {
tcpListener *tcp.Listener
authenticator auth.Authenticator
handler Handler
}
func NewListener(bind netip.AddrPort, authenticator auth.Authenticator, handler Handler) *Listener {
listener := &Listener{
handler: handler,
authenticator: authenticator,
}
listener.tcpListener = tcp.NewTCPListener(bind, listener)
return listener
}
func (l *Listener) NewConnection(conn net.Conn, metadata M.Metadata) error {
return HandleConnection(conn, l.authenticator, l.handler)
}
func (l *Listener) Start() error {
return l.tcpListener.Start()
}
func (l *Listener) Close() error {
return l.tcpListener.Close()
}
func (l *Listener) HandleError(err error) {
l.handler.HandleError(err)
}
func HandleConnection(conn net.Conn, authenticator auth.Authenticator, handler Handler) error {
authRequest, err := ReadAuthRequest(conn)
if err != nil {
return E.Cause(err, "read socks auth request")
}
var authMethod byte
if authenticator == nil {
authMethod = AuthTypeNotRequired
} else {
authMethod = AuthTypeUsernamePassword
}
if !common.Contains(authRequest.Methods, authMethod) {
err = WriteAuthResponse(conn, &AuthResponse{
Version: authRequest.Version,
Method: AuthTypeNoAcceptedMethods,
})
if err != nil {
return E.Cause(err, "write socks auth response")
}
}
err = WriteAuthResponse(conn, &AuthResponse{
Version: authRequest.Version,
Method: AuthTypeNotRequired,
})
if err != nil {
return E.Cause(err, "write socks auth response")
}
if authMethod == AuthTypeUsernamePassword {
usernamePasswordAuthRequest, err := ReadUsernamePasswordAuthRequest(conn)
if err != nil {
return E.Cause(err, "read user auth request")
}
response := new(UsernamePasswordAuthResponse)
if authenticator.Verify(usernamePasswordAuthRequest.Username, usernamePasswordAuthRequest.Password) {
response.Status = UsernamePasswordStatusSuccess
} else {
response.Status = UsernamePasswordStatusFailure
}
err = WriteUsernamePasswordAuthResponse(conn, response)
if err != nil {
return E.Cause(err, "write user auth response")
}
}
request, err := ReadRequest(conn)
if err != nil {
return E.Cause(err, "read socks request")
}
switch request.Command {
case CommandConnect:
err = WriteResponse(conn, &Response{
Version: request.Version,
ReplyCode: ReplyCodeSuccess,
Bind: M.AddrPortFromNetAddr(conn.LocalAddr()),
})
if err != nil {
return E.Cause(err, "write socks response")
}
return handler.NewConnection(conn, M.Metadata{
Destination: request.Destination,
})
case CommandUDPAssociate:
udpConn, err := net.ListenUDP("udp", nil)
if err != nil {
return err
}
defer udpConn.Close()
err = WriteResponse(conn, &Response{
Version: request.Version,
ReplyCode: ReplyCodeSuccess,
Bind: M.AddrPortFromNetAddr(udpConn.LocalAddr()),
})
if err != nil {
return E.Cause(err, "write socks response")
}
go func() {
err := handler.NewPacketConnection(NewPacketConn(conn, udpConn), M.Metadata{
Source: M.AddrPortFromNetAddr(conn.RemoteAddr()),
Destination: request.Destination,
})
if err != nil {
handler.HandleError(err)
}
conn.Close()
}()
return common.Error(io.Copy(io.Discard, conn))
default:
err = WriteResponse(conn, &Response{
Version: request.Version,
ReplyCode: ReplyCodeUnsupported,
})
if err != nil {
return E.Cause(err, "write response")
}
}
return nil
}