add socks

This commit is contained in:
ginuerzh
2021-11-05 23:36:50 +08:00
parent ec8615991b
commit e8f040cbdf
29 changed files with 1210 additions and 144 deletions

View File

@ -0,0 +1,160 @@
package v5
import (
"context"
"net"
"github.com/go-gost/gosocks5"
"github.com/go-gost/gost/pkg/handler"
"github.com/go-gost/gost/pkg/logger"
)
func (h *socks5Handler) handleBind(ctx context.Context, conn net.Conn, req *gosocks5.Request) {
addr := req.Addr.String()
h.logger = h.logger.WithFields(map[string]interface{}{
"dst": addr,
"cmd": "bind",
})
h.logger.Infof("%s >> %s", conn.RemoteAddr(), addr)
if h.chain.IsEmpty() {
h.bindLocal(ctx, conn, addr)
return
}
r := (&handler.Router{}).
WithChain(h.chain).
WithRetry(h.md.retryCount).
WithLogger(h.logger)
cc, err := r.Connect(ctx)
if err != nil {
resp := gosocks5.NewReply(gosocks5.Failure, nil)
resp.Write(conn)
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(resp)
}
return
}
defer cc.Close()
if err := req.Write(cc); err != nil {
h.logger.Error(err)
resp := gosocks5.NewReply(gosocks5.NetUnreachable, nil)
resp.Write(conn)
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(resp)
}
return
}
h.logger.Infof("%s <-> %s", conn.RemoteAddr(), addr)
handler.Transport(conn, cc)
h.logger.Infof("%s >-< %s", conn.RemoteAddr(), addr)
}
func (h *socks5Handler) bindLocal(ctx context.Context, conn net.Conn, addr string) {
bindAddr, _ := net.ResolveTCPAddr("tcp", addr)
ln, err := net.ListenTCP("tcp", bindAddr) // strict mode: if the port already in use, it will return error
if err != nil {
h.logger.Error(err)
reply := gosocks5.NewReply(gosocks5.Failure, nil)
if err := reply.Write(conn); err != nil {
h.logger.Error(err)
}
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(reply.String())
}
return
}
socksAddr, err := gosocks5.NewAddr(ln.Addr().String())
if err != nil {
h.logger.Warn(err)
socksAddr = &gosocks5.Addr{
Type: gosocks5.AddrIPv4,
}
}
// Issue: may not reachable when host has multi-interface
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
if err := reply.Write(conn); err != nil {
h.logger.Error(err)
ln.Close()
return
}
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(reply.String())
}
h.logger.Infof("bind on: %s OK", socksAddr.String())
h.serveBind(ctx, conn, ln)
}
func (h *socks5Handler) serveBind(ctx context.Context, conn net.Conn, ln net.Listener) {
var rc net.Conn
accept := func() <-chan error {
errc := make(chan error, 1)
go func() {
defer close(errc)
defer ln.Close()
c, err := ln.Accept()
if err != nil {
errc <- err
}
rc = c
}()
return errc
}
pc1, pc2 := net.Pipe()
pipe := func() <-chan error {
errc := make(chan error, 1)
go func() {
defer close(errc)
defer pc1.Close()
errc <- handler.Transport(conn, pc1)
}()
return errc
}
defer pc2.Close()
select {
case err := <-accept():
if err != nil {
h.logger.Error(err)
return
}
defer rc.Close()
raddr, _ := gosocks5.NewAddr(rc.RemoteAddr().String())
reply := gosocks5.NewReply(gosocks5.Succeeded, raddr)
if err := reply.Write(pc2); err != nil {
h.logger.Error(err)
}
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(reply.String())
}
h.logger.Infof("PEER %s ACCEPTED", raddr.String())
h.logger.Infof("%s <-> %s", conn.RemoteAddr(), raddr.String())
handler.Transport(pc2, rc)
h.logger.Infof("%s >-< %s", conn.RemoteAddr(), raddr.String())
case err := <-pipe():
if err != nil {
h.logger.Error(err)
}
ln.Close()
return
}
}

View File

@ -0,0 +1,57 @@
package v5
import (
"context"
"net"
"github.com/go-gost/gosocks5"
"github.com/go-gost/gost/pkg/handler"
"github.com/go-gost/gost/pkg/logger"
)
func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, addr string) {
h.logger = h.logger.WithFields(map[string]interface{}{
"dst": addr,
"cmd": "connect",
})
h.logger.Infof("%s >> %s", conn.RemoteAddr(), addr)
if h.bypass != nil && h.bypass.Contains(addr) {
resp := gosocks5.NewReply(gosocks5.NotAllowed, nil)
resp.Write(conn)
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(resp)
}
h.logger.Info("bypass: ", addr)
return
}
r := (&handler.Router{}).
WithChain(h.chain).
WithRetry(h.md.retryCount).
WithLogger(h.logger)
cc, err := r.Dial(ctx, "tcp", addr)
if err != nil {
resp := gosocks5.NewReply(gosocks5.NetUnreachable, nil)
resp.Write(conn)
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(resp)
}
return
}
defer cc.Close()
resp := gosocks5.NewReply(gosocks5.Succeeded, nil)
if err := resp.Write(conn); err != nil {
h.logger.Error(err)
return
}
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(resp)
}
h.logger.Infof("%s <-> %s", conn.RemoteAddr(), addr)
handler.Transport(conn, cc)
h.logger.Infof("%s >-< %s", conn.RemoteAddr(), addr)
}

View File

@ -0,0 +1,125 @@
package v5
import (
"context"
"net"
"time"
"github.com/go-gost/gosocks5"
"github.com/go-gost/gost/pkg/bypass"
"github.com/go-gost/gost/pkg/chain"
"github.com/go-gost/gost/pkg/handler"
"github.com/go-gost/gost/pkg/logger"
md "github.com/go-gost/gost/pkg/metadata"
"github.com/go-gost/gost/pkg/registry"
)
const (
// MethodTLS is an extended SOCKS5 method with tls encryption support.
MethodTLS uint8 = 0x80
// MethodTLSAuth is an extended SOCKS5 method with tls encryption and authentication support.
MethodTLSAuth uint8 = 0x82
// MethodMux is an extended SOCKS5 method for stream multiplexing.
MethodMux = 0x88
)
const (
// CmdMuxBind is an extended SOCKS5 request CMD for
// multiplexing transport with the binding server.
CmdMuxBind uint8 = 0xF2
// CmdUDPTun is an extended SOCKS5 request CMD for UDP over TCP.
CmdUDPTun uint8 = 0xF3
)
func init() {
registry.RegisterHandler("socks5", NewHandler)
registry.RegisterHandler("socks", NewHandler)
}
type socks5Handler struct {
selector gosocks5.Selector
chain *chain.Chain
bypass bypass.Bypass
logger logger.Logger
md metadata
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := &handler.Options{}
for _, opt := range opts {
opt(options)
}
return &socks5Handler{
chain: options.Chain,
bypass: options.Bypass,
logger: options.Logger,
}
}
func (h *socks5Handler) Init(md md.Metadata) (err error) {
if err := h.parseMetadata(md); err != nil {
return err
}
h.selector = &serverSelector{
Authenticator: h.md.authenticator,
TLSConfig: h.md.tlsConfig,
logger: h.logger,
noTLS: h.md.noTLS,
}
return
}
func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn) {
defer conn.Close()
start := time.Now()
h.logger = h.logger.WithFields(map[string]interface{}{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
h.logger.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
h.logger.WithFields(map[string]interface{}{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
if h.md.readTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
}
conn = gosocks5.ServerConn(conn, h.selector)
req, err := gosocks5.ReadRequest(conn)
if err != nil {
h.logger.Error(err)
return
}
conn.SetReadDeadline(time.Time{})
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(req)
}
switch req.Cmd {
case gosocks5.CmdConnect:
h.handleConnect(ctx, conn, req.Addr.String())
case gosocks5.CmdBind:
h.handleBind(ctx, conn, req)
case CmdMuxBind:
case gosocks5.CmdUdp:
case CmdUDPTun:
default:
h.logger.Errorf("unknown cmd: %d", req.Cmd)
resp := gosocks5.NewReply(gosocks5.CmdUnsupported, nil)
resp.Write(conn)
if h.logger.IsLevelEnabled(logger.DebugLevel) {
h.logger.Debug(resp)
}
return
}
}

View File

@ -0,0 +1,62 @@
package v5
import (
"crypto/tls"
"strings"
"time"
"github.com/go-gost/gost/pkg/auth"
"github.com/go-gost/gost/pkg/internal/utils"
md "github.com/go-gost/gost/pkg/metadata"
)
const (
certFile = "certFile"
keyFile = "keyFile"
caFile = "caFile"
authsKey = "auths"
readTimeout = "readTimeout"
retryCount = "retry"
noTLS = "notls"
)
type metadata struct {
tlsConfig *tls.Config
authenticator auth.Authenticator
readTimeout time.Duration
retryCount int
noTLS bool
}
func (h *socks5Handler) parseMetadata(md md.Metadata) error {
var err error
h.md.tlsConfig, err = utils.LoadTLSConfig(
md.GetString(certFile),
md.GetString(keyFile),
md.GetString(caFile),
)
if err != nil {
h.logger.Warn("parse tls config: ", err)
}
if v, _ := md.Get(authsKey).([]interface{}); len(v) > 0 {
authenticator := auth.NewLocalAuthenticator(nil)
for _, auth := range v {
if s, _ := auth.(string); s != "" {
ss := strings.SplitN(s, ":", 2)
if len(ss) == 1 {
authenticator.Add(ss[0], "")
} else {
authenticator.Add(ss[0], ss[1])
}
}
}
h.md.authenticator = authenticator
}
h.md.readTimeout = md.GetDuration(readTimeout)
h.md.retryCount = md.GetInt(retryCount)
h.md.noTLS = md.GetBool(noTLS)
return nil
}

View File

@ -0,0 +1,97 @@
package v5
import (
"crypto/tls"
"net"
"github.com/go-gost/gosocks5"
"github.com/go-gost/gost/pkg/auth"
"github.com/go-gost/gost/pkg/logger"
)
type serverSelector struct {
methods []uint8
Authenticator auth.Authenticator
TLSConfig *tls.Config
logger logger.Logger
noTLS bool
}
func (selector *serverSelector) Methods() []uint8 {
return selector.methods
}
func (s *serverSelector) Select(methods ...uint8) (method uint8) {
if s.logger.IsLevelEnabled(logger.DebugLevel) {
s.logger.Debugf("%d %d %v", gosocks5.Ver5, len(methods), methods)
}
method = gosocks5.MethodNoAuth
for _, m := range methods {
if m == MethodTLS && !s.noTLS {
method = m
break
}
}
// when Authenticator is set, auth is mandatory
if s.Authenticator != nil {
if method == gosocks5.MethodNoAuth {
method = gosocks5.MethodUserPass
}
if method == MethodTLS && !s.noTLS {
method = MethodTLSAuth
}
}
return
}
func (s *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) {
if s.logger.IsLevelEnabled(logger.DebugLevel) {
s.logger.Debugf("%d %d", gosocks5.Ver5, method)
}
switch method {
case MethodTLS:
conn = tls.Server(conn, s.TLSConfig)
case gosocks5.MethodUserPass, MethodTLSAuth:
if method == MethodTLSAuth {
conn = tls.Server(conn, s.TLSConfig)
}
req, err := gosocks5.ReadUserPassRequest(conn)
if err != nil {
s.logger.Error(err)
return nil, err
}
if s.logger.IsLevelEnabled(logger.DebugLevel) {
s.logger.Debug(req.String())
}
if s.Authenticator != nil &&
!s.Authenticator.Authenticate(req.Username, req.Password) {
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure)
if err := resp.Write(conn); err != nil {
s.logger.Error(err)
return nil, err
}
if s.logger.IsLevelEnabled(logger.DebugLevel) {
s.logger.Info(resp.String())
}
return nil, gosocks5.ErrAuthFailure
}
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Succeeded)
if err := resp.Write(conn); err != nil {
s.logger.Error(err)
return nil, err
}
if s.logger.IsLevelEnabled(logger.DebugLevel) {
s.logger.Debug(resp.String())
}
case gosocks5.MethodNoAcceptable:
return nil, gosocks5.ErrBadMethod
}
return conn, nil
}