mirror of
https://github.com/p4gefau1t/trojan-go.git
synced 2024-04-21 12:21:34 +00:00
add router, fix transport
This commit is contained in:
@@ -5,6 +5,7 @@ go 1.14
|
||||
require (
|
||||
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0
|
||||
github.com/smartystreets/goconvey v1.6.4
|
||||
@@ -15,6 +16,7 @@ require (
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
v2ray.com/core v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
replace v2ray.com/core => github.com/v2ray/v2ray-core v0.0.0-20200603100350-6b5d2fed91c0
|
||||
|
||||
@@ -1,30 +1,51 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4=
|
||||
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
|
||||
github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0 h1:vIkvetWOJZSADSKCF9MLTsQNW2httdBmYz47dQQteP8=
|
||||
github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/v2ray/v2ray-core v0.0.0-20200603100350-6b5d2fed91c0 h1:beJRvss6cKPj/Qy8RLI/O8EKYaxaKlsXBjsXgmNqSUQ=
|
||||
github.com/v2ray/v2ray-core v0.0.0-20200603100350-6b5d2fed91c0/go.mod h1:6qvbJidjCnQWxyTc9SBD/cLCtN4qLs2neS/VzwSTnTY=
|
||||
github.com/xtaci/smux v1.5.15-0.20200523091831-637399ad4398 h1:1nJafFt4SJPzJ5RbWBP2OUJ7Xcx7pdjyjldEdFrLfKs=
|
||||
github.com/xtaci/smux v1.5.15-0.20200523091831-637399ad4398/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||
go.starlark.net v0.0.0-20190919145610-979af19b165c h1:WR7X1xgXJlXhQBdorVc9Db3RhwG+J/kp6bLuMyJjfVw=
|
||||
go.starlark.net v0.0.0-20190919145610-979af19b165c/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed h1:g4KENRiCMEx58Q7/ecwfT0N2o8z35Fnbsjig/Alf2T4=
|
||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
|
||||
@@ -32,9 +53,17 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
h12.io/socks v1.0.0/go.mod h1:MdYbo5/eB9ka7u5dzW2Qh0iSyJENwB3KI5H5ngenFGA=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/p4gefau1t/trojan-go/proxy"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel/mux"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel/router"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel/simplesocks"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel/socks"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel/transport"
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
const Name = "CLIENT"
|
||||
|
||||
// GenerateClientTree generate general outbound protocol stack
|
||||
func GenerateClientTree(isMux bool, isWebsocket bool) []string {
|
||||
func GenerateClientTree(isMux bool, isWebsocket bool, isRouter bool) []string {
|
||||
clientStack := []string{transport.Name}
|
||||
if isWebsocket {
|
||||
clientStack = append(clientStack, websocket.Name)
|
||||
@@ -25,6 +26,9 @@ func GenerateClientTree(isMux bool, isWebsocket bool) []string {
|
||||
if isMux {
|
||||
clientStack = append(clientStack, []string{mux.Name, simplesocks.Name}...)
|
||||
}
|
||||
if isRouter {
|
||||
clientStack = append(clientStack, router.Name)
|
||||
}
|
||||
return clientStack
|
||||
}
|
||||
|
||||
@@ -32,7 +36,7 @@ func init() {
|
||||
proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {
|
||||
cfg := config.FromContext(ctx, Name).(*Config)
|
||||
serverStack := []string{socks.Name}
|
||||
clientStack := GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled)
|
||||
clientStack := GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Router.Enabled)
|
||||
c, err := proxy.CreateClientStack(ctx, clientStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -3,16 +3,21 @@ package client
|
||||
import "github.com/p4gefau1t/trojan-go/config"
|
||||
|
||||
type MuxConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
Enabled bool `json,yaml:"enabled"`
|
||||
}
|
||||
|
||||
type WebsocketConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
Enabled bool `json,yaml:"enabled"`
|
||||
}
|
||||
|
||||
type RouterConfig struct {
|
||||
Enabled bool `json,yaml:"enabled"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Mux MuxConfig `json:"mux" yaml:"mux"`
|
||||
Websocket WebsocketConfig `json:"websocket" yaml:"websocket"`
|
||||
Mux MuxConfig `json,yaml:"mux"`
|
||||
Websocket WebsocketConfig `json,yaml:"websocket"`
|
||||
Router RouterConfig `json,yaml:"router"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -15,7 +15,7 @@ func init() {
|
||||
proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {
|
||||
cfg := config.FromContext(ctx, Name).(*client.Config)
|
||||
serverStack := []string{dokodemo.Name}
|
||||
clientStack := client.GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled)
|
||||
clientStack := client.GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Router.Enabled)
|
||||
c, err := proxy.CreateClientStack(ctx, clientStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ func init() {
|
||||
proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {
|
||||
cfg := config.FromContext(ctx, Name).(*client.Config)
|
||||
serverStack := []string{tproxy.Name}
|
||||
clientStack := client.GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled)
|
||||
clientStack := client.GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled, false)
|
||||
c, err := proxy.CreateClientStack(ctx, clientStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package senario_test
|
||||
package scenario
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
+26
-5
@@ -2,25 +2,41 @@ package raw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/p4gefau1t/trojan-go/config"
|
||||
"net"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/tunnel"
|
||||
)
|
||||
|
||||
type Client struct{}
|
||||
type Client struct {
|
||||
preferIPv4 bool
|
||||
noDelay bool
|
||||
keepAlive bool
|
||||
}
|
||||
|
||||
func (c *Client) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) {
|
||||
tcpConn, err := net.Dial("tcp", addr.String())
|
||||
network := "tcp"
|
||||
if c.preferIPv4 {
|
||||
network = "tcp4"
|
||||
}
|
||||
tcpConn, err := net.Dial(network, addr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpConn.(*net.TCPConn).SetKeepAlive(c.keepAlive)
|
||||
tcpConn.(*net.TCPConn).SetNoDelay(c.noDelay)
|
||||
return &Conn{
|
||||
TCPConn: tcpConn.(*net.TCPConn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
|
||||
udpConn, err := net.ListenPacket("udp", "")
|
||||
network := "udp"
|
||||
if c.preferIPv4 {
|
||||
network = "udp4"
|
||||
}
|
||||
udpConn, err := net.ListenPacket(network, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,8 +49,13 @@ func (c *Client) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFreeClient(context.Context, tunnel.Client) (*Client, error) {
|
||||
return &Client{}, nil
|
||||
func NewFreeClient(ctx context.Context, client tunnel.Client) (*Client, error) {
|
||||
cfg := config.FromContext(ctx, Name).(*Config)
|
||||
return &Client{
|
||||
noDelay: cfg.TCP.NoDelay,
|
||||
keepAlive: cfg.TCP.KeepAlive,
|
||||
preferIPv4: cfg.TCP.PreferIPV4,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FixedClient will always dial to the FixedAddr
|
||||
|
||||
+21
-14
@@ -1,21 +1,28 @@
|
||||
package raw
|
||||
|
||||
import "github.com/p4gefau1t/trojan-go/config"
|
||||
|
||||
type Config struct {
|
||||
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"`
|
||||
DNS []string `json:"dns"`
|
||||
TCP TCPConfig `json:"tcp"`
|
||||
LocalHost string `json:"local_addr"`
|
||||
LocalPort int `json:"local_port"`
|
||||
DNS []string `json:"dns"`
|
||||
TCP TCPConfig `json:"tcp"`
|
||||
}
|
||||
|
||||
type TCPConfig struct {
|
||||
PreferIPV4 bool `json:"prefer_ipv4"`
|
||||
KeepAlive bool `json:"keep_alive"`
|
||||
FastOpen bool `json:"fast_open"`
|
||||
FastOpenQLen int `json:"fast_open_qlen"`
|
||||
ReusePort bool `json:"reuse_port"`
|
||||
NoDelay bool `json:"no_delay"`
|
||||
PreferIPV4 bool `json:"prefer_ipv4" yaml:"prefer-ipv4"`
|
||||
KeepAlive bool `json:"keep_alive" yaml:"keep-alive"`
|
||||
NoDelay bool `json:"no_delay" yaml:"no-delay"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
config.RegisterConfigCreator(Name, func() interface{} {
|
||||
return &Config{
|
||||
TCP: TCPConfig{
|
||||
PreferIPV4: false,
|
||||
NoDelay: true,
|
||||
KeepAlive: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1 +1,363 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/config"
|
||||
"github.com/p4gefau1t/trojan-go/log"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel/transport"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
v2router "v2ray.com/core/app/router"
|
||||
)
|
||||
|
||||
const (
|
||||
Proxy = 0
|
||||
Bypass = 1
|
||||
Block = 2
|
||||
)
|
||||
|
||||
const (
|
||||
AsIs = 0
|
||||
IPIfNonMatch = 1
|
||||
IPOnDemand = 2
|
||||
)
|
||||
|
||||
const MaxPacketSize = 1024 * 8
|
||||
|
||||
func matchDomain(list []*v2router.Domain, target string) bool {
|
||||
for _, d := range list {
|
||||
switch d.GetType() {
|
||||
case v2router.Domain_Full:
|
||||
domain := d.GetValue()
|
||||
if domain == target {
|
||||
log.Trace("domain:", target, "hit domain(full) rule:", domain)
|
||||
return true
|
||||
}
|
||||
case v2router.Domain_Domain:
|
||||
domain := d.GetValue()
|
||||
if strings.HasSuffix(target, domain) {
|
||||
idx := strings.Index(target, domain)
|
||||
if idx == 0 || target[idx-1] == '.' {
|
||||
log.Trace("domain:", target, "hit domain rule:", domain)
|
||||
return true
|
||||
}
|
||||
}
|
||||
case v2router.Domain_Plain:
|
||||
//keyword
|
||||
if strings.Contains(target, d.GetValue()) {
|
||||
log.Trace("domain:", target, "hit keyword rule:", d.GetValue())
|
||||
return true
|
||||
}
|
||||
case v2router.Domain_Regex:
|
||||
matched, err := regexp.Match(d.GetValue(), []byte(target))
|
||||
if err != nil {
|
||||
log.Error("invalid regex", d.GetValue())
|
||||
return false
|
||||
}
|
||||
if matched {
|
||||
log.Trace("domain:", target, "hit regex rule:", d.GetValue())
|
||||
return true
|
||||
}
|
||||
default:
|
||||
log.Debug("unknown rule type:" + d.GetType().String())
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchIP(list []*v2router.CIDR, target net.IP) bool {
|
||||
isIPv6 := true
|
||||
len := net.IPv6len
|
||||
if target.To4() != nil {
|
||||
len = net.IPv4len
|
||||
isIPv6 = false
|
||||
}
|
||||
for _, c := range list {
|
||||
n := int(c.GetPrefix())
|
||||
mask := net.CIDRMask(n, 8*len)
|
||||
cidrIP := net.IP(c.GetIp())
|
||||
if cidrIP.To4() != nil { //IPv4 CIDR
|
||||
if isIPv6 {
|
||||
continue
|
||||
}
|
||||
} else { //IPv6 CIDR
|
||||
if !isIPv6 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
subnet := &net.IPNet{IP: cidrIP.Mask(mask), Mask: mask}
|
||||
if subnet.Contains(target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newIPAddress(address *tunnel.Address) (*tunnel.Address, error) {
|
||||
ip, err := address.ResolveIP()
|
||||
if err != nil {
|
||||
return nil, common.NewError("router failed to resolve ip").Base(err)
|
||||
}
|
||||
newAddress := &tunnel.Address{
|
||||
IP: ip,
|
||||
Port: address.Port,
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
newAddress.AddressType = tunnel.IPv4
|
||||
} else {
|
||||
newAddress.AddressType = tunnel.IPv6
|
||||
}
|
||||
return newAddress, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
domains [3][]*v2router.Domain
|
||||
cidrs [3][]*v2router.CIDR
|
||||
defaultPolicy int
|
||||
domainStrategy int
|
||||
underlay tunnel.Client
|
||||
context.Context
|
||||
context.CancelFunc
|
||||
}
|
||||
|
||||
func (c *Client) Route(address *tunnel.Address) int {
|
||||
policy := -1
|
||||
var err error
|
||||
if c.defaultPolicy == IPOnDemand {
|
||||
address, err = newIPAddress(address)
|
||||
if err != nil {
|
||||
return c.defaultPolicy
|
||||
}
|
||||
}
|
||||
if address.AddressType == tunnel.DomainName {
|
||||
for i := 0; i < 3; i++ {
|
||||
if matchDomain(c.domains[i], address.DomainName) {
|
||||
policy = i
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < 3; i++ {
|
||||
if matchIP(c.cidrs[i], address.IP) {
|
||||
policy = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if policy == -1 && c.domainStrategy == IPIfNonMatch {
|
||||
address, err = newIPAddress(address)
|
||||
if err != nil {
|
||||
return c.defaultPolicy
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if matchIP(c.cidrs[i], address.IP) {
|
||||
policy = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if policy == -1 {
|
||||
policy = c.defaultPolicy
|
||||
}
|
||||
return policy
|
||||
}
|
||||
|
||||
func (c *Client) DialConn(address *tunnel.Address, overlay tunnel.Tunnel) (tunnel.Conn, error) {
|
||||
policy := c.Route(address)
|
||||
switch policy {
|
||||
case Proxy:
|
||||
return c.underlay.DialConn(address, overlay)
|
||||
case Block:
|
||||
return nil, common.NewError("router blocked address: " + address.String())
|
||||
case Bypass:
|
||||
conn, err := net.Dial("tcp", address.String())
|
||||
if err != nil {
|
||||
return nil, common.NewError("router dial error").Base(err)
|
||||
}
|
||||
return &transport.Conn{
|
||||
Conn: conn,
|
||||
}, nil
|
||||
}
|
||||
panic("unknown policy")
|
||||
}
|
||||
|
||||
func (c *Client) DialPacket(overlay tunnel.Tunnel) (tunnel.PacketConn, error) {
|
||||
direct, err := net.ListenPacket("udp", "")
|
||||
if err != nil {
|
||||
return nil, common.NewError("router failed to dial udp (direct)").Base(err)
|
||||
}
|
||||
proxy, err := c.underlay.DialPacket(overlay)
|
||||
if err != nil {
|
||||
return nil, common.NewError("router failed to dial udp (proxy)").Base(err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(c.Context)
|
||||
return &PacketConn{
|
||||
PacketConn: direct,
|
||||
proxy: proxy,
|
||||
CancelFunc: cancel,
|
||||
Context: ctx,
|
||||
packetChan: make(chan *packetInfo, 16),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.CancelFunc()
|
||||
return c.underlay.Close()
|
||||
}
|
||||
|
||||
type codeInfo struct {
|
||||
code string
|
||||
strategy int
|
||||
}
|
||||
|
||||
func loadCode(cfg *Config, prefix string) []codeInfo {
|
||||
codes := []codeInfo{}
|
||||
for _, s := range cfg.Router.Proxy {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
codes = append(codes, codeInfo{
|
||||
code: s[len(prefix):],
|
||||
strategy: Proxy,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, s := range cfg.Router.Bypass {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
codes = append(codes, codeInfo{
|
||||
code: s[len(prefix):],
|
||||
strategy: Bypass,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, s := range cfg.Router.Block {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
codes = append(codes, codeInfo{
|
||||
code: s[len(prefix):],
|
||||
strategy: Block,
|
||||
})
|
||||
}
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) {
|
||||
cfg := config.FromContext(ctx, Name).(*Config)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
client := &Client{
|
||||
domains: [3][]*v2router.Domain{},
|
||||
cidrs: [3][]*v2router.CIDR{},
|
||||
underlay: underlay,
|
||||
Context: ctx,
|
||||
CancelFunc: cancel,
|
||||
}
|
||||
switch cfg.Router.DomainStrategy {
|
||||
case "as_is":
|
||||
client.domainStrategy = AsIs
|
||||
case "ip_if_non_match":
|
||||
client.domainStrategy = IPIfNonMatch
|
||||
case "ip_on_demand":
|
||||
client.domainStrategy = IPOnDemand
|
||||
default:
|
||||
return nil, common.NewError("unknown strategy: " + cfg.Router.DomainStrategy)
|
||||
}
|
||||
|
||||
switch cfg.Router.DefaultPolicy {
|
||||
case "proxy":
|
||||
client.defaultPolicy = Proxy
|
||||
case "bypass":
|
||||
client.defaultPolicy = Bypass
|
||||
case "block":
|
||||
client.defaultPolicy = Block
|
||||
return nil, common.NewError("unknown strategy: " + cfg.Router.DomainStrategy)
|
||||
}
|
||||
|
||||
geoipData, err := ioutil.ReadFile(cfg.Router.GeoIPFilename)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
} else {
|
||||
geoip := new(v2router.GeoIPList)
|
||||
if err := proto.Unmarshal(geoipData, geoip); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipCode := loadCode(cfg, "geoip:")
|
||||
for _, c := range ipCode {
|
||||
c.code = strings.ToUpper(c.code)
|
||||
found := false
|
||||
for _, e := range geoip.GetEntry() {
|
||||
code := e.GetCountryCode()
|
||||
if c.code == code {
|
||||
client.cidrs[c.strategy] = append(client.cidrs[c.strategy], e.GetCidr()...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
log.Info("geoip info", c, "loaded")
|
||||
} else {
|
||||
log.Warn("geoip info", c, "not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geositeData, err := ioutil.ReadFile(cfg.Router.GeoSiteFilename)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
} else {
|
||||
geosite := new(v2router.GeoSiteList)
|
||||
if err := proto.Unmarshal(geositeData, geosite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
siteCode := loadCode(cfg, "geosite:")
|
||||
for _, c := range siteCode {
|
||||
c.code = strings.ToUpper(c.code)
|
||||
found := false
|
||||
for _, e := range geosite.GetEntry() {
|
||||
code := e.GetCountryCode()
|
||||
if c.code == code {
|
||||
client.domains[c.strategy] = append(client.domains[c.strategy], e.GetDomain()...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
log.Info("geosite info", c, "loaded")
|
||||
} else {
|
||||
log.Warn("geosite info", c, "not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
domainInfo := loadCode(cfg, "domain:")
|
||||
for _, info := range domainInfo {
|
||||
client.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{
|
||||
Type: v2router.Domain_Domain,
|
||||
Value: info.code,
|
||||
Attribute: nil,
|
||||
})
|
||||
}
|
||||
|
||||
regexInfo := loadCode(cfg, "regex:")
|
||||
for _, info := range regexInfo {
|
||||
client.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{
|
||||
Type: v2router.Domain_Regex,
|
||||
Value: info.code,
|
||||
Attribute: nil,
|
||||
})
|
||||
}
|
||||
|
||||
fullInfo := loadCode(cfg, "full:")
|
||||
for _, info := range fullInfo {
|
||||
client.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{
|
||||
Type: v2router.Domain_Regex,
|
||||
Value: info.code,
|
||||
Attribute: nil,
|
||||
})
|
||||
}
|
||||
|
||||
log.Info("router client created")
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -1 +1,33 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/p4gefau1t/trojan-go/config"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Router RouterConfig `json,yaml:"router"`
|
||||
}
|
||||
|
||||
type RouterConfig struct {
|
||||
Enabled bool `json,yaml:"enabled"`
|
||||
Bypass []string `json,yaml:"bypass"`
|
||||
Proxy []string `json,yaml:"proxy"`
|
||||
Block []string `json,yaml:"block"`
|
||||
DomainStrategy string `json:"domain_strategy" yaml:"domain-strategy"`
|
||||
DefaultPolicy string `json:"default_policy" yaml:"default-policy"`
|
||||
GeoIPFilename string `json,yaml:"geoip"`
|
||||
GeoSiteFilename string `json,yaml:"geosite"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
config.RegisterConfigCreator(Name, func() interface{} {
|
||||
return &Config{
|
||||
Router: RouterConfig{
|
||||
DefaultPolicy: "proxy",
|
||||
DomainStrategy: "as_is",
|
||||
GeoIPFilename: "geoip.dat",
|
||||
GeoSiteFilename: "geosite.dat",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/log"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type packetInfo struct {
|
||||
src *tunnel.Metadata
|
||||
payload []byte
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
proxy tunnel.PacketConn
|
||||
net.PacketConn
|
||||
packetChan chan *packetInfo
|
||||
*Client
|
||||
context.Context
|
||||
context.CancelFunc
|
||||
}
|
||||
|
||||
func (c *PacketConn) packetLoop() {
|
||||
go func() {
|
||||
for {
|
||||
buf := make([]byte, MaxPacketSize)
|
||||
n, addr, err := c.proxy.ReadWithMetadata(buf)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return
|
||||
default:
|
||||
log.Error("router packetConn error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
c.packetChan <- &packetInfo{
|
||||
src: addr,
|
||||
payload: buf[:n],
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
buf := make([]byte, MaxPacketSize)
|
||||
n, addr, err := c.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return
|
||||
default:
|
||||
log.Error("router packetConn error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
address, err := tunnel.NewAddressFromAddr("udp", addr.String())
|
||||
c.packetChan <- &packetInfo{
|
||||
src: &tunnel.Metadata{
|
||||
Address: address,
|
||||
},
|
||||
payload: buf[:n],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
c.CancelFunc()
|
||||
c.proxy.Close()
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) {
|
||||
policy := c.Route(m.Address)
|
||||
switch policy {
|
||||
case Proxy:
|
||||
return c.proxy.WriteWithMetadata(p, m)
|
||||
case Block:
|
||||
return 0, common.NewError("router blocked address (udp): " + m.Address.String())
|
||||
case Bypass:
|
||||
ip, err := m.Address.ResolveIP()
|
||||
if err != nil {
|
||||
return 0, common.NewError("router failed to resolve udp address").Base(err)
|
||||
}
|
||||
return c.PacketConn.WriteTo(p, &net.UDPAddr{
|
||||
IP: ip,
|
||||
Port: m.Address.Port,
|
||||
})
|
||||
default:
|
||||
panic("unknown policy")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) {
|
||||
select {
|
||||
case info := <-c.packetChan:
|
||||
n := copy(p, info.payload)
|
||||
return n, info.src, nil
|
||||
case <-c.Done():
|
||||
return 0, nil, io.EOF
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package router
|
||||
@@ -0,0 +1,93 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/config"
|
||||
"github.com/p4gefau1t/trojan-go/test/util"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MockClient struct{}
|
||||
|
||||
func (m *MockClient) DialConn(address *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) {
|
||||
return nil, common.NewError("mockproxy")
|
||||
}
|
||||
|
||||
func (m *MockClient) DialPacket(t tunnel.Tunnel) (tunnel.PacketConn, error) {
|
||||
return nil, common.NewError("mockproxy")
|
||||
}
|
||||
|
||||
func (m MockClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRouter(t *testing.T) {
|
||||
data := `
|
||||
router:
|
||||
enabled: true
|
||||
bypass:
|
||||
- "regex:bypassreg(.*)"
|
||||
- "full:bypassfull"
|
||||
- "full:localhost"
|
||||
- "domain:bypass.com"
|
||||
block:
|
||||
- "regex:blockreg(.*)"
|
||||
- "full:blockfull"
|
||||
- "domain:block.com"
|
||||
proxy:
|
||||
- "regex:proxyreg(.*)"
|
||||
- "full:proxyfull"
|
||||
- "domain:proxy.com"
|
||||
`
|
||||
ctx, err := config.WithYAMLConfig(context.Background(), []byte(data))
|
||||
common.Must(err)
|
||||
client, err := NewClient(ctx, &MockClient{})
|
||||
common.Must(err)
|
||||
_, err = client.DialConn(&tunnel.Address{
|
||||
AddressType: tunnel.DomainName,
|
||||
DomainName: "proxy.com",
|
||||
Port: 80,
|
||||
}, nil)
|
||||
if err.Error() != "mockproxy" {
|
||||
t.Fail()
|
||||
}
|
||||
_, err = client.DialConn(&tunnel.Address{
|
||||
AddressType: tunnel.DomainName,
|
||||
DomainName: "proxyreg123456",
|
||||
Port: 80,
|
||||
}, nil)
|
||||
if err.Error() != "mockproxy" {
|
||||
t.Fail()
|
||||
}
|
||||
_, err = client.DialConn(&tunnel.Address{
|
||||
AddressType: tunnel.DomainName,
|
||||
DomainName: "proxyfull",
|
||||
Port: 80,
|
||||
}, nil)
|
||||
if err.Error() != "mockproxy" {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
_, err = client.DialConn(&tunnel.Address{
|
||||
AddressType: tunnel.DomainName,
|
||||
DomainName: "block.com",
|
||||
Port: 80,
|
||||
}, nil)
|
||||
if !strings.Contains(err.Error(), "block") {
|
||||
t.Fail()
|
||||
}
|
||||
port, err := strconv.Atoi(util.HTTPPort)
|
||||
common.Must(err)
|
||||
_, err = client.DialConn(&tunnel.Address{
|
||||
AddressType: tunnel.DomainName,
|
||||
DomainName: "localhost",
|
||||
Port: port,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,13 @@ func (t *Tunnel) Name() string {
|
||||
}
|
||||
|
||||
func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {
|
||||
panic("implement me")
|
||||
return NewClient(ctx, client)
|
||||
}
|
||||
|
||||
func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func init() {
|
||||
tunnel.RegisterTunnel(Name, &Tunnel{})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {
|
||||
if err != nil {
|
||||
return nil, common.NewError("tproxy failed to accept connection").Base(err)
|
||||
}
|
||||
addr, err := getOriginalTCPDest(conn.(*net.TCPConn))
|
||||
addr, err := getOriginalTCPDest(conn.(*tproxy.Conn).TCPConn)
|
||||
if err != nil {
|
||||
return nil, common.NewError("tproxy failed to obtain original address of tcp socket").Base(err)
|
||||
}
|
||||
|
||||
@@ -20,3 +20,7 @@ func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Cl
|
||||
func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {
|
||||
return NewServer(ctx, server)
|
||||
}
|
||||
|
||||
func init() {
|
||||
tunnel.RegisterTunnel(Name, &Tunnel{})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
@@ -31,10 +34,18 @@ type Client struct {
|
||||
fingerprint string
|
||||
keyLogger io.WriteCloser
|
||||
websocket bool
|
||||
plugin bool
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return c.keyLogger.Close()
|
||||
if c.cmd != nil {
|
||||
c.cmd.Process.Kill()
|
||||
}
|
||||
if c.keyLogger != nil {
|
||||
c.keyLogger.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
|
||||
@@ -43,6 +54,15 @@ func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
|
||||
|
||||
// DialConn implements tunnel.Client. It will ignore the params and directly dial to remote server
|
||||
func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) {
|
||||
if c.plugin {
|
||||
conn, err := net.Dial("tcp", c.serverAddress.String())
|
||||
if err != nil {
|
||||
return nil, common.NewError("transport failed to connect to plugin")
|
||||
}
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
}, nil
|
||||
}
|
||||
if c.fingerprint != "" {
|
||||
tcpConn, err := net.Dial("tcp", c.serverAddress.String())
|
||||
if err != nil {
|
||||
@@ -89,6 +109,51 @@ func NewClient(ctx context.Context, c tunnel.Client) (*Client, error) {
|
||||
cfg := config.FromContext(ctx, Name).(*Config)
|
||||
serverAddress := tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort)
|
||||
|
||||
if cfg.TransportPlugin.Enabled {
|
||||
var cmd *exec.Cmd
|
||||
log.Warn("trojan-go will use transport plugin and work in plain text mode")
|
||||
switch cfg.TransportPlugin.Type {
|
||||
case "plaintext":
|
||||
// do nothing
|
||||
case "shadowsocks":
|
||||
pluginHost := "127.0.0.1"
|
||||
pluginPort := common.PickPort("tcp", pluginHost)
|
||||
cfg.TransportPlugin.Env = append(
|
||||
cfg.TransportPlugin.Env,
|
||||
"SS_LOCAL_HOST="+pluginHost,
|
||||
"SS_LOCAL_PORT="+strconv.FormatInt(int64(pluginPort), 10),
|
||||
"SS_REMOTE_HOST="+cfg.RemoteHost,
|
||||
"SS_REMOTE_PORT="+strconv.FormatInt(int64(cfg.RemotePort), 10),
|
||||
"SS_PLUGIN_OPTIONS="+cfg.TransportPlugin.PluginOption,
|
||||
)
|
||||
cfg.RemoteHost = pluginHost
|
||||
cfg.RemotePort = pluginPort
|
||||
serverAddress = tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort)
|
||||
log.Debug("plugin address", serverAddress.String())
|
||||
log.Debug("plugin env", cfg.TransportPlugin.Env)
|
||||
|
||||
cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)
|
||||
cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stdout
|
||||
cmd.Start()
|
||||
case "other":
|
||||
cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)
|
||||
cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stdout
|
||||
cmd.Start()
|
||||
default:
|
||||
return nil, common.NewError("invalid plugin type: " + cfg.TransportPlugin.Type)
|
||||
}
|
||||
client := &Client{
|
||||
serverAddress: serverAddress,
|
||||
cmd: cmd,
|
||||
plugin: true,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
if cfg.TLS.Fingerprint != "" {
|
||||
_, err := fingerprint.GetClientHelloSpec(cfg.TLS.Fingerprint, cfg.Websocket.Enabled)
|
||||
if err != nil {
|
||||
|
||||
+81
-12
@@ -12,6 +12,8 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
@@ -40,13 +42,20 @@ type Server struct {
|
||||
redir *redirector.Redirector
|
||||
connChan chan tunnel.Conn
|
||||
wsChan chan tunnel.Conn
|
||||
plugin bool
|
||||
cmd *exec.Cmd
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
s.cancel()
|
||||
s.keyLogger.Close()
|
||||
if s.keyLogger != nil {
|
||||
s.keyLogger.Close()
|
||||
}
|
||||
if s.cmd != nil {
|
||||
s.cmd.Process.Kill()
|
||||
}
|
||||
return s.tcpListener.Close()
|
||||
}
|
||||
|
||||
@@ -59,6 +68,12 @@ func (s *Server) acceptLoop() {
|
||||
return
|
||||
}
|
||||
go func(tcpConn net.Conn) {
|
||||
if s.plugin {
|
||||
s.connChan <- &Conn{
|
||||
Conn: tcpConn,
|
||||
}
|
||||
return
|
||||
}
|
||||
sniVerified := true
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: s.keyPair,
|
||||
@@ -165,16 +180,7 @@ func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
|
||||
// NewServer creates a transport layer server
|
||||
func NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) {
|
||||
cfg := config.FromContext(ctx, Name).(*Config)
|
||||
|
||||
if cfg.TLS.FallbackHost == "" {
|
||||
cfg.TLS.FallbackHost = cfg.RemoteHost
|
||||
log.Warn("empty fallback address")
|
||||
}
|
||||
if cfg.TLS.FallbackPort == 0 {
|
||||
cfg.TLS.FallbackPort = cfg.RemotePort
|
||||
log.Warn("empty fallback port")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
listenAddress := tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort)
|
||||
fallbackAddress := tunnel.NewAddressFromHostPort("tcp", cfg.TLS.FallbackHost, cfg.TLS.FallbackPort)
|
||||
if cfg.TLS.FallbackPort != 0 {
|
||||
@@ -185,11 +191,74 @@ func NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) {
|
||||
fallbackConn.Close()
|
||||
}
|
||||
|
||||
if cfg.TransportPlugin.Enabled {
|
||||
log.Warn("transport server will use transport plugin and work in plain text mode")
|
||||
var cmd *exec.Cmd
|
||||
switch cfg.TransportPlugin.Type {
|
||||
case "shadowsocks":
|
||||
trojanHost := "127.0.0.1"
|
||||
trojanPort := common.PickPort("tcp", trojanHost)
|
||||
cfg.TransportPlugin.Env = append(
|
||||
cfg.TransportPlugin.Env,
|
||||
"SS_REMOTE_HOST="+cfg.LocalHost,
|
||||
"SS_REMOTE_PORT="+strconv.FormatInt(int64(cfg.LocalPort), 10),
|
||||
"SS_LOCAL_HOST="+trojanHost,
|
||||
"SS_LOCAL_PORT="+strconv.FormatInt(int64(trojanPort), 10),
|
||||
"SS_PLUGIN_OPTIONS="+cfg.TransportPlugin.PluginOption,
|
||||
)
|
||||
|
||||
cfg.LocalHost = trojanHost
|
||||
cfg.LocalPort = trojanPort
|
||||
listenAddress = tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort)
|
||||
log.Debug("new listen address", listenAddress)
|
||||
log.Debug("plugin env", cfg.TransportPlugin.Env)
|
||||
|
||||
cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)
|
||||
cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stdout
|
||||
cmd.Start()
|
||||
case "other":
|
||||
cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)
|
||||
cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stdout
|
||||
cmd.Start()
|
||||
case "plaintext":
|
||||
// do nothing
|
||||
default:
|
||||
return nil, common.NewError("invalid plugin type: " + cfg.TransportPlugin.Type)
|
||||
}
|
||||
tcpListener, err := net.Listen("tcp", listenAddress.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := &Server{
|
||||
connChan: make(chan tunnel.Conn, 32),
|
||||
tcpListener: tcpListener,
|
||||
redir: redirector.NewRedirector(ctx),
|
||||
cmd: cmd,
|
||||
plugin: true,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
go server.acceptLoop()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
if cfg.TLS.FallbackHost == "" {
|
||||
cfg.TLS.FallbackHost = cfg.RemoteHost
|
||||
log.Warn("empty fallback address")
|
||||
}
|
||||
if cfg.TLS.FallbackPort == 0 {
|
||||
cfg.TLS.FallbackPort = cfg.RemotePort
|
||||
log.Warn("empty fallback port")
|
||||
}
|
||||
|
||||
tcpListener, err := net.Listen("tcp", listenAddress.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
server := &Server{
|
||||
fallbackAddress: fallbackAddress,
|
||||
redir: redirector.NewRedirector(ctx),
|
||||
|
||||
@@ -112,3 +112,59 @@ func TestTLS(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
serverCfg := &Config{
|
||||
LocalHost: "127.0.0.1",
|
||||
LocalPort: common.PickPort("tcp", "127.0.0.1"),
|
||||
RemoteHost: "127.0.0.1",
|
||||
RemotePort: common.PickPort("tcp", "127.0.0.1"),
|
||||
TLS: TLSConfig{
|
||||
KeyBytes: []byte(key),
|
||||
CertBytes: []byte(cert),
|
||||
},
|
||||
TransportPlugin: TransportPluginConfig{
|
||||
Enabled: true,
|
||||
Type: "plaintext",
|
||||
},
|
||||
}
|
||||
clientCfg := &Config{
|
||||
LocalHost: "127.0.0.1",
|
||||
LocalPort: common.PickPort("tcp", "127.0.0.1"),
|
||||
RemoteHost: "127.0.0.1",
|
||||
RemotePort: serverCfg.LocalPort,
|
||||
TLS: TLSConfig{
|
||||
Verify: false,
|
||||
},
|
||||
TransportPlugin: TransportPluginConfig{
|
||||
Enabled: true,
|
||||
Type: "plaintext",
|
||||
},
|
||||
}
|
||||
sctx := config.WithConfig(context.Background(), Name, serverCfg)
|
||||
cctx := config.WithConfig(context.Background(), Name, clientCfg)
|
||||
|
||||
s, err := NewServer(sctx, nil)
|
||||
common.Must(err)
|
||||
c, err := NewClient(cctx, nil)
|
||||
common.Must(err)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
var conn1, conn2 net.Conn
|
||||
go func() {
|
||||
conn2, err = s.AcceptConn(nil)
|
||||
common.Must(err)
|
||||
wg.Done()
|
||||
}()
|
||||
conn1, err = c.DialConn(nil, nil)
|
||||
common.Must(err)
|
||||
|
||||
common.Must2(conn1.Write([]byte("12345678\r\n")))
|
||||
wg.Wait()
|
||||
buf := [10]byte{}
|
||||
conn2.Read(buf[:])
|
||||
if !util.CheckConn(conn1, conn2) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
+18
-13
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"github.com/p4gefau1t/trojan-go/tunnel/mux"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/log"
|
||||
@@ -36,7 +37,7 @@ func (c *OutboundConn) Metadata() *tunnel.Metadata {
|
||||
return c.metadata
|
||||
}
|
||||
|
||||
func (c *OutboundConn) WriteHeader() error {
|
||||
func (c *OutboundConn) WriteHeader(payload []byte) error {
|
||||
if !c.headerWritten {
|
||||
users := c.auth.ListUsers()
|
||||
if len(users) == 0 {
|
||||
@@ -45,31 +46,29 @@ func (c *OutboundConn) WriteHeader() error {
|
||||
user := users[0]
|
||||
hash := user.Hash()
|
||||
c.user = user
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 128))
|
||||
buf := bytes.NewBuffer(make([]byte, 0, MaxPacketSize))
|
||||
crlf := []byte{0x0d, 0x0a}
|
||||
buf.Write([]byte(hash))
|
||||
buf.Write(crlf)
|
||||
c.metadata.WriteTo(buf)
|
||||
buf.Write(crlf)
|
||||
if payload != nil {
|
||||
buf.Write(payload)
|
||||
}
|
||||
_, err := c.Conn.Write(buf.Bytes())
|
||||
c.headerWritten = true
|
||||
return err
|
||||
/*
|
||||
// stick the payload after the trojan request header
|
||||
_, err := c.Conn.Write(append(buf.Bytes(), p...))
|
||||
c.meter.AddTraffic(len(p)+len(buf.Bytes()), 0)
|
||||
c.sent += uint64(len(p) + len(buf.Bytes()))
|
||||
c.headerWritten = true
|
||||
log.Debug("trojan header and payload flushed")
|
||||
return len(p), err
|
||||
*/
|
||||
}
|
||||
return common.NewError("header is already written")
|
||||
}
|
||||
|
||||
func (c *OutboundConn) Write(p []byte) (int, error) {
|
||||
if !c.headerWritten {
|
||||
c.WriteHeader()
|
||||
err := c.WriteHeader(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
n, err := c.Conn.Write(p)
|
||||
c.user.AddTraffic(n, 0)
|
||||
@@ -115,7 +114,13 @@ func (c *Client) DialConn(addr *tunnel.Address, overlay tunnel.Tunnel) (tunnel.C
|
||||
if _, ok := overlay.(*mux.Tunnel); ok {
|
||||
newConn.metadata.Command = Mux
|
||||
}
|
||||
newConn.WriteHeader()
|
||||
|
||||
go func(newConn *OutboundConn) {
|
||||
// if the trojan header is still buffered after 100 ms, the client may expect data from the server
|
||||
// so we flush the trojan header
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
newConn.WriteHeader(nil)
|
||||
}(newConn)
|
||||
return newConn, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user