Feature: support outbound socks5 udp

This commit is contained in:
Dreamacro
2019-04-25 13:48:47 +08:00
parent cec2206774
commit 936ea3aa55
12 changed files with 337 additions and 206 deletions

View File

@ -3,8 +3,8 @@ package adapters
import (
"net"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/socks"
)
// SocketAdapter is a adapter for socks and redir connection
@ -19,7 +19,7 @@ func (s *SocketAdapter) Metadata() *C.Metadata {
}
// NewSocket is SocketAdapter generator
func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter {
func NewSocket(target socks5.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = netType
metadata.Source = source

View File

@ -5,24 +5,24 @@ import (
"net/http"
"strconv"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/socks"
)
func parseSocksAddr(target socks.Addr) *C.Metadata {
func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata := &C.Metadata{
AddrType: int(target[0]),
}
switch target[0] {
case socks.AtypDomainName:
case socks5.AtypDomainName:
metadata.Host = string(target[2 : 2+target[1]])
metadata.Port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks.AtypIPv4:
case socks5.AtypIPv4:
ip := net.IP(target[1 : 1+net.IPv4len])
metadata.IP = &ip
metadata.Port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks.AtypIPv6:
case socks5.AtypIPv6:
ip := net.IP(target[1 : 1+net.IPv6len])
metadata.IP = &ip
metadata.Port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))

View File

@ -1,7 +1,6 @@
package adapters
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
@ -11,11 +10,11 @@ import (
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/socks5"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/socks"
)
type ShadowSocks struct {
@ -177,26 +176,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
}, nil
}
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
aType := uint8(metadata.AddrType)
p, _ := strconv.Atoi(metadata.Port)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType {
case socks.AtypDomainName:
len := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, len}, host, port}
case socks.AtypIPv4:
host := metadata.IP.To4()
buf = [][]byte{{aType}, host, port}
case socks.AtypIPv6:
host := metadata.IP.To16()
buf = [][]byte{{aType}, host, port}
}
return bytes.Join(buf, nil)
}
type ssUDPConn struct {
net.PacketConn
rAddr net.Addr
@ -205,7 +184,7 @@ type ssUDPConn struct {
func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)])
rAddr := socks.ParseAddr(uc.rAddr.String())
rAddr := socks5.ParseAddr(uc.rAddr.String())
copy(buf[len(rAddr):], b)
copy(buf, rAddr)
return uc.PacketConn.WriteTo(buf[:len(rAddr)+len(b)], addr)
@ -213,7 +192,7 @@ func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, a, e := uc.PacketConn.ReadFrom(b)
addr := socks.SplitAddr(b[:n])
addr := socks5.SplitAddr(b[:n])
copy(b, b[len(addr):])
return n - len(addr), a, e
}

View File

@ -1,17 +1,13 @@
package adapters
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"strconv"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/socks"
)
type Socks5 struct {
@ -48,69 +44,44 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) {
return nil, fmt.Errorf("%s connect error", ss.addr)
}
tcpKeepAlive(c)
if err := ss.shakeHand(metadata, c); err != nil {
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
}
if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err
}
return c, nil
}
func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
buf := make([]byte, socks.MaxAddrLen)
var err error
func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
// VER, NMETHODS, METHODS
if len(ss.user) > 0 {
_, err = rw.Write([]byte{5, 1, 2})
} else {
_, err = rw.Write([]byte{5, 1, 0})
if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig)
err = cc.Handshake()
c = cc
}
if err != nil {
return err
return nil, nil, fmt.Errorf("%s connect error", ss.addr)
}
// VER, METHOD
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
return err
}
if buf[0] != 5 {
return errors.New("SOCKS version error")
}
if buf[1] == 2 {
// password protocol version
authMsg := &bytes.Buffer{}
authMsg.WriteByte(1)
authMsg.WriteByte(uint8(len(ss.user)))
authMsg.WriteString(ss.user)
authMsg.WriteByte(uint8(len(ss.pass)))
authMsg.WriteString(ss.pass)
if _, err := rw.Write(authMsg.Bytes()); err != nil {
return err
tcpKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
return err
}
if buf[1] != 0 {
return errors.New("rejected username/password")
}
} else if buf[1] != 0 {
return errors.New("SOCKS need auth")
}
// VER, CMD, RSV, ADDR
if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(metadata)}, []byte(""))); err != nil {
return err
if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user); err != nil {
return nil, nil, err
}
if _, err := io.ReadFull(rw, buf[:10]); err != nil {
return err
}
return nil
return &fakeUDPConn{Conn: c}, c.LocalAddr(), nil
}
func NewSocks5(option Socks5Option) *Socks5 {

View File

@ -1,13 +1,16 @@
package adapters
import (
"bytes"
"crypto/tls"
"fmt"
"net"
"net/url"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
@ -60,3 +63,36 @@ func getClientSessionCache() tls.ClientSessionCache {
})
return globalClientSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
aType := uint8(metadata.AddrType)
p, _ := strconv.Atoi(metadata.Port)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType {
case socks5.AtypDomainName:
len := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, len}, host, port}
case socks5.AtypIPv4:
host := metadata.IP.To4()
buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6:
host := metadata.IP.To16()
buf = [][]byte{{aType}, host, port}
}
return bytes.Join(buf, nil)
}
type fakeUDPConn struct {
net.Conn
}
func (fuc *fakeUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return fuc.Conn.Write(b)
}
func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := fuc.Conn.Read(b)
return n, fuc.RemoteAddr(), err
}