426 lines
9.2 KiB
Go
426 lines
9.2 KiB
Go
package gost
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"github.com/ginuerzh/gosocks4"
|
|
"github.com/ginuerzh/gosocks5"
|
|
"github.com/go-log/log"
|
|
)
|
|
|
|
const (
|
|
MethodTLS uint8 = 0x80 // extended method for tls
|
|
MethodTLSAuth uint8 = 0x82 // extended method for tls+auth
|
|
)
|
|
|
|
const (
|
|
CmdUdpTun uint8 = 0xF3 // extended method for udp over tcp
|
|
)
|
|
|
|
type clientSelector struct {
|
|
methods []uint8
|
|
User *url.Userinfo
|
|
TLSConfig *tls.Config
|
|
}
|
|
|
|
func (selector *clientSelector) Methods() []uint8 {
|
|
return selector.methods
|
|
}
|
|
|
|
func (selector *clientSelector) AddMethod(methods ...uint8) {
|
|
selector.methods = append(selector.methods, methods...)
|
|
}
|
|
|
|
func (selector *clientSelector) Select(methods ...uint8) (method uint8) {
|
|
return
|
|
}
|
|
|
|
func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) {
|
|
switch method {
|
|
case MethodTLS:
|
|
conn = tls.Client(conn, selector.TLSConfig)
|
|
|
|
case gosocks5.MethodUserPass, MethodTLSAuth:
|
|
if method == MethodTLSAuth {
|
|
conn = tls.Client(conn, selector.TLSConfig)
|
|
}
|
|
|
|
var username, password string
|
|
if selector.User != nil {
|
|
username = selector.User.Username()
|
|
password, _ = selector.User.Password()
|
|
}
|
|
|
|
req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password)
|
|
if err := req.Write(conn); err != nil {
|
|
log.Log("[socks5]", err)
|
|
return nil, err
|
|
}
|
|
if Debug {
|
|
log.Log("[socks5]", req)
|
|
}
|
|
resp, err := gosocks5.ReadUserPassResponse(conn)
|
|
if err != nil {
|
|
log.Log("[socks5]", err)
|
|
return nil, err
|
|
}
|
|
if Debug {
|
|
log.Log("[socks5]", resp)
|
|
}
|
|
if resp.Status != gosocks5.Succeeded {
|
|
return nil, gosocks5.ErrAuthFailure
|
|
}
|
|
case gosocks5.MethodNoAcceptable:
|
|
return nil, gosocks5.ErrBadMethod
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
type serverSelector struct {
|
|
methods []uint8
|
|
Users []*url.Userinfo
|
|
TLSConfig *tls.Config
|
|
}
|
|
|
|
func (selector *serverSelector) Methods() []uint8 {
|
|
return selector.methods
|
|
}
|
|
|
|
func (selector *serverSelector) AddMethod(methods ...uint8) {
|
|
selector.methods = append(selector.methods, methods...)
|
|
}
|
|
|
|
func (selector *serverSelector) Select(methods ...uint8) (method uint8) {
|
|
if Debug {
|
|
log.Logf("[socks5] %d %d %v", gosocks5.Ver5, len(methods), methods)
|
|
}
|
|
method = gosocks5.MethodNoAuth
|
|
for _, m := range methods {
|
|
if m == MethodTLS {
|
|
method = m
|
|
break
|
|
}
|
|
}
|
|
|
|
// when user/pass is set, auth is mandatory
|
|
if len(selector.Users) > 0 {
|
|
if method == gosocks5.MethodNoAuth {
|
|
method = gosocks5.MethodUserPass
|
|
}
|
|
if method == MethodTLS {
|
|
method = MethodTLSAuth
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) {
|
|
if Debug {
|
|
log.Logf("[socks5] %d %d", gosocks5.Ver5, method)
|
|
}
|
|
switch method {
|
|
case MethodTLS:
|
|
conn = tls.Server(conn, selector.TLSConfig)
|
|
|
|
case gosocks5.MethodUserPass, MethodTLSAuth:
|
|
if method == MethodTLSAuth {
|
|
conn = tls.Server(conn, selector.TLSConfig)
|
|
}
|
|
|
|
req, err := gosocks5.ReadUserPassRequest(conn)
|
|
if err != nil {
|
|
log.Log("[socks5]", err)
|
|
return nil, err
|
|
}
|
|
if Debug {
|
|
log.Log("[socks5]", req.String())
|
|
}
|
|
valid := false
|
|
for _, user := range selector.Users {
|
|
username := user.Username()
|
|
password, _ := user.Password()
|
|
if (req.Username == username && req.Password == password) ||
|
|
(req.Username == username && password == "") ||
|
|
(username == "" && req.Password == password) {
|
|
valid = true
|
|
break
|
|
}
|
|
}
|
|
if len(selector.Users) > 0 && !valid {
|
|
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure)
|
|
if err := resp.Write(conn); err != nil {
|
|
log.Log("[socks5]", err)
|
|
return nil, err
|
|
}
|
|
if Debug {
|
|
log.Log("[socks5]", resp)
|
|
}
|
|
log.Log("[socks5] proxy authentication required")
|
|
return nil, gosocks5.ErrAuthFailure
|
|
}
|
|
|
|
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Succeeded)
|
|
if err := resp.Write(conn); err != nil {
|
|
log.Log("[socks5]", err)
|
|
return nil, err
|
|
}
|
|
if Debug {
|
|
log.Log("[socks5]", resp)
|
|
}
|
|
case gosocks5.MethodNoAcceptable:
|
|
return nil, gosocks5.ErrBadMethod
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
type socks5Connector struct {
|
|
User *url.Userinfo
|
|
}
|
|
|
|
func SOCKS5Connector(user *url.Userinfo) Connector {
|
|
return &socks5Connector{User: user}
|
|
}
|
|
|
|
func (c *socks5Connector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
|
selector := &clientSelector{
|
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
|
User: c.User,
|
|
}
|
|
selector.AddMethod(
|
|
gosocks5.MethodNoAuth,
|
|
gosocks5.MethodUserPass,
|
|
MethodTLS,
|
|
)
|
|
|
|
cc := gosocks5.ClientConn(conn, selector)
|
|
if err := cc.Handleshake(); err != nil {
|
|
return nil, err
|
|
}
|
|
conn = cc
|
|
|
|
host, port, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p, _ := strconv.Atoi(port)
|
|
req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{
|
|
Type: gosocks5.AddrDomain,
|
|
Host: host,
|
|
Port: uint16(p),
|
|
})
|
|
if err := req.Write(conn); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if Debug {
|
|
log.Log("[socks5]", req)
|
|
}
|
|
|
|
reply, err := gosocks5.ReadReply(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if Debug {
|
|
log.Log("[socks5]", reply)
|
|
}
|
|
|
|
if reply.Rep != gosocks5.Succeeded {
|
|
return nil, errors.New("Service unavailable")
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
type socks4Connector struct{}
|
|
|
|
func SOCKS4Connector() Connector {
|
|
return &socks4Connector{}
|
|
}
|
|
|
|
func (c *socks4Connector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
|
taddr, err := net.ResolveTCPAddr("tcp4", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := gosocks4.NewRequest(gosocks4.CmdConnect,
|
|
&gosocks4.Addr{
|
|
Type: gosocks4.AddrIPv4,
|
|
Host: taddr.IP.String(),
|
|
Port: uint16(taddr.Port),
|
|
}, nil,
|
|
)
|
|
if err := req.Write(conn); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if Debug {
|
|
log.Logf("[socks4] %s", req)
|
|
}
|
|
|
|
reply, err := gosocks4.ReadReply(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if Debug {
|
|
log.Logf("[socks4] %s", reply)
|
|
}
|
|
|
|
if reply.Code != gosocks4.Granted {
|
|
return nil, fmt.Errorf("[socks4] %d", reply.Code)
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
type socks4aConnector struct{}
|
|
|
|
func SOCKS4AConnector() Connector {
|
|
return &socks4aConnector{}
|
|
}
|
|
|
|
func (c *socks4aConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
|
host, port, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p, _ := strconv.Atoi(port)
|
|
|
|
req := gosocks4.NewRequest(gosocks4.CmdConnect,
|
|
&gosocks4.Addr{Type: gosocks4.AddrDomain, Host: host, Port: uint16(p)}, nil)
|
|
if err := req.Write(conn); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if Debug {
|
|
log.Logf("[socks4] %s", req)
|
|
}
|
|
|
|
reply, err := gosocks4.ReadReply(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if Debug {
|
|
log.Logf("[socks4] %s", reply)
|
|
}
|
|
|
|
if reply.Code != gosocks4.Granted {
|
|
return nil, fmt.Errorf("[socks4] %d", reply.Code)
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
type socks5Handler struct {
|
|
selector *serverSelector
|
|
options *HandlerOptions
|
|
}
|
|
|
|
func SOCKS5Handler(opts ...HandlerOption) Handler {
|
|
options := &HandlerOptions{
|
|
Chain: new(Chain),
|
|
}
|
|
for _, opt := range opts {
|
|
opt(options)
|
|
}
|
|
|
|
selector := &serverSelector{ // socks5 server selector
|
|
Users: options.Users,
|
|
TLSConfig: options.TLSConfig,
|
|
}
|
|
// methods that socks5 server supported
|
|
selector.AddMethod(
|
|
gosocks5.MethodNoAuth,
|
|
gosocks5.MethodUserPass,
|
|
MethodTLS,
|
|
MethodTLSAuth,
|
|
)
|
|
return &socks5Handler{
|
|
options: options,
|
|
selector: selector,
|
|
}
|
|
}
|
|
|
|
func (h *socks5Handler) Handle(conn net.Conn) {
|
|
conn = gosocks5.ServerConn(conn, h.selector)
|
|
req, err := gosocks5.ReadRequest(conn)
|
|
if err != nil {
|
|
log.Log("[socks5]", err)
|
|
return
|
|
}
|
|
|
|
if Debug {
|
|
log.Logf("[socks5] %s -> %s\n%s", conn.RemoteAddr(), req.Addr, req)
|
|
}
|
|
switch req.Cmd {
|
|
case gosocks5.CmdConnect:
|
|
log.Logf("[socks5-connect] %s -> %s", conn.RemoteAddr(), req.Addr)
|
|
h.handleConnect(conn, req)
|
|
|
|
case gosocks5.CmdBind:
|
|
log.Logf("[socks5-bind] %s - %s", conn.RemoteAddr(), req.Addr)
|
|
h.handleBind(conn, req)
|
|
|
|
case gosocks5.CmdUdp:
|
|
log.Logf("[socks5-udp] %s - %s", conn.RemoteAddr(), req.Addr)
|
|
//s.handleUDPRelay(req)
|
|
|
|
case CmdUdpTun:
|
|
log.Logf("[socks5-rudp] %s - %s", conn.RemoteAddr(), req.Addr)
|
|
//s.handleUDPTunnel(req)
|
|
|
|
default:
|
|
log.Log("[socks5] Unrecognized request:", req.Cmd)
|
|
}
|
|
}
|
|
|
|
func (h *socks5Handler) handleConnect(conn net.Conn, req *gosocks5.Request) {
|
|
addr := req.Addr.String()
|
|
|
|
//! if !s.Base.Node.Can("tcp", addr) {
|
|
//! glog.Errorf("Unauthorized to tcp connect to %s", addr)
|
|
//! rep := gosocks5.NewReply(gosocks5.NotAllowed, nil)
|
|
//! rep.Write(s.conn)
|
|
//! return
|
|
//! }
|
|
|
|
cc, err := h.options.Chain.Dial(addr)
|
|
if err != nil {
|
|
log.Logf("[socks5-connect] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
|
|
rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil)
|
|
rep.Write(conn)
|
|
if Debug {
|
|
log.Logf("[socks5-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep)
|
|
}
|
|
return
|
|
}
|
|
defer cc.Close()
|
|
|
|
rep := gosocks5.NewReply(gosocks5.Succeeded, nil)
|
|
if err := rep.Write(conn); err != nil {
|
|
log.Logf("[socks5-connect] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err)
|
|
return
|
|
}
|
|
if Debug {
|
|
log.Logf("[socks5-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep)
|
|
}
|
|
log.Logf("[socks5-connect] %s <-> %s", conn.RemoteAddr(), req.Addr)
|
|
transport(conn, cc)
|
|
log.Logf("[socks5-connect] %s >-< %s", conn.RemoteAddr(), req.Addr)
|
|
}
|
|
|
|
func (h *socks5Handler) handleBind(conn net.Conn, req *gosocks5.Request) {
|
|
// TODO: socks5 bind
|
|
}
|