diff --git a/build-all.sh b/build-all.sh index 35abca3b0..d13a1a4a3 100755 --- a/build-all.sh +++ b/build-all.sh @@ -43,7 +43,7 @@ for PLATFORM in $PLATFORMS; do GOOS=${PLATFORM%/*} GOARCH=${PLATFORM#*/} ZIP_FILENAME="trojan-go-${GOOS}-${GOARCH}.zip" - CMD="CGO_ENABLE=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -tags \"full\" -o temp -ldflags=\"-s -w ${VAR_SETTING}\"" + CMD="CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -tags \"full\" -o temp -ldflags=\"-s -w ${VAR_SETTING}\"" echo "${CMD}" eval $CMD || FAILURES="${FAILURES} ${PLATFORM}" zip -j release/$ZIP_FILENAME temp/* ./*.dat @@ -59,7 +59,7 @@ for GOOS in $PLATFORMS_ARM; do GOARCH="arm" for GOARM in 7 6 5; do ZIP_FILENAME="trojan-go-${GOOS}-${GOARCH}v${GOARM}.zip" - CMD="CGO_ENABLE=0 GOARM=${GOARM} GOOS=${GOOS} GOARCH=${GOARCH} go build -tags \"full\" -o temp -ldflags \"-s -w ${VAR_SETTING}\"" + CMD="CGO_ENABLED=0 GOARM=${GOARM} GOOS=${GOOS} GOARCH=${GOARCH} go build -tags \"full\" -o temp -ldflags \"-s -w ${VAR_SETTING}\"" echo "${CMD}" eval "${CMD}" || FAILURES="${FAILURES} ${GOOS}/${GOARCH}v${GOARM}" zip -j release/$ZIP_FILENAME temp/* ./*.dat diff --git a/build/custom.go b/build/custom.go new file mode 100644 index 000000000..1b7821517 --- /dev/null +++ b/build/custom.go @@ -0,0 +1,18 @@ +// +build custom full + +package build + +import ( + _ "github.com/p4gefau1t/trojan-go/proxy/custom" + _ "github.com/p4gefau1t/trojan-go/tunnel/adapter" + _ "github.com/p4gefau1t/trojan-go/tunnel/http" + _ "github.com/p4gefau1t/trojan-go/tunnel/mux" + _ "github.com/p4gefau1t/trojan-go/tunnel/router" + _ "github.com/p4gefau1t/trojan-go/tunnel/shadowsocks" + _ "github.com/p4gefau1t/trojan-go/tunnel/simplesocks" + _ "github.com/p4gefau1t/trojan-go/tunnel/socks" + _ "github.com/p4gefau1t/trojan-go/tunnel/tls" + _ "github.com/p4gefau1t/trojan-go/tunnel/transport" + _ "github.com/p4gefau1t/trojan-go/tunnel/trojan" + _ "github.com/p4gefau1t/trojan-go/tunnel/websocket" +) diff --git a/config/config_test.go b/config/config_test.go index 3f1ffc01a..70b913fdb 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -7,11 +7,17 @@ import ( "github.com/p4gefau1t/trojan-go/common" ) -type TestStruct struct { +type Foo struct { Field1 string `json,yaml:"field1"` Field2 bool `json:"field2" yaml:"field2"` } +type TestStruct struct { + Field1 string `json,yaml:"field1"` + Field2 bool `json,yaml:"field2"` + Field3 []Foo `json,yaml:"field3"` +} + func creator() interface{} { return &TestStruct{} } @@ -20,8 +26,14 @@ func TestJSONConfig(t *testing.T) { RegisterConfigCreator("test", creator) data := []byte(` { - "Field1": "test1", - "Field2": true + "field1": "test1", + "field2": true, + "field3": [ + { + "field1": "aaaa", + "field2": true + } + ] } `) ctx, err := WithJSONConfig(context.Background(), data) @@ -37,11 +49,14 @@ func TestYAMLConfig(t *testing.T) { data := []byte(` field1: 012345678 field2: true +field3: + - field1: test + field2: true `) ctx, err := WithYAMLConfig(context.Background(), data) common.Must(err) c := FromContext(ctx, "test").(*TestStruct) - if c.Field1 != "012345678" || c.Field2 != true { + if c.Field1 != "012345678" || c.Field2 != true || c.Field3[0].Field1 != "test" { t.Fail() } } diff --git a/proxy/custom/config.go b/proxy/custom/config.go index 08181287e..334daaf53 100644 --- a/proxy/custom/config.go +++ b/proxy/custom/config.go @@ -4,15 +4,15 @@ import "github.com/p4gefau1t/trojan-go/config" const Name = "CUSTOM" -type Node struct { - Protocol string - Tag string - Config interface{} +type NodeConfig struct { + Protocol string `json,yaml:"protocol"` + Tag string `json,yaml:"tag"` + Config interface{} `json,yaml:"config"` } type StackConfig struct { - NodeList []Node `json,yaml:"node"` - Path [][]string `json,yaml:"path"` + Path [][]string `json,yaml:"path"` + Node []NodeConfig `json,yaml:"node"` } type Config struct { diff --git a/proxy/custom/custom.go b/proxy/custom/custom.go index 85544860c..4245b4334 100644 --- a/proxy/custom/custom.go +++ b/proxy/custom/custom.go @@ -1 +1,126 @@ package custom + +import ( + "context" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/proxy" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/freedom" + "github.com/p4gefau1t/trojan-go/tunnel/http" + "github.com/p4gefau1t/trojan-go/tunnel/simplesocks" + "github.com/p4gefau1t/trojan-go/tunnel/socks" + "github.com/p4gefau1t/trojan-go/tunnel/transport" + "github.com/p4gefau1t/trojan-go/tunnel/trojan" + "gopkg.in/yaml.v2" + "strings" +) + +func buildNodes(ctx context.Context, nodeConfigList []NodeConfig) (map[string]*proxy.Node, *proxy.Node, error) { + nodes := make(map[string]*proxy.Node) + var root *proxy.Node + for _, nodeCfg := range nodeConfigList { + nodeCfg.Protocol = strings.ToUpper(nodeCfg.Protocol) + if _, err := tunnel.GetTunnel(nodeCfg.Protocol); err != nil { + return nil, nil, common.NewError("invalid protocol name:" + nodeCfg.Protocol) + } + data, err := yaml.Marshal(nodeCfg.Config) + if err != nil { + return nil, nil, common.NewError("failed to parse config data for " + nodeCfg.Tag + " with protocol" + nodeCfg.Protocol).Base(err) + } + nodeContext, err := config.WithYAMLConfig(ctx, data) + node := &proxy.Node{ + Name: nodeCfg.Protocol, + Next: make(map[string]*proxy.Node), + Context: nodeContext, + } + nodes[nodeCfg.Tag] = node + if nodeCfg.Protocol == transport.Name || nodeCfg.Protocol == freedom.Name { + if root != nil { + return nil, nil, common.NewError("transport layer is defined for twice") + } + log.Debug("root found:" + nodeCfg.Tag) + root = node + } + } + if root == nil { + return nil, nil, common.NewError("no transport layer found") + } + return nodes, root, nil +} + +func init() { + proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { + cfg := config.FromContext(ctx, Name).(*Config) + + // inbound + nodes, root, err := buildNodes(ctx, cfg.Inbound.Node) + if err != nil { + return nil, err + } + + transportServer, err := transport.NewServer(root.Context, nil) + if err != nil { + return nil, common.NewError("failed to initialize transport server").Base(err) + } + root.Server = transportServer + + // build server tree + for _, path := range cfg.Inbound.Path { + lastNode := root + for i, tag := range path { + if _, found := nodes[tag]; !found { + return nil, common.NewError("invalid node tag: " + tag) + } + if i == len(path)-1 { + switch nodes[tag].Name { + case trojan.Name, simplesocks.Name, socks.Name, http.Name: + default: + return nil, common.NewError("inbound path must end with protocol trojan/simplesocks/http/socks") + } + } + if i == 0 { + if nodes[tag].Name != transport.Name { + return nil, common.NewError("inbound path must start with protocol transport") + } + continue + } + lastNode = lastNode.LinkNextNode(nodes[tag]) + } + lastNode.IsEndpoint = true + } + + servers := proxy.FindAllEndpoints(root) + + if len(cfg.Outbound.Path) != 1 { + return nil, common.NewError("there must be only 1 path for outbound protocol stack") + } + + // outbound + nodes, _, err = buildNodes(ctx, cfg.Outbound.Node) + if err != nil { + return nil, err + } + + // build client stack + var client tunnel.Client + for i, tag := range cfg.Outbound.Path[0] { + if _, found := nodes[tag]; !found { + return nil, common.NewError("invalid node tag: " + tag) + } + if i == 0 && nodes[tag].Name != freedom.Name && nodes[tag].Name != transport.Name { + return nil, common.NewError("outbound path must start with protocol freedom/transport") + } + t, err := tunnel.GetTunnel(nodes[tag].Name) + if err != nil { + return nil, common.NewError("invalid tunnel name").Base(err) + } + client, err = t.NewClient(nodes[tag].Context, client) + if err != nil { + return nil, common.NewError("failed to create client").Base(err) + } + } + return proxy.NewProxy(ctx, servers, client), nil + }) +} diff --git a/proxy/stack.go b/proxy/stack.go index 19c24036f..20c372659 100644 --- a/proxy/stack.go +++ b/proxy/stack.go @@ -12,16 +12,7 @@ type Node struct { IsEndpoint bool context.Context tunnel.Server -} - -func NewNode(name string, isEndpoint bool, context context.Context, server tunnel.Server) *Node { - return &Node{ - Name: name, - IsEndpoint: isEndpoint, - Context: context, - Server: server, - Next: make(map[string]*Node), - } + tunnel.Client } func (n *Node) BuildNext(name string) *Node { @@ -46,6 +37,23 @@ func (n *Node) BuildNext(name string) *Node { return newNode } +func (n *Node) LinkNextNode(next *Node) *Node { + if next, found := n.Next[next.Name]; found { + return next + } + n.Next[next.Name] = next + t, err := tunnel.GetTunnel(next.Name) + if err != nil { + log.Fatal(err) + } + s, err := t.NewServer(next.Context, n.Server) // context of the child nodes have been initialized + if err != nil { + log.Fatal(err) + } + next.Server = s + return next +} + func FindAllEndpoints(root *Node) []tunnel.Server { list := make([]tunnel.Server, 0) if root.IsEndpoint || len(root.Next) == 0 { @@ -57,34 +65,6 @@ func FindAllEndpoints(root *Node) []tunnel.Server { return list } -func buildServerStacksTree(ctx context.Context, current *Node, parent *Node) ([]tunnel.Server, error) { - t, err := tunnel.GetTunnel(current.Name) - if err != nil { - return nil, err - } - current.Server, err = t.NewServer(ctx, parent) - if err != nil { - return nil, err - } - leaves := make([]tunnel.Server, 0) - for _, child := range current.Next { - subTreeLeaves, err := buildServerStacksTree(ctx, child, current) - if err != nil { - return nil, err - } - leaves = append(leaves, subTreeLeaves...) - } - // current node is a leave node - if len(leaves) == 0 || current.IsEndpoint { - leaves = append(leaves, current) - } - return leaves, nil -} - -func CreateServersStacksTree(ctx context.Context, root *Node) ([]tunnel.Server, error) { - return buildServerStacksTree(ctx, root, nil) -} - // CreateClientStack create client tunnel stacks from lists func CreateClientStack(ctx context.Context, clientStack []string) (tunnel.Client, error) { var client tunnel.Client diff --git a/test/scenario/custom_test.go b/test/scenario/custom_test.go new file mode 100644 index 000000000..80756890a --- /dev/null +++ b/test/scenario/custom_test.go @@ -0,0 +1,108 @@ +package scenario + +import ( + "fmt" + "github.com/p4gefau1t/trojan-go/common" + _ "github.com/p4gefau1t/trojan-go/proxy/custom" + "github.com/p4gefau1t/trojan-go/test/util" + "testing" +) + +func TestCustom(t *testing.T) { + serverPort := common.PickPort("tcp", "127.0.0.1") + socksPort := common.PickPort("tcp", "127.0.0.1") + clientData := fmt.Sprintf(` +run-type: custom + +inbound: + node: + - protocol: transport + tag: transport + config: + local-addr: 127.0.0.1 + local-port: %d + - protocol: socks + tag: socks + path: + - + - transport + - socks + +outbound: + node: + - protocol: transport + tag: transport + config: + remote-addr: 127.0.0.1 + remote-port: %d + + - protocol: tls + tag: tls + config: + ssl: + sni: localhost + key: server.key + cert: server.crt + + - protocol: trojan + tag: trojan + config: + password: + - 12345678 + + path: + - + - transport + - tls + - trojan + +`, socksPort, serverPort) + serverData := fmt.Sprintf(` +run-type: custom + +inbound: + node: + - protocol: transport + tag: transport + config: + local-addr: 127.0.0.1 + local-port: %d + remote-addr: 127.0.0.1 + remote-port: %s + + - protocol: tls + tag: tls + config: + ssl: + sni: localhost + key: server.key + cert: server.crt + + - protocol: trojan + tag: trojan + config: + disable-http-check: true + password: + - 12345678 + + path: + - + - transport + - tls + - trojan + +outbound: + node: + - protocol: freedom + tag: freedom + + path: + - + - freedom + +`, serverPort, util.HTTPPort) + + if !CheckClientServer(clientData, serverData, socksPort) { + t.Fail() + } +} diff --git a/tunnel/freedom/raw_test.go b/tunnel/freedom/freedom_test.go similarity index 100% rename from tunnel/freedom/raw_test.go rename to tunnel/freedom/freedom_test.go diff --git a/tunnel/freedom/tunnel.go b/tunnel/freedom/tunnel.go index a0db33f87..2b4f165b1 100644 --- a/tunnel/freedom/tunnel.go +++ b/tunnel/freedom/tunnel.go @@ -5,7 +5,7 @@ import ( "github.com/p4gefau1t/trojan-go/tunnel" ) -const Name = "RAW" +const Name = "FREEDOM" type Tunnel struct{}