This commit is contained in:
ginuerzh 2020-02-24 14:53:51 +08:00
parent c7568170ac
commit a99ff4aa15
10 changed files with 382 additions and 24 deletions

View File

@ -197,6 +197,7 @@ type ConnectOptions struct {
Selector gosocks5.Selector Selector gosocks5.Selector
UserAgent string UserAgent string
NoTLS bool NoTLS bool
NoDelay bool
} }
// ConnectOption allows a common way to set ConnectOptions. // ConnectOption allows a common way to set ConnectOptions.
@ -243,3 +244,10 @@ func NoTLSConnectOption(b bool) ConnectOption {
opts.NoTLS = b opts.NoTLS = b
} }
} }
// NoDelayConnectOption specifies the NoDelay option for ss.Connect.
func NoDelayConnectOption(b bool) ConnectOption {
return func(opts *ConnectOptions) {
opts.NoDelay = b
}
}

View File

@ -137,8 +137,6 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
timeout := node.GetDuration("timeout") timeout := node.GetDuration("timeout")
var host string
var tr gost.Transporter var tr gost.Transporter
switch node.Transport { switch node.Transport {
case "tls": case "tls":
@ -195,8 +193,9 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
case "obfs4": case "obfs4":
tr = gost.Obfs4Transporter() tr = gost.Obfs4Transporter()
case "ohttp": case "ohttp":
host = node.Get("host")
tr = gost.ObfsHTTPTransporter() tr = gost.ObfsHTTPTransporter()
case "otls":
tr = gost.ObfsTLSTransporter()
case "ftcp": case "ftcp":
tr = gost.FakeTCPTransporter() tr = gost.FakeTCPTransporter()
case "udp": case "udp":
@ -240,8 +239,10 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
node.ConnectOptions = []gost.ConnectOption{ node.ConnectOptions = []gost.ConnectOption{
gost.UserAgentConnectOption(node.Get("agent")), gost.UserAgentConnectOption(node.Get("agent")),
gost.NoTLSConnectOption(node.GetBool("notls")), gost.NoTLSConnectOption(node.GetBool("notls")),
gost.NoDelayConnectOption(node.GetBool("nodelay")),
} }
host := node.Get("host")
if host == "" { if host == "" {
host = node.Host host = node.Host
} }
@ -441,6 +442,8 @@ func (r *route) GenRouters() ([]router, error) {
ln, err = gost.Obfs4Listener(node.Addr) ln, err = gost.Obfs4Listener(node.Addr)
case "ohttp": case "ohttp":
ln, err = gost.ObfsHTTPListener(node.Addr) ln, err = gost.ObfsHTTPListener(node.Addr)
case "otls":
ln, err = gost.ObfsTLSListener(node.Addr)
case "tun": case "tun":
cfg := gost.TunConfig{ cfg := gost.TunConfig{
Name: node.Get("name"), Name: node.Get("name"),

2
go.mod
View File

@ -14,7 +14,7 @@ require (
github.com/docker/libcontainer v2.2.1+incompatible github.com/docker/libcontainer v2.2.1+incompatible
github.com/ginuerzh/gosocks4 v0.0.1 github.com/ginuerzh/gosocks4 v0.0.1
github.com/ginuerzh/gosocks5 v0.2.0 github.com/ginuerzh/gosocks5 v0.2.0
github.com/ginuerzh/tls-dissector v0.0.1 github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796
github.com/go-log/log v0.1.0 github.com/go-log/log v0.1.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/golang/mock v1.2.0 // indirect github.com/golang/mock v1.2.0 // indirect

10
go.sum
View File

@ -30,6 +30,16 @@ github.com/ginuerzh/gosocks5 v0.2.0 h1:K0Ua23U9LU3BZrf3XpGDcs0mP8DiEpa6PJE4TA/MU
github.com/ginuerzh/gosocks5 v0.2.0/go.mod h1:qp22mr6tH/prEoaN0pFukq76LlScIE+F2rP2ZP5ZHno= github.com/ginuerzh/gosocks5 v0.2.0/go.mod h1:qp22mr6tH/prEoaN0pFukq76LlScIE+F2rP2ZP5ZHno=
github.com/ginuerzh/tls-dissector v0.0.1 h1:yF6fIt78TO4CdjiLLn6R8r0XajQJE1Lbnuq6rP8mGW8= github.com/ginuerzh/tls-dissector v0.0.1 h1:yF6fIt78TO4CdjiLLn6R8r0XajQJE1Lbnuq6rP8mGW8=
github.com/ginuerzh/tls-dissector v0.0.1/go.mod h1:u/kbBOqIOgJv39gywuUb3VwyzdZG5DKquOqfToKE6lk= github.com/ginuerzh/tls-dissector v0.0.1/go.mod h1:u/kbBOqIOgJv39gywuUb3VwyzdZG5DKquOqfToKE6lk=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223041816-c0cb3da7ea91 h1:bFBTbZglO4xNVWSLwDEcVKBIurTXGL2sNKi9UuQima4=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223041816-c0cb3da7ea91/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223072427-83db9c3e4eb5 h1:pmGmno31njvF5xncoDcDuM8mE1984cxrQ0DeVD4lVfA=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223072427-83db9c3e4eb5/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223110639-e9c10af0eb19 h1:t/AZCq8FiVNN+Mx6UmIv7bXj3+OVThg070G8ajZ3wJw=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223110639-e9c10af0eb19/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223121713-a8bf02a99d69 h1:h9lREy0OWSTrjweGxduikppA2tCjGPoUj32SVHI3dr0=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200223121713-a8bf02a99d69/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796 h1:VPXbYRvZUzTemsI7u0FzOnEuHeHwQuMTPXApAu8aeX4=
github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0=
github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U= github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U=
github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=

View File

@ -20,7 +20,7 @@ import (
) )
// Version is the gost version. // Version is the gost version.
const Version = "2.10.1" const Version = "2.10.2-dev"
// Debug is a flag that enables the debug log. // Debug is a flag that enables the debug log.
var Debug bool var Debug bool

View File

@ -83,10 +83,9 @@ func ParseNode(s string) (node Node, err error) {
case "kcp", "ssh", "quic": case "kcp", "ssh", "quic":
case "ssu": case "ssu":
node.Transport = "udp" node.Transport = "udp"
case "obfs4": case "ohttp", "otls", "obfs4": // obfs
case "tcp", "udp": case "tcp", "udp":
case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding
case "ohttp": // obfs-http
case "tun", "tap": // tun/tap device case "tun", "tap": // tun/tap device
case "ftcp": // fake TCP case "ftcp": // fake TCP
case "dns": case "dns":

312
obfs.go
View File

@ -5,6 +5,8 @@ package gost
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"crypto/rand"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -20,6 +22,7 @@ import (
pt "git.torproject.org/pluggable-transports/goptlib.git" pt "git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base" "git.torproject.org/pluggable-transports/obfs4.git/transports/base"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4" "git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4"
dissector "github.com/ginuerzh/tls-dissector"
) )
type obfsHTTPTransporter struct { type obfsHTTPTransporter struct {
@ -249,6 +252,315 @@ func (c *obfsHTTPConn) Write(b []byte) (n int, err error) {
return c.Conn.Write(b) return c.Conn.Write(b)
} }
type obfsTLSTransporter struct {
tcpTransporter
}
// ObfsTLSTransporter creates a Transporter that is used by TLS obfuscating.
func ObfsTLSTransporter() Transporter {
return &obfsTLSTransporter{}
}
func (tr *obfsTLSTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
opts := &HandshakeOptions{}
for _, option := range options {
option(opts)
}
return ClientObfsTLSConn(conn, opts.Host), nil
}
type obfsTLSListener struct {
net.Listener
}
// ObfsTLSListener creates a Listener for TLS obfuscating server.
func ObfsTLSListener(addr string) (Listener, error) {
laddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
ln, err := net.ListenTCP("tcp", laddr)
if err != nil {
return nil, err
}
return &obfsTLSListener{Listener: tcpKeepAliveListener{ln}}, nil
}
func (l *obfsTLSListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
return ServerObfsTLSConn(conn, ""), nil
}
var (
cipherSuites = []uint16{
0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8, 0xccaa, 0xc02b, 0xc02f,
0x009e, 0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067, 0xc00a,
0xc014, 0x0039, 0xc009, 0xc013, 0x0033, 0x009d, 0x009c, 0x003d,
0x003c, 0x0035, 0x002f, 0x00ff,
}
compressionMethods = []uint8{0x00}
algorithms = []uint16{
0x0601, 0x0602, 0x0603, 0x0501, 0x0502, 0x0503, 0x0401, 0x0402,
0x0403, 0x0301, 0x0302, 0x0303, 0x0201, 0x0202, 0x0203,
}
)
type obfsTLSConn struct {
net.Conn
rbuf bytes.Buffer
wbuf bytes.Buffer
host string
isServer bool
handshaked chan struct{}
handshakeMutex sync.Mutex
}
func ClientObfsTLSConn(conn net.Conn, host string) net.Conn {
return &obfsTLSConn{
Conn: conn,
host: host,
handshaked: make(chan struct{}),
}
}
func ServerObfsTLSConn(conn net.Conn, host string) net.Conn {
return &obfsTLSConn{
Conn: conn,
host: host,
isServer: true,
handshaked: make(chan struct{}),
}
}
func (c *obfsTLSConn) Handshaked() bool {
select {
case <-c.handshaked:
return true
default:
return false
}
}
func (c *obfsTLSConn) Handshake(payload []byte) (err error) {
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
if c.Handshaked() {
return
}
if c.isServer {
err = c.serverHandshake()
} else {
err = c.clientHandshake(payload)
}
if err != nil {
return
}
close(c.handshaked)
return nil
}
func (c *obfsTLSConn) clientHandshake(payload []byte) error {
clientMsg := &dissector.ClientHelloMsg{
Version: tls.VersionTLS12,
SessionID: make([]byte, 32),
CipherSuites: cipherSuites,
CompressionMethods: compressionMethods,
Extensions: []dissector.Extension{
&dissector.SessionTicketExtension{
Data: payload,
},
&dissector.ServerNameExtension{
Name: c.host,
},
&dissector.ECPointFormatsExtension{
Formats: []uint8{0x01, 0x00, 0x02},
},
&dissector.SupportedGroupsExtension{
Groups: []uint16{0x001d, 0x0017, 0x0019, 0x0018},
},
&dissector.SignatureAlgorithmsExtension{
Algorithms: algorithms,
},
&dissector.EncryptThenMacExtension{},
&dissector.ExtendedMasterSecretExtension{},
},
}
clientMsg.Random.Time = uint32(time.Now().Unix())
rand.Read(clientMsg.Random.Opaque[:])
rand.Read(clientMsg.SessionID)
b, err := clientMsg.Encode()
if err != nil {
return err
}
record := &dissector.Record{
Type: dissector.Handshake,
Version: tls.VersionTLS10,
Opaque: b,
}
if _, err := record.WriteTo(c.Conn); err != nil {
return err
}
// server hello handshake message
if _, err := record.ReadFrom(c.Conn); err != nil {
return err
}
if record.Type != dissector.Handshake {
return dissector.ErrBadType
}
// change cipher spec message
if _, err := record.ReadFrom(c.Conn); err != nil {
return err
}
if record.Type != dissector.ChangeCipherSpec {
return dissector.ErrBadType
}
// encrypted handshake message
if _, err := record.ReadFrom(c.Conn); err != nil {
return err
}
if record.Type != dissector.Handshake {
return dissector.ErrBadType
}
_, err = c.rbuf.Write(record.Opaque)
return err
}
func (c *obfsTLSConn) serverHandshake() error {
record := &dissector.Record{}
if _, err := record.ReadFrom(c.Conn); err != nil {
log.Log(err)
return err
}
if record.Type != dissector.Handshake {
return dissector.ErrBadType
}
clientMsg := &dissector.ClientHelloMsg{}
if err := clientMsg.Decode(record.Opaque); err != nil {
log.Log(err)
return err
}
for _, ext := range clientMsg.Extensions {
if ext.Type() == dissector.ExtSessionTicket {
b, err := ext.Encode()
if err != nil {
log.Log(err)
return err
}
c.rbuf.Write(b)
break
}
}
serverMsg := &dissector.ServerHelloMsg{
Version: tls.VersionTLS12,
SessionID: clientMsg.SessionID,
CipherSuite: 0xcca8,
CompressionMethod: 0x00,
Extensions: []dissector.Extension{
&dissector.RenegotiationInfoExtension{},
&dissector.ExtendedMasterSecretExtension{},
&dissector.ECPointFormatsExtension{
Formats: []uint8{0x00},
},
},
}
serverMsg.Random.Time = uint32(time.Now().Unix())
rand.Read(serverMsg.Random.Opaque[:])
b, err := serverMsg.Encode()
if err != nil {
return err
}
record = &dissector.Record{
Type: dissector.Handshake,
Version: tls.VersionTLS10,
Opaque: b,
}
if _, err := record.WriteTo(&c.wbuf); err != nil {
return err
}
record = &dissector.Record{
Type: dissector.ChangeCipherSpec,
Version: tls.VersionTLS12,
Opaque: []byte{0x01},
}
if _, err := record.WriteTo(&c.wbuf); err != nil {
return err
}
return nil
}
func (c *obfsTLSConn) Read(b []byte) (n int, err error) {
if c.isServer { // NOTE: only Write performs the handshake operation on client side.
if err = c.Handshake(nil); err != nil {
return
}
}
select {
case <-c.handshaked:
}
if c.rbuf.Len() > 0 {
return c.rbuf.Read(b)
}
record := &dissector.Record{}
if _, err = record.ReadFrom(c.Conn); err != nil {
return
}
n = copy(b, record.Opaque)
_, err = c.rbuf.Write(record.Opaque[n:])
return
}
func (c *obfsTLSConn) Write(b []byte) (n int, err error) {
n = len(b)
if !c.Handshaked() {
if err = c.Handshake(b); err != nil {
return
}
if !c.isServer { // the data b has been sended during handshake phase.
return
}
}
record := &dissector.Record{
Type: dissector.AppData,
Version: tls.VersionTLS12,
Opaque: b,
}
if c.wbuf.Len() > 0 {
record.Type = dissector.Handshake
record.WriteTo(&c.wbuf)
_, err = c.wbuf.WriteTo(c.Conn)
return
}
if _, err = record.WriteTo(c.Conn); err != nil {
return
}
return
}
type obfs4Context struct { type obfs4Context struct {
cf base.ClientFactory cf base.ClientFactory
cargs interface{} // type obfs4ClientArgs cargs interface{} // type obfs4ClientArgs

View File

@ -1,6 +1,6 @@
name: gost name: gost
type: app type: app
version: '2.10.1' version: '2.10.2'
title: GO Simple Tunnel title: GO Simple Tunnel
summary: A simple security tunnel written in golang summary: A simple security tunnel written in golang
description: | description: |

9
sni.go
View File

@ -270,7 +270,7 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
clientHello := &dissector.ClientHelloHandshake{} clientHello := &dissector.ClientHelloMsg{}
if err := clientHello.Decode(record.Opaque); err != nil { if err := clientHello.Decode(record.Opaque); err != nil {
return nil, "", err return nil, "", err
} }
@ -280,7 +280,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
for _, ext := range clientHello.Extensions { for _, ext := range clientHello.Extensions {
if ext.Type() == 0xFFFE { if ext.Type() == 0xFFFE {
if host, err = decodeServerName(string(ext.Bytes()[4:])); err == nil { b, _ := ext.Encode()
if host, err = decodeServerName(string(b)); err == nil {
continue continue
} }
} }
@ -296,8 +297,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
host = snExtension.Name host = snExtension.Name
} }
if isClient { if isClient {
clientHello.Extensions = append(clientHello.Extensions, e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))
dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))) clientHello.Extensions = append(clientHello.Extensions, e)
} }
if host != "" { if host != "" {
snExtension.Name = host snExtension.Name = host

49
ss.go
View File

@ -75,10 +75,21 @@ func (c *shadowConnector) ConnectContext(ctx context.Context, conn net.Conn, net
if c.cipher != nil { if c.cipher != nil {
conn = c.cipher.StreamConn(conn) conn = c.cipher.StreamConn(conn)
} }
if _, err := conn.Write(rawaddr[:n]); err != nil {
return nil, err sc := &shadowConn{
Conn: conn,
} }
return conn, nil
// write the addr at once.
if opts.NoDelay {
if _, err := sc.Write(rawaddr[:n]); err != nil {
return nil, err
}
} else {
sc.wbuf.Write(rawaddr[:n]) // cache the header
}
return sc, nil
} }
type shadowHandler struct { type shadowHandler struct {
@ -111,7 +122,9 @@ func (h *shadowHandler) Handle(conn net.Conn) {
defer conn.Close() defer conn.Close()
if h.cipher != nil { if h.cipher != nil {
conn = h.cipher.StreamConn(conn) conn = &shadowConn{
Conn: h.cipher.StreamConn(conn),
}
} }
conn.SetReadDeadline(time.Now().Add(ReadTimeout)) conn.SetReadDeadline(time.Now().Add(ReadTimeout))
@ -244,7 +257,9 @@ func (c *shadowUDPConnector) ConnectContext(ctx context.Context, conn net.Conn,
} }
if c.cipher != nil { if c.cipher != nil {
conn = c.cipher.StreamConn(conn) conn = &shadowConn{
Conn: c.cipher.StreamConn(conn),
}
} }
return &socks5UDPTunnelConn{ return &socks5UDPTunnelConn{
@ -302,7 +317,9 @@ func (h *shadowUDPHandler) Handle(conn net.Conn) {
} }
if h.cipher != nil { if h.cipher != nil {
conn = h.cipher.StreamConn(conn) conn = &shadowConn{
Conn: h.cipher.StreamConn(conn),
}
} }
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
@ -469,10 +486,17 @@ func (h *shadowUDPHandler) transportUDP(conn net.Conn, cc net.PacketConn) error
// we wrap around it to make io.Copy happy. // we wrap around it to make io.Copy happy.
type shadowConn struct { type shadowConn struct {
net.Conn net.Conn
wbuf bytes.Buffer
} }
func (c *shadowConn) Write(b []byte) (n int, err error) { func (c *shadowConn) Write(b []byte) (n int, err error) {
n = len(b) // force byte length consistent n = len(b) // force byte length consistent
if c.wbuf.Len() > 0 {
c.wbuf.Write(b) // append the data to the cached header
_, err = c.Conn.Write(c.wbuf.Bytes())
c.wbuf.Reset()
return
}
_, err = c.Conn.Write(b) _, err = c.Conn.Write(b)
return return
} }
@ -546,9 +570,7 @@ type shadowCipher struct {
} }
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn { func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
return &shadowConn{ return ss.NewConn(conn, c.cipher.Copy())
Conn: ss.NewConn(conn, c.cipher.Copy()),
}
} }
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn { func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
@ -566,14 +588,17 @@ func initShadowCipher(info *url.Userinfo) (cipher core.Cipher) {
return return
} }
cipher, _ = core.PickCipher(method, nil, password) cp, _ := ss.NewCipher(method, password)
if cp != nil {
cipher = &shadowCipher{cipher: cp}
}
if cipher == nil { if cipher == nil {
cp, err := ss.NewCipher(method, password) var err error
cipher, err = core.PickCipher(method, nil, password)
if err != nil { if err != nil {
log.Logf("[ss] %s", err) log.Logf("[ss] %s", err)
return return
} }
cipher = &shadowCipher{cipher: cp}
} }
return return
} }