diff --git a/api/client.go b/api/client.go index 54b0706..be9139f 100644 --- a/api/client.go +++ b/api/client.go @@ -2,13 +2,13 @@ package api import ( "context" + "net" "time" "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/stat" "google.golang.org/grpc" - "v2ray.com/core/common/net" ) type ClientAPIService struct { diff --git a/build-all.sh b/build-all.sh index 083bb0b..c92f941 100755 --- a/build-all.sh +++ b/build-all.sh @@ -3,16 +3,16 @@ PLATFORMS="darwin/amd64 darwin/386" PLATFORMS="$PLATFORMS windows/amd64 windows/386" PLATFORMS="$PLATFORMS linux/amd64 linux/386" -PLATFORMS="$PLATFORMS freebsd/amd64 freebsd/386" -PLATFORMS="$PLATFORMS openbsd/amd64 openbsd/386" PLATFORMS="$PLATFORMS linux/ppc64 linux/ppc64le" PLATFORMS="$PLATFORMS linux/mips64 linux/mips64le" PLATFORMS="$PLATFORMS linux/mips linux/mipsle" -PLATFORMS="$PLATFORMS dragonfly/amd64" PLATFORMS="$PLATFORMS linux/arm64 linux/arm" -PLATFORMS="$PLATFORMS freebsd/arm64 freebsd/arm" -PLATFORMS="$PLATFORMS openbsd/arm64 openbsd/arm" PLATFORMS="$PLATFORMS linux/s390x" +PLATFORMS="$PLATFORMS dragonfly/amd64" +PLATFORMS="$PLATFORMS openbsd/arm64 openbsd/arm" +PLATFORMS="$PLATFORMS openbsd/amd64 openbsd/386" +PLATFORMS="$PLATFORMS freebsd/amd64 freebsd/386" +PLATFORMS="$PLATFORMS freebsd/arm64 freebsd/arm" type setopt >/dev/null 2>&1 diff --git a/common/common.go b/common/common.go index 7067e96..a715867 100644 --- a/common/common.go +++ b/common/common.go @@ -11,7 +11,7 @@ import ( ) const ( - Version = "v0.4.1" + Version = "v0.4.2" ) type Runnable interface { diff --git a/conf/conf.go b/conf/conf.go index 2dbd517..de0738b 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -19,20 +19,21 @@ const ( ) type TLSConfig struct { - Verify bool `json:"verify"` - VerifyHostname bool `json:"verify_hostname"` - CertPath string `json:"cert"` - KeyPath string `json:"key"` - KeyPassword string `json:"key_password"` - Cipher string `json:"cipher"` - CipherTLS13 string `json:"cipher_tls13"` - PreferServerCipher bool `json:"prefer_server_cipher"` - SNI string `json:"sni"` - HTTPFile string `json:"plain_http_response"` - FallbackHost string `json:"fallback_addr"` - FallbackPort int `json:"fallback_port"` - ReuseSession bool `json:"reuse_session"` - Fingerprint string `json:"fingerprint"` + Verify bool `json:"verify"` + VerifyHostname bool `json:"verify_hostname"` + CertPath string `json:"cert"` + KeyPath string `json:"key"` + KeyPassword string `json:"key_password"` + Cipher string `json:"cipher"` + CipherTLS13 string `json:"cipher_tls13"` + PreferServerCipher bool `json:"prefer_server_cipher"` + SNI string `json:"sni"` + HTTPFile string `json:"plain_http_response"` + FallbackHost string `json:"fallback_addr"` + FallbackPort int `json:"fallback_port"` + ReuseSession bool `json:"reuse_session"` + ALPN []string `json:"alpn"` + Fingerprint string `json:"fingerprint"` ClientHelloID *utls.ClientHelloID FallbackAddress *common.Address @@ -122,25 +123,27 @@ type APIConfig struct { } type GlobalConfig struct { - RunType RunType `json:"run_type"` - LogLevel int `json:"log_level"` - LocalHost string `json:"local_addr"` - LocalPort int `json:"local_port"` - TargetHost string `json:"target_addr"` - TargetPort int `json:"target_port"` - RemoteHost string `json:"remote_addr"` - RemotePort int `json:"remote_port"` - BufferSize int `json:"buffer_size"` - Passwords []string `json:"password"` - DNS []string `json:"dns"` - TLS TLSConfig `json:"ssl"` - TCP TCPConfig `json:"tcp"` - MySQL MySQLConfig `json:"mysql"` - SQLite SQLiteConfig `json:"sqlite"` - Mux MuxConfig `json:"mux"` - Router RouterConfig `json:"router"` - Websocket WebsocketConfig `json:"websocket"` - API APIConfig `json:"api"` + RunType RunType `json:"run_type"` + LogLevel int `json:"log_level"` + LogFile string `json:"log_file"` + LocalHost string `json:"local_addr"` + LocalPort int `json:"local_port"` + TargetHost string `json:"target_addr"` + TargetPort int `json:"target_port"` + RemoteHost string `json:"remote_addr"` + RemotePort int `json:"remote_port"` + BufferSize int `json:"buffer_size"` + DisableHTTPCheck bool `json:"disable_http_check"` + Passwords []string `json:"password"` + DNS []string `json:"dns"` + TLS TLSConfig `json:"ssl"` + TCP TCPConfig `json:"tcp"` + MySQL MySQLConfig `json:"mysql"` + SQLite SQLiteConfig `json:"sqlite"` + Mux MuxConfig `json:"mux"` + Router RouterConfig `json:"router"` + Websocket WebsocketConfig `json:"websocket"` + API APIConfig `json:"api"` LocalAddress *common.Address RemoteAddress *common.Address diff --git a/conf/parse.go b/conf/parse.go index 79d9a2c..1ebcfeb 100644 --- a/conf/parse.go +++ b/conf/parse.go @@ -256,16 +256,17 @@ func loadClientConfig(config *GlobalConfig) error { } func loadServerConfig(config *GlobalConfig) error { - //check web server - resp, err := http.Get("http://" + config.RemoteAddress.String()) - if err != nil { - return common.NewError(config.RemoteAddress.String() + " is not a valid web server").Base(err) + if !config.DisableHTTPCheck { + resp, err := http.Get("http://" + config.RemoteAddress.String()) + if err != nil { + return common.NewError(config.RemoteAddress.String() + " is not a valid web server").Base(err) + } + buf := [128]byte{} + _, err = resp.Body.Read(buf[:]) + log.Debug("body:\n" + string(buf[:])) + resp.Body.Close() } - buf := [128]byte{} - _, err = resp.Body.Read(buf[:]) - log.Debug("body:\n" + string(buf[:])) - resp.Body.Close() if config.TLS.KeyPassword != "" { keyFile, err := ioutil.ReadFile(config.TLS.KeyPath) @@ -316,10 +317,17 @@ func ParseJSON(data []byte) (*GlobalConfig, error) { //default settings config.LogLevel = 1 config.BufferSize = 512 + config.TCP.FastOpenQLen = 20 + config.TCP.KeepAlive = true + config.TCP.NoDelay = true config.TLS.Verify = true config.TLS.VerifyHostname = true config.TLS.SessionTicket = true config.TLS.ReuseSession = true + config.TLS.ALPN = []string{ + "http/1.1", + "h2", + } config.Mux.IdleTimeout = 60 config.Mux.Concurrency = 8 config.MySQL.CheckRate = 60 diff --git a/easy/easy.go b/easy/easy.go index 00f6f9d..6c5d521 100644 --- a/easy/easy.go +++ b/easy/easy.go @@ -3,12 +3,12 @@ package easy import ( "flag" "fmt" + "net" "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/proxy" - "v2ray.com/core/common/net" ) type EasyOption struct { diff --git a/go.mod b/go.mod index 68f64ff..677724b 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( github.com/go-acme/lego/v3 v3.5.0 github.com/go-sql-driver/mysql v1.5.0 github.com/golang/protobuf v1.4.0 + github.com/posener/h2conn v0.0.0-20180911140238-13e7df33ed15 github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57 github.com/smartystreets/goconvey v1.6.4 - github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a github.com/xtaci/smux v1.5.12 golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 golang.org/x/net v0.0.0-20200421231249-e086a090c8fd diff --git a/go.sum b/go.sum index 4b6d55b..a2b6479 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ= +github.com/gdamore/tcell v1.1.0/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-acme/lego/v3 v3.5.0 h1:/0+NJQK+hNwRznhCi+19lbEa4xufhe7wJZOVd5j486s= github.com/go-acme/lego/v3 v3.5.0/go.mod h1:TXodhTGOiWEqXDdgrzBoCtJ5R4L9lfOE68CTM0KGkT0= @@ -178,8 +180,11 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HK github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= +github.com/lucasb-eyer/go-colorful v0.0.0-20180709185858-c7842319cf3a/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= +github.com/marcusolsson/tui-go v0.3.0/go.mod h1:cW3uKFFnYI5ywRJlYvcaoK/1yDVyld22v5erMdEVWO4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -188,6 +193,7 @@ github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -212,6 +218,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/h2conn v0.0.0-20180911140238-13e7df33ed15 h1:N2JoDX2KIfZlzcMuTqPTeeMXi8GwdwJHgZ8sXqe73Ds= +github.com/posener/h2conn v0.0.0-20180911140238-13e7df33ed15/go.mod h1:Ncj2NdkYalS3y+a1qSENl09uDMvEIoICB8dAfzsL9BA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -256,8 +264,6 @@ github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLp github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= diff --git a/main.go b/main.go index f6e538f..a47c1a5 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( _ "github.com/p4gefau1t/trojan-go/proxy/client" _ "github.com/p4gefau1t/trojan-go/proxy/relay" _ "github.com/p4gefau1t/trojan-go/proxy/server" + _ "github.com/p4gefau1t/trojan-go/router/mixed" _ "github.com/p4gefau1t/trojan-go/version" //_ "github.com/p4gefau1t/trojan-go/log/simplelog" ) diff --git a/protocol/trojan/h2.go b/protocol/trojan/h2.go new file mode 100644 index 0000000..b477f03 --- /dev/null +++ b/protocol/trojan/h2.go @@ -0,0 +1,69 @@ +package trojan + +import ( + "context" + "crypto/tls" + "io" + "net" + "net/http" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" + "github.com/posener/h2conn" + "golang.org/x/net/http2" +) + +//just for fun + +func NewH2InboundConn(ctx context.Context, conn net.Conn) (io.ReadWriteCloser, error) { + rewindConn := common.NewRewindConn(conn) + rewindConn.R.SetBufferSize(512) + defer rewindConn.R.StopBuffering() + framer := http2.NewFramer(nil, rewindConn) + frame, err := framer.ReadFrame() + if err != nil { + return nil, err + } + log.Debug(frame.Header()) + rewindConn.R.Rewind() + var newConn *h2conn.Conn + errChan := make(chan error) + + h2Server := http2.Server{} + go h2Server.ServeConn(rewindConn, &http2.ServeConnOpts{ + Context: ctx, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + newConn, err = h2conn.Accept(w, r) + errChan <- err + if err != nil { + return + } + <-ctx.Done() + }), + }) + err = <-errChan + if err != nil { + return nil, err + } + return newConn, nil +} + +func NewH2OutboundConn(ctx context.Context, conn net.Conn) (io.ReadWriteCloser, error) { + httpClient := &http.Client{ + Transport: &http2.Transport{ + DialTLS: func(string, string, *tls.Config) (net.Conn, error) { + return conn, nil + }, + }, + } + h2ConnClient := h2conn.Client{ + Client: httpClient, + } + newConn, resp, err := h2ConnClient.Connect(ctx, "https://trojan.server/testpath") + log.Debug(resp) + if err != nil { + return nil, err + } + return newConn, nil +} diff --git a/protocol/trojan/h2_test.go b/protocol/trojan/h2_test.go new file mode 100644 index 0000000..fbe4693 --- /dev/null +++ b/protocol/trojan/h2_test.go @@ -0,0 +1,41 @@ +package trojan + +import ( + "net" + "testing" + "time" + + "github.com/p4gefau1t/trojan-go/common" + "golang.org/x/net/context" +) + +func TestHTTP2(t *testing.T) { + ctx := context.Background() + go func() { + l, err := net.Listen("tcp", "127.0.0.1:4444") + common.Must(err) + conn, err := l.Accept() + common.Must(err) + rwc, err := NewH2InboundConn(ctx, conn) + common.Must(err) + common.Must2(rwc.Write([]byte("HelloImServer"))) + buf := [256]byte{} + n, err := rwc.Read(buf[:]) + common.Must(err) + if string(buf[:n]) != "HelloImClient" { + t.Fail() + } + rwc.Close() + }() + time.Sleep(time.Second) + conn, err := net.Dial("tcp", "127.0.0.1:4444") + common.Must(err) + rwc, err := NewH2OutboundConn(ctx, conn) + buf := [256]byte{} + n, err := rwc.Read(buf[:]) + if string(buf[:n]) != "HelloImServer" { + t.Fail() + } + common.Must2(rwc.Write([]byte("HelloImClient"))) + rwc.Close() +} diff --git a/protocol/trojan/websocket.go b/protocol/trojan/websocket.go index 4f8159b..d27fa6a 100644 --- a/protocol/trojan/websocket.go +++ b/protocol/trojan/websocket.go @@ -42,7 +42,7 @@ func (rwc *obfReadWriteCloser) Close() error { return rwc.Conn.Close() } -func NewOutboundObfReadWriteCloser(key []byte, conn *websocket.Conn) *obfReadWriteCloser { +func NewOutboundObfReadWriteCloser(key []byte, conn net.Conn) *obfReadWriteCloser { //use bufio to avoid fixed ws packet length bufrw := common.NewBufioReadWriter(conn) iv := [aes.BlockSize]byte{} diff --git a/proxy/client/client.go b/proxy/client/client.go index 17ec489..d456a77 100644 --- a/proxy/client/client.go +++ b/proxy/client/client.go @@ -316,7 +316,7 @@ func (c *Client) listenTCP(errChan chan error) { for { conn, err := listener.Accept() if err != nil { - errChan <- common.NewError("error occured when accpeting conn").Base(err) + errChan <- common.NewError("error occured when accepting conn").Base(err) return } rwc := common.NewRewindReadWriteCloser(conn) @@ -368,9 +368,7 @@ func (c *Client) Close() error { func (c *Client) Build(config *conf.GlobalConfig) (common.Runnable, error) { c.ctx, c.cancel = context.WithCancel(context.Background()) c.associated = common.NewNotifier() - c.router = &router.EmptyRouter{ - DefaultPolicy: router.Proxy, - } + c.router = &router.EmptyRouter{} c.meter = &stat.MemoryTrafficMeter{} var err error if config.Mux.Enabled { @@ -381,7 +379,7 @@ func (c *Client) Build(config *conf.GlobalConfig) (common.Runnable, error) { } if config.Router.Enabled { log.Info("router enabled") - c.router, err = router.NewMixedRouter(config) + c.router, err = router.NewRouter(&config.Router) if err != nil { log.Fatal(common.NewError("invalid router list").Base(err)) } diff --git a/proxy/client/forward.go b/proxy/client/forward.go index 5fc97ac..5a6151f 100644 --- a/proxy/client/forward.go +++ b/proxy/client/forward.go @@ -169,7 +169,7 @@ func (f *Forward) listenTCP(errChan chan error) { for { inboundConn, err := listener.Accept() if err != nil { - errChan <- common.NewError("error occured when accpeting conn").Base(err) + errChan <- common.NewError("error occured when accepting conn").Base(err) } handle := func(inboundConn net.Conn) { outboundConn, err := f.openOutboundConn(req) diff --git a/proxy/client/tls.go b/proxy/client/tls.go index 46d17eb..c433967 100644 --- a/proxy/client/tls.go +++ b/proxy/client/tls.go @@ -12,6 +12,7 @@ import ( "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/protocol/trojan" + "github.com/p4gefau1t/trojan-go/sockopt" utls "github.com/refraction-networking/utls" ) @@ -156,12 +157,21 @@ func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) { tlsConn = utls.UClient(conn, m.utlsConfig, *m.config.TLS.ClientHelloID) } else { //normal golang tls - tlsConn, err = tls.Dial(network, m.config.RemoteAddress.String(), m.tlsConfig) + conn, err := net.Dial(network, m.config.RemoteAddress.String()) + if err != nil { + return nil, err + } + err = sockopt.ApplyTCPConnOption(conn.(*net.TCPConn), &m.config.TCP) + if err != nil { + return nil, common.NewError("failed to apply tcp option").Base(err) + } + tlsConn = tls.Client(conn, m.tlsConfig) + err = tlsConn.(*tls.Conn).Handshake() } - m.printConnInfo(tlsConn) if err != nil { return nil, common.NewError("cannot dial to the remote server").Base(err) } + m.printConnInfo(tlsConn) var transport io.ReadWriteCloser = tlsConn if m.config.Websocket.Enabled { ws, err := trojan.NewOutboundWebosocket(tlsConn, m.config) diff --git a/proxy/server/server.go b/proxy/server/server.go index 844dfeb..8cef03e 100644 --- a/proxy/server/server.go +++ b/proxy/server/server.go @@ -16,6 +16,7 @@ import ( "github.com/p4gefau1t/trojan-go/protocol/trojan" "github.com/p4gefau1t/trojan-go/proxy" "github.com/p4gefau1t/trojan-go/shadow" + "github.com/p4gefau1t/trojan-go/sockopt" "github.com/p4gefau1t/trojan-go/stat" "github.com/xtaci/smux" ) @@ -62,7 +63,7 @@ func (s *Server) handleMuxConn(stream *smux.Stream) { } } -func (s *Server) handleConn(conn net.Conn) { +func (s *Server) handleConn(conn *tls.Conn) { inboundConn, req, err := trojan.NewInboundConnSession(s.ctx, conn, s.config, s.auth, s.shadow) if err != nil { //once the auth is failed, the conn will be took over by shadow manager. don't close it @@ -151,32 +152,24 @@ func (s *Server) Run() error { log.Info("server is running at", s.config.LocalAddress) var listener net.Listener - if s.config.TCP.ReusePort || s.config.TCP.FastOpen || s.config.TCP.NoDelay { - localIP, err := s.config.LocalAddress.ResolveIP(false) - listener, err = ListenWithTCPOption( - s.config.TCP.FastOpen, - s.config.TCP.ReusePort, - s.config.TCP.NoDelay, - localIP, - s.config.LocalAddress.String(), - ) - if err != nil { - return err - } - } else { - listener, err = net.Listen("tcp", s.config.LocalAddress.String()) - if err != nil { - return err - } + listener, err = net.Listen("tcp", s.config.LocalAddress.String()) + if err != nil { + return err } s.listener = listener defer listener.Close() + err = sockopt.ApplyTCPListenerOption(listener.(*net.TCPListener), &s.config.TCP) + if err != nil { + return common.NewError(fmt.Sprintf("failed to apply tcp option: %v", &s.config.TCP)).Base(err) + } + tlsConfig := &tls.Config{ Certificates: s.config.TLS.KeyPair, CipherSuites: s.config.TLS.CipherSuites, PreferServerCipherSuites: s.config.TLS.PreferServerCipher, SessionTicketsDisabled: !s.config.TLS.SessionTicket, + NextProtos: s.config.TLS.ALPN, } for { conn, err := listener.Accept() @@ -195,7 +188,6 @@ func (s *Server) Run() error { tlsConn := tls.Server(rewindConn, tlsConfig) err = tlsConn.Handshake() - rewindConn.R.StopBuffering() if err != nil { diff --git a/proxy/server/tcp_option.go b/proxy/server/tcp_option.go deleted file mode 100644 index 554536e..0000000 --- a/proxy/server/tcp_option.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build !windows - -package server - -import ( - "net" - - "github.com/valyala/tcplisten" -) - -func ListenWithTCPOption(fastOpen, reusePort, noDelay bool, ip net.IP, addr string) (net.Listener, error) { - cfg := tcplisten.Config{ - ReusePort: reusePort, - FastOpen: fastOpen, - DeferAccept: noDelay, - } - network := "tcp6" - if ip.To4() != nil { - network = "tcp4" - } - return cfg.NewListener(network, addr) -} diff --git a/proxy/server/tcp_option_stub.go b/proxy/server/tcp_option_stub.go deleted file mode 100644 index 86bb8eb..0000000 --- a/proxy/server/tcp_option_stub.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build windows - -package server - -import ( - "net" -) - -func ListenWithTCPOption(fastOpen, reusePort, noDelay bool, ip net.IP, addr string) (net.Listener, error) { - panic("this os does not support tcp options") -} diff --git a/router/empty.go b/router/empty.go deleted file mode 100644 index f9ba2c4..0000000 --- a/router/empty.go +++ /dev/null @@ -1,11 +0,0 @@ -package router - -import "github.com/p4gefau1t/trojan-go/protocol" - -type EmptyRouter struct { - DefaultPolicy Policy -} - -func (r *EmptyRouter) RouteRequest(req *protocol.Request) (Policy, error) { - return r.DefaultPolicy, nil -} diff --git a/router/mixed.go b/router/mixed.go deleted file mode 100644 index 46613b6..0000000 --- a/router/mixed.go +++ /dev/null @@ -1,106 +0,0 @@ -package router - -import ( - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" -) - -type MixedRouter struct { - proxyList *ListRouter - bypassList *ListRouter - blockList *ListRouter - proxyGeo *GeoRouter - bypassGeo *GeoRouter - blockGeo *GeoRouter - defaultPolicy Policy -} - -func (r *MixedRouter) match(router Router, req *protocol.Request) bool { - policy, err := router.RouteRequest(req) - if err != nil { - log.Warn(common.NewError("match error").Base(err)) - return false - } - if policy == match { - return true - } - return false -} - -func (r *MixedRouter) RouteRequest(req *protocol.Request) (Policy, error) { - - if r.match(r.blockGeo, req) { - return Block, nil - } - if r.match(r.blockList, req) { - return Block, nil - } - - if r.match(r.bypassGeo, req) { - return Bypass, nil - } - if r.match(r.bypassList, req) { - return Bypass, nil - } - - if r.match(r.proxyGeo, req) { - return Proxy, nil - } - if r.match(r.proxyList, req) { - return Proxy, nil - } - - return r.defaultPolicy, nil -} - -func NewMixedRouter(config *conf.GlobalConfig) (Router, error) { - var defaultPolicy Policy - - switch config.Router.DefaultPolicy { - case "proxy": - defaultPolicy = Proxy - case "bypass": - defaultPolicy = Bypass - case "block": - defaultPolicy = Block - } - - routeByIP := config.Router.RouteByIP - routeByIPOnNonmatch := config.Router.RouteByIPOnNonmatch - - block := config.Router.BlockList - bypass := config.Router.BypassList - proxy := config.Router.ProxyList - - r := &MixedRouter{ - defaultPolicy: defaultPolicy, - } - - var err error - if r.blockList, err = NewListRouter(match, nonMatch, routeByIP, routeByIPOnNonmatch, block); err != nil { - return nil, err - } - if r.bypassList, err = NewListRouter(match, nonMatch, routeByIP, routeByIPOnNonmatch, bypass); err != nil { - return nil, err - } - if r.proxyList, err = NewListRouter(match, nonMatch, routeByIP, routeByIPOnNonmatch, proxy); err != nil { - return nil, err - } - - r.blockGeo, _ = NewGeoRouter(match, nonMatch, routeByIP, false) - r.bypassGeo, _ = NewGeoRouter(match, nonMatch, routeByIP, routeByIPOnNonmatch) - r.proxyGeo, _ = NewGeoRouter(match, nonMatch, routeByIP, routeByIPOnNonmatch) - - if err := r.blockGeo.LoadGeoData(config.Router.GeoIP, config.Router.BlockIPCode, config.Router.GeoSite, config.Router.BlockSiteCode); err != nil { - log.Warn(err) - } - if err := r.bypassGeo.LoadGeoData(config.Router.GeoIP, config.Router.BypassIPCode, config.Router.GeoSite, config.Router.BypassSiteCode); err != nil { - log.Warn(err) - } - if err := r.proxyGeo.LoadGeoData(config.Router.GeoIP, config.Router.ProxyIPCode, config.Router.GeoSite, config.Router.ProxySiteCode); err != nil { - log.Warn(err) - } - return r, nil -} diff --git a/router/geo.go b/router/mixed/geo.go similarity index 81% rename from router/geo.go rename to router/mixed/geo.go index 1d5e0fc..192fa1a 100644 --- a/router/geo.go +++ b/router/mixed/geo.go @@ -1,4 +1,4 @@ -package router +package mixed import ( "net" @@ -9,15 +9,15 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/protocol" - "v2ray.com/core/app/router" + "github.com/p4gefau1t/trojan-go/router" + v2router "v2ray.com/core/app/router" ) type GeoRouter struct { - Router - domains []*router.Domain - cidrs []*router.CIDR - matchPolicy Policy - nonMatchPolicy Policy + domains []*v2router.Domain + cidrs []*v2router.CIDR + matchPolicy router.Policy + nonMatchPolicy router.Policy routeByIP bool routeByIPOnNonmatch bool } @@ -35,7 +35,7 @@ func (r *GeoRouter) isSubdomain(fulldomain, domain string) bool { func (r *GeoRouter) matchDomain(fulldomain string) bool { for _, d := range r.domains { switch d.GetType() { - case router.Domain_Domain, router.Domain_Full: + case v2router.Domain_Domain, v2router.Domain_Full: domain := d.GetValue() if strings.HasSuffix(fulldomain, domain) { idx := strings.Index(fulldomain, domain) @@ -43,12 +43,12 @@ func (r *GeoRouter) matchDomain(fulldomain string) bool { return true } } - case router.Domain_Plain: + case v2router.Domain_Plain: //keyword if strings.Contains(fulldomain, d.GetValue()) { return true } - case router.Domain_Regex: + case v2router.Domain_Regex: //expregexp.Compile(site.GetValue()) matched, err := regexp.Match(d.GetValue(), []byte(fulldomain)) if err != nil { @@ -91,10 +91,10 @@ func (r *GeoRouter) matchIP(ip net.IP) bool { return false } -func (r *GeoRouter) routeRequestByIP(domain string) (Policy, error) { +func (r *GeoRouter) routeRequestByIP(domain string) (router.Policy, error) { addr, err := net.ResolveIPAddr("ip", domain) if err != nil { - return Unknown, err + return router.Unknown, err } atype := common.IPv6 if addr.IP.To4() != nil { @@ -108,7 +108,7 @@ func (r *GeoRouter) routeRequestByIP(domain string) (Policy, error) { }) } -func (r *GeoRouter) RouteRequest(req *protocol.Request) (Policy, error) { +func (r *GeoRouter) RouteRequest(req *protocol.Request) (router.Policy, error) { if r.domains == nil || r.cidrs == nil { return r.nonMatchPolicy, nil } @@ -131,12 +131,12 @@ func (r *GeoRouter) RouteRequest(req *protocol.Request) (Policy, error) { } return r.nonMatchPolicy, nil default: - return Unknown, common.NewError("invalid address type") + return router.Unknown, common.NewError("invalid address type") } } func (r *GeoRouter) LoadGeoData(geoipData []byte, ipCode []string, geositeData []byte, siteCode []string) error { - geoip := new(router.GeoIPList) + geoip := new(v2router.GeoIPList) if err := proto.Unmarshal(geoipData, geoip); err != nil { return err } @@ -158,7 +158,7 @@ func (r *GeoRouter) LoadGeoData(geoipData []byte, ipCode []string, geositeData [ } } - geosite := new(router.GeoSiteList) + geosite := new(v2router.GeoSiteList) if err := proto.Unmarshal(geositeData, geosite); err != nil { return err } @@ -183,7 +183,7 @@ func (r *GeoRouter) LoadGeoData(geoipData []byte, ipCode []string, geositeData [ return nil } -func NewGeoRouter(matchPolicy Policy, nonMatchPolicy Policy, routeByIP bool, routeByIPOnNonmatch bool) (*GeoRouter, error) { +func NewGeoRouter(matchPolicy router.Policy, nonMatchPolicy router.Policy, routeByIP bool, routeByIPOnNonmatch bool) (*GeoRouter, error) { r := GeoRouter{ matchPolicy: matchPolicy, nonMatchPolicy: nonMatchPolicy, diff --git a/router/geo_test.go b/router/mixed/geo_test.go similarity index 84% rename from router/geo_test.go rename to router/mixed/geo_test.go index 01ea947..00666bd 100644 --- a/router/geo_test.go +++ b/router/mixed/geo_test.go @@ -1,4 +1,4 @@ -package router +package mixed import ( "io/ioutil" @@ -7,10 +7,11 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/protocol" + "github.com/p4gefau1t/trojan-go/router" ) func TestGeoRouter(t *testing.T) { - r, err := NewGeoRouter(Bypass, Proxy, false, false) + r, err := NewGeoRouter(router.Bypass, router.Proxy, false, false) common.Must(err) geoipData, err := ioutil.ReadFile("geoip.dat") common.Must(err) @@ -25,7 +26,7 @@ func TestGeoRouter(t *testing.T) { }, }) common.Must(err) - if p != Proxy { + if p != router.Proxy { t.Fatal("wrong result") } @@ -36,7 +37,7 @@ func TestGeoRouter(t *testing.T) { }, }) common.Must(err) - if p != Bypass { + if p != router.Bypass { t.Fatal("wrong result") } @@ -47,7 +48,7 @@ func TestGeoRouter(t *testing.T) { }, }) common.Must(err) - if p != Proxy { + if p != router.Proxy { t.Fatal("wrong result") } @@ -58,7 +59,7 @@ func TestGeoRouter(t *testing.T) { }, }) common.Must(err) - if p != Bypass { + if p != router.Bypass { t.Fatal("wrong result") } } diff --git a/router/list.go b/router/mixed/list.go similarity index 83% rename from router/list.go rename to router/mixed/list.go index 722f1e8..50cc844 100644 --- a/router/list.go +++ b/router/mixed/list.go @@ -1,4 +1,4 @@ -package router +package mixed import ( "bytes" @@ -7,14 +7,15 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/protocol" + "github.com/p4gefau1t/trojan-go/router" ) type ListRouter struct { - Router + router.Router domainList []string ipList []*net.IPNet - matchPolicy Policy - nonMatchPolicy Policy + matchPolicy router.Policy + nonMatchPolicy router.Policy routeByIP bool routeByIPOnNonmatch bool } @@ -29,7 +30,7 @@ func (r *ListRouter) isSubdomain(fulldomain, domain string) bool { return false } -func (r *ListRouter) RouteRequest(req *protocol.Request) (Policy, error) { +func (r *ListRouter) RouteRequest(req *protocol.Request) (router.Policy, error) { switch req.AddressType { case common.DomainName: domain := string(req.DomainName) @@ -44,7 +45,7 @@ func (r *ListRouter) RouteRequest(req *protocol.Request) (Policy, error) { if r.routeByIP { addr, err := net.ResolveIPAddr("ip", domain) if err != nil { - return Unknown, err + return router.Unknown, err } atype := common.IPv6 if addr.IP.To4() != nil { @@ -65,7 +66,7 @@ func (r *ListRouter) RouteRequest(req *protocol.Request) (Policy, error) { if r.routeByIPOnNonmatch { addr, err := net.ResolveIPAddr("ip", domain) if err != nil { - return Unknown, err + return router.Unknown, err } atype := common.IPv6 if addr.IP.To4() != nil { @@ -88,7 +89,7 @@ func (r *ListRouter) RouteRequest(req *protocol.Request) (Policy, error) { } return r.nonMatchPolicy, nil default: - return Unknown, common.NewError("invalid address type") + return router.Unknown, common.NewError("invalid address type") } } @@ -115,7 +116,7 @@ func (r *ListRouter) LoadList(data []byte) error { return nil } -func NewListRouter(matchPolicy Policy, nonMatchPolicy Policy, routeByIP bool, routeByIPOnNonmatch bool, list []byte) (*ListRouter, error) { +func NewListRouter(matchPolicy router.Policy, nonMatchPolicy router.Policy, routeByIP bool, routeByIPOnNonmatch bool, list []byte) (*ListRouter, error) { r := ListRouter{ matchPolicy: matchPolicy, nonMatchPolicy: nonMatchPolicy, diff --git a/router/mixed/mixed.go b/router/mixed/mixed.go new file mode 100644 index 0000000..006aa98 --- /dev/null +++ b/router/mixed/mixed.go @@ -0,0 +1,111 @@ +package mixed + +import ( + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/conf" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/protocol" + "github.com/p4gefau1t/trojan-go/router" +) + +type MixedRouter struct { + proxyList *ListRouter + bypassList *ListRouter + blockList *ListRouter + proxyGeo *GeoRouter + bypassGeo *GeoRouter + blockGeo *GeoRouter + defaultPolicy router.Policy +} + +func (r *MixedRouter) match(rr router.Router, req *protocol.Request) bool { + policy, err := rr.RouteRequest(req) + if err != nil { + log.Warn(common.NewError("match error").Base(err)) + return false + } + if policy == router.Match { + return true + } + return false +} + +func (r *MixedRouter) RouteRequest(req *protocol.Request) (router.Policy, error) { + + if r.match(r.blockGeo, req) { + return router.Block, nil + } + if r.match(r.blockList, req) { + return router.Block, nil + } + + if r.match(r.bypassGeo, req) { + return router.Bypass, nil + } + if r.match(r.bypassList, req) { + return router.Bypass, nil + } + + if r.match(r.proxyGeo, req) { + return router.Proxy, nil + } + if r.match(r.proxyList, req) { + return router.Proxy, nil + } + + return r.defaultPolicy, nil +} + +func NewMixedRouter(config *conf.RouterConfig) (router.Router, error) { + var defaultPolicy router.Policy + + switch config.DefaultPolicy { + case "proxy": + defaultPolicy = router.Proxy + case "bypass": + defaultPolicy = router.Bypass + case "block": + defaultPolicy = router.Block + } + + routeByIP := config.RouteByIP + routeByIPOnNonmatch := config.RouteByIPOnNonmatch + + block := config.BlockList + bypass := config.BypassList + proxy := config.ProxyList + + r := &MixedRouter{ + defaultPolicy: defaultPolicy, + } + + var err error + if r.blockList, err = NewListRouter(router.Match, router.NonMatch, routeByIP, routeByIPOnNonmatch, block); err != nil { + return nil, err + } + if r.bypassList, err = NewListRouter(router.Match, router.NonMatch, routeByIP, routeByIPOnNonmatch, bypass); err != nil { + return nil, err + } + if r.proxyList, err = NewListRouter(router.Match, router.NonMatch, routeByIP, routeByIPOnNonmatch, proxy); err != nil { + return nil, err + } + + r.blockGeo, _ = NewGeoRouter(router.Match, router.NonMatch, routeByIP, false) + r.bypassGeo, _ = NewGeoRouter(router.Match, router.NonMatch, routeByIP, routeByIPOnNonmatch) + r.proxyGeo, _ = NewGeoRouter(router.Match, router.NonMatch, routeByIP, routeByIPOnNonmatch) + + if err := r.blockGeo.LoadGeoData(config.GeoIP, config.BlockIPCode, config.GeoSite, config.BlockSiteCode); err != nil { + log.Warn(err) + } + if err := r.bypassGeo.LoadGeoData(config.GeoIP, config.BypassIPCode, config.GeoSite, config.BypassSiteCode); err != nil { + log.Warn(err) + } + if err := r.proxyGeo.LoadGeoData(config.GeoIP, config.ProxyIPCode, config.GeoSite, config.ProxySiteCode); err != nil { + log.Warn(err) + } + return r, nil +} + +func init() { + router.NewRouter = NewMixedRouter +} diff --git a/router/mixed_test.go b/router/mixed/mixed_test.go similarity index 85% rename from router/mixed_test.go rename to router/mixed/mixed_test.go index 367dddc..f2ee2c6 100644 --- a/router/mixed_test.go +++ b/router/mixed/mixed_test.go @@ -1,4 +1,4 @@ -package router +package mixed import ( "io/ioutil" @@ -9,17 +9,18 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/protocol" + "github.com/p4gefau1t/trojan-go/router" ) func TestSimpleMixedRouter(t *testing.T) { bypass := []byte("0.0.0.0/8\n10.0.0.0/8\n192.0.0.0/24\nbaidu.com\nqq.com\n") - r, err := NewMixedRouter(&conf.GlobalConfig{ - Router: conf.RouterConfig{ + r, err := NewMixedRouter( + &conf.RouterConfig{ BypassList: bypass, DefaultPolicy: "proxy", }, - }) + ) common.Must(err) p, err := r.RouteRequest(&protocol.Request{ Address: &common.Address{ @@ -28,7 +29,7 @@ func TestSimpleMixedRouter(t *testing.T) { }, }) common.Must(err) - if p != Bypass { + if p != router.Bypass { t.Fatal("wrong result") } @@ -39,7 +40,7 @@ func TestSimpleMixedRouter(t *testing.T) { }, }) common.Must(err) - if p != Proxy { + if p != router.Proxy { t.Fatal("wrong result") } @@ -50,7 +51,7 @@ func TestSimpleMixedRouter(t *testing.T) { }, }) common.Must(err) - if p != Bypass { + if p != router.Bypass { t.Fatal("wrong result") } @@ -61,7 +62,7 @@ func TestSimpleMixedRouter(t *testing.T) { }, }) common.Must(err) - if p != Bypass { + if p != router.Bypass { t.Fatal("wrong result") } @@ -72,7 +73,7 @@ func TestSimpleMixedRouter(t *testing.T) { }, }) common.Must(err) - if p != Proxy { + if p != router.Proxy { t.Fatal("wrong result") } } @@ -86,12 +87,12 @@ func TestMixedRouter(t *testing.T) { common.Must(err) bypass += string(buf) - r, err := NewMixedRouter(&conf.GlobalConfig{ - Router: conf.RouterConfig{ + r, err := NewMixedRouter( + &conf.RouterConfig{ BypassList: []byte(bypass), DefaultPolicy: "proxy", }, - }) + ) policy, err := r.RouteRequest(&protocol.Request{ Address: &common.Address{ @@ -99,7 +100,7 @@ func TestMixedRouter(t *testing.T) { DomainName: "baidu.com", }, }) - if policy != Bypass { + if policy != router.Bypass { log.Fatal("wrong result") } @@ -109,7 +110,7 @@ func TestMixedRouter(t *testing.T) { DomainName: "api.github.com", }, }) - if policy != Proxy { + if policy != router.Proxy { log.Fatal("wrong result") } } diff --git a/router/router.go b/router/router.go index d48977a..44052bd 100644 --- a/router/router.go +++ b/router/router.go @@ -1,6 +1,7 @@ package router import ( + "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/protocol" ) @@ -12,10 +13,22 @@ const ( Block Unknown - match - nonMatch + Match + NonMatch ) +type EmptyRouter struct{} + +func (r *EmptyRouter) RouteRequest(req *protocol.Request) (Policy, error) { + return Proxy, nil +} + type Router interface { RouteRequest(*protocol.Request) (Policy, error) } + +var NewRouter func(config *conf.RouterConfig) (Router, error) = NewEmptyRouter + +func NewEmptyRouter(*conf.RouterConfig) (Router, error) { + return &EmptyRouter{}, nil +} diff --git a/sockopt/darwin.go b/sockopt/darwin.go new file mode 100644 index 0000000..85cc4c2 --- /dev/null +++ b/sockopt/darwin.go @@ -0,0 +1,35 @@ +// +build darwin + +package sockopt + +import ( + "syscall" + + "github.com/p4gefau1t/trojan-go/conf" + "github.com/p4gefau1t/trojan-go/log" +) + +const ( + // TCP_FASTOPEN is the socket option on darwin for TCP fast open. + TCP_FASTOPEN = 0x105 + // TCP_FASTOPEN_SERVER is the value to enable TCP fast open on darwin for server connections. + TCP_FASTOPEN_SERVER = 0x01 + // TCP_FASTOPEN_CLIENT is the value to enable TCP fast open on darwin for client connections. + TCP_FASTOPEN_CLIENT = 0x02 +) + +func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { + if config.FastOpen { + if isInbound { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, TCP_FASTOPEN_SERVER); err != nil { + return err + } + } else { + //if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, TCP_FASTOPEN_CLIENT); err != nil { + //return err + //} + } + log.Debug("tcp fast open enabled") + } + return nil +} diff --git a/sockopt/linux.go b/sockopt/linux.go new file mode 100644 index 0000000..3fd6fc8 --- /dev/null +++ b/sockopt/linux.go @@ -0,0 +1,47 @@ +// +build linux + +package sockopt + +import ( + "syscall" + + "github.com/p4gefau1t/trojan-go/conf" + "github.com/p4gefau1t/trojan-go/log" + "golang.org/x/sys/unix" +) + +func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { + if config.ReusePort && isInbound { + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil { + return err + } + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil { + return err + } + log.Debug("port reusing enabled") + } + + if config.FastOpen { + if isInbound { + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN, config.FastOpenQLen); err != nil { + return err + } + } else { + //if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN_CONNECT, 1); err != nil { + //return err + //} + } + log.Debug("tcp fast open enabled") + } + /* + if config.Transparent && isInbound { + if err := syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { + return err + } + if err := syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1); err != nil { + return err + } + } + */ + return nil +} diff --git a/sockopt/other.go b/sockopt/other.go new file mode 100644 index 0000000..5699168 --- /dev/null +++ b/sockopt/other.go @@ -0,0 +1,17 @@ +// +build !linux +// +build !windows +// +build !darwin + +package sockopt + +import ( + "runtime" + + "github.com/p4gefau1t/trojan-go/conf" + "github.com/p4gefau1t/trojan-go/log" +) + +func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { + log.Warn("tcp options is ignored in this os:", runtime.GOOS) + return nil +} diff --git a/sockopt/sockopt.go b/sockopt/sockopt.go index fc5c134..654b95c 100644 --- a/sockopt/sockopt.go +++ b/sockopt/sockopt.go @@ -1 +1,35 @@ package sockopt + +import ( + "net" + + "github.com/p4gefau1t/trojan-go/conf" +) + +func ApplyTCPListenerOption(l *net.TCPListener, config *conf.TCPConfig) error { + rawConn, err := l.SyscallConn() + if err != nil { + return err + } + rawConn.Control(func(fd uintptr) { + err = ApplySocketOption(fd, config, true) + }) + return err +} + +func ApplyTCPConnOption(conn *net.TCPConn, config *conf.TCPConfig) error { + if err := conn.SetKeepAlive(config.KeepAlive); err != nil { + return err + } + if err := conn.SetNoDelay(config.NoDelay); err != nil { + return err + } + rawConn, err := conn.SyscallConn() + if err != nil { + return err + } + rawConn.Control(func(fd uintptr) { + err = ApplySocketOption(fd, config, false) + }) + return err +} diff --git a/sockopt/windows.go b/sockopt/windows.go new file mode 100644 index 0000000..ba13928 --- /dev/null +++ b/sockopt/windows.go @@ -0,0 +1,24 @@ +// +build windows + +package sockopt + +import ( + "syscall" + + "github.com/p4gefau1t/trojan-go/conf" + "github.com/p4gefau1t/trojan-go/log" +) + +const ( + TCP_FASTOPEN = 15 +) + +func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { + if config.FastOpen { + if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, 1); err != nil { + return err + } + log.Debug("tcp fast open enabled") + } + return nil +} diff --git a/test/proxy_test.go b/test/proxy_test.go index dbf1c31..a563023 100644 --- a/test/proxy_test.go +++ b/test/proxy_test.go @@ -171,6 +171,16 @@ func addRouterConfig(config *conf.GlobalConfig) *conf.GlobalConfig { return config } +func addTCPOption(config *conf.GlobalConfig) *conf.GlobalConfig { + config.TCP = conf.TCPConfig{ + KeepAlive: true, + FastOpen: true, + NoDelay: true, + FastOpenQLen: 5, + } + return config +} + func RunClient(ctx context.Context, config *conf.GlobalConfig) { c := client.Client{} common.Must2(c.Build(config)) @@ -314,10 +324,13 @@ func MultiThreadSpeedTestClientServer(b *testing.B, clientConfig *conf.GlobalCon } func TestIt(t *testing.T) { - clientConfig := getBasicClientConfig() - serverConfig := getBasicServerConfig() - go RunClient(context.Background(), clientConfig) - RunServer(context.Background(), serverConfig) + /* + clientConfig := getBasicClientConfig() + serverConfig := getBasicServerConfig() + go RunClient(context.Background(), clientConfig) + go RunHelloHTTPServer(context.Background()) + RunServer(context.Background(), serverConfig) + */ } func TestNormal(t *testing.T) { @@ -456,3 +469,9 @@ func TestAutoClientID(t *testing.T) { clientConfig.TLS.Fingerprint = "auto" CheckClientServer(t, clientConfig, serverConfig) } + +func TestTCPOptions(t *testing.T) { + serverConfig := addTCPOption(getBasicServerConfig()) + clientConfig := addTCPOption(getBasicClientConfig()) + CheckClientServer(t, clientConfig, serverConfig) +} diff --git a/test/target.go b/test/target.go index ae1d8d8..a129bb0 100644 --- a/test/target.go +++ b/test/target.go @@ -103,7 +103,7 @@ func RunHelloHTTPServer(ctx context.Context) { http.HandleFunc("/", httpHello) http.HandleFunc("/websocket", wsServer.ServeHTTP) server := http.Server{Addr: "127.0.0.1:10080"} - server.ListenAndServe() + go server.ListenAndServe() <-ctx.Done() server.Close() } diff --git a/version/version.go b/version/version.go index d5c6e5d..68055d3 100644 --- a/version/version.go +++ b/version/version.go @@ -26,6 +26,7 @@ func (c *versionOption) Handle() error { fmt.Println("Developed by PageFault(p4gefau1t)") fmt.Println("Lisensed under GNU General Public License v3") fmt.Println("GitHub Repository: https://github.com/p4gefau1t/trojan-go") + fmt.Println("Documents: https://p4gefau1t.github.io/trojan-go/") return nil } return common.NewError("not set")