add router, fix transport

This commit is contained in:
Page Fault
2020-06-10 08:51:29 +00:00
parent a3ee9af30b
commit 38b30b00f5
21 changed files with 926 additions and 56 deletions
+2
View File
@@ -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
+29
View File
@@ -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 -2
View File
@@ -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
+9 -4
View File
@@ -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() {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
},
}
})
}
+362
View File
@@ -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
}
+32
View File
@@ -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",
},
}
})
}
+111
View File
@@ -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
}
}
+1
View File
@@ -0,0 +1 @@
package router
+93
View File
@@ -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()
}
}
+5 -1
View File
@@ -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{})
}
+1 -1
View File
@@ -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)
}
+4
View File
@@ -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{})
}
+66 -1
View File
@@ -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
View File
@@ -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),
+56
View File
@@ -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
View File
@@ -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
}