浏览代码

Add v2ray transport to trojan

世界 3 年之前
父节点
当前提交
c9b7acd22c
共有 6 个文件被更改,包括 197 次插入81 次删除
  1. 0 35
      cmd/internal/protogen/main.go
  2. 46 5
      inbound/trojan.go
  3. 9 7
      option/trojan.go
  4. 37 19
      outbound/trojan.go
  5. 2 2
      test/trojan_test.go
  6. 103 13
      test/v2ray_transport_test.go

+ 0 - 35
cmd/internal/protogen/main.go

@@ -9,9 +9,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
-	"regexp"
 	"runtime"
-	"strconv"
 	"strings"
 )
 
@@ -81,39 +79,6 @@ func GetGOBIN() string {
 	return GOBIN
 }
 
-func getInstalledProtocVersion(protocPath string) (string, error) {
-	cmd := exec.Command(protocPath, "--version")
-	cmd.Env = append(cmd.Env, os.Environ()...)
-	output, cmdErr := cmd.CombinedOutput()
-	if cmdErr != nil {
-		return "", cmdErr
-	}
-	versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+\.\d+)`)
-	matched := versionRegexp.FindStringSubmatch(string(output))
-	return matched[1], nil
-}
-
-func parseVersion(s string, width int) int64 {
-	strList := strings.Split(s, ".")
-	format := fmt.Sprintf("%%s%%0%ds", width)
-	v := ""
-	for _, value := range strList {
-		v = fmt.Sprintf(format, v, value)
-	}
-	var result int64
-	var err error
-	if result, err = strconv.ParseInt(v, 10, 64); err != nil {
-		return 0
-	}
-	return result
-}
-
-func needToUpdate(targetedVersion, installedVersion string) bool {
-	vt := parseVersion(targetedVersion, 4)
-	vi := parseVersion(installedVersion, 4)
-	return vt > vi
-}
-
 func main() {
 	pwd, err := os.Getwd()
 	if err != nil {

+ 46 - 5
inbound/trojan.go

@@ -10,6 +10,7 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-box/transport/v2ray"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/auth"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -27,6 +28,7 @@ type Trojan struct {
 	users        []option.TrojanUser
 	tlsConfig    *TLSConfig
 	fallbackAddr M.Socksaddr
+	transport    adapter.V2RayServerTransport
 }
 
 func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (*Trojan, error) {
@@ -63,6 +65,16 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
 		}
 		inbound.tlsConfig = tlsConfig
 	}
+	if options.Transport != nil {
+		var tlsConfig *tls.Config
+		if inbound.tlsConfig != nil {
+			tlsConfig = inbound.tlsConfig.Config()
+		}
+		inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), tlsConfig, adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newTransportConnection, nil, nil), inbound)
+		if err != nil {
+			return nil, E.Cause(err, "create server transport: ", options.Transport.Type)
+		}
+	}
 	inbound.service = service
 	inbound.connHandler = inbound
 	return inbound, nil
@@ -75,17 +87,41 @@ func (h *Trojan) Start() error {
 			return E.Cause(err, "create TLS config")
 		}
 	}
-	return common.Start(
-		h.service,
-		&h.myInboundAdapter,
-	)
+	if h.transport == nil {
+		return h.myInboundAdapter.Start()
+	}
+	if common.Contains(h.transport.Network(), N.NetworkTCP) {
+		tcpListener, err := h.myInboundAdapter.ListenTCP()
+		if err != nil {
+			return err
+		}
+		go func() {
+			sErr := h.transport.Serve(tcpListener)
+			if sErr != nil && !E.IsClosed(sErr) {
+				h.logger.Error("transport serve error: ", sErr)
+			}
+		}()
+	}
+	if common.Contains(h.transport.Network(), N.NetworkUDP) {
+		udpConn, err := h.myInboundAdapter.ListenUDP()
+		if err != nil {
+			return err
+		}
+		go func() {
+			sErr := h.transport.ServePacket(udpConn)
+			if sErr != nil && !E.IsClosed(sErr) {
+				h.logger.Error("transport serve error: ", sErr)
+			}
+		}()
+	}
+	return nil
 }
 
 func (h *Trojan) Close() error {
 	return common.Close(
-		h.service,
 		&h.myInboundAdapter,
 		common.PtrOrNil(h.tlsConfig),
+		h.transport,
 	)
 }
 
@@ -96,6 +132,11 @@ func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adap
 	return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
 }
 
+func (h *Trojan) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	metadata = h.createMetadata(conn)
+	return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
+}
+
 func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 	userIndex, loaded := auth.UserFromContext[int](ctx)
 	if !loaded {

+ 9 - 7
option/trojan.go

@@ -2,9 +2,10 @@ package option
 
 type TrojanInboundOptions struct {
 	ListenOptions
-	Users    []TrojanUser       `json:"users,omitempty"`
-	TLS      *InboundTLSOptions `json:"tls,omitempty"`
-	Fallback *ServerOptions     `json:"fallback,omitempty"`
+	Users     []TrojanUser           `json:"users,omitempty"`
+	TLS       *InboundTLSOptions     `json:"tls,omitempty"`
+	Fallback  *ServerOptions         `json:"fallback,omitempty"`
+	Transport *V2RayTransportOptions `json:"transport,omitempty"`
 }
 
 type TrojanUser struct {
@@ -15,8 +16,9 @@ type TrojanUser struct {
 type TrojanOutboundOptions struct {
 	OutboundDialerOptions
 	ServerOptions
-	Password         string              `json:"password"`
-	Network          NetworkList         `json:"network,omitempty"`
-	TLSOptions       *OutboundTLSOptions `json:"tls,omitempty"`
-	MultiplexOptions *MultiplexOptions   `json:"multiplex,omitempty"`
+	Password  string                 `json:"password"`
+	Network   NetworkList            `json:"network,omitempty"`
+	TLS       *OutboundTLSOptions    `json:"tls,omitempty"`
+	Multiplex *MultiplexOptions      `json:"multiplex,omitempty"`
+	Transport *V2RayTransportOptions `json:"transport,omitempty"`
 }

+ 37 - 19
outbound/trojan.go

@@ -2,6 +2,7 @@ package outbound
 
 import (
 	"context"
+	"crypto/tls"
 	"net"
 
 	"github.com/sagernet/sing-box/adapter"
@@ -10,6 +11,7 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-box/transport/v2ray"
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
 	M "github.com/sagernet/sing/common/metadata"
@@ -25,6 +27,8 @@ type Trojan struct {
 	serverAddr      M.Socksaddr
 	key             [56]byte
 	multiplexDialer N.Dialer
+	tlsConfig       *tls.Config
+	transport       adapter.V2RayClientTransport
 }
 
 func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) {
@@ -36,15 +40,24 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
 			logger:   logger,
 			tag:      tag,
 		},
+		dialer:     dialer.NewOutbound(router, options.OutboundDialerOptions),
 		serverAddr: options.ServerOptions.Build(),
 		key:        trojan.Key(options.Password),
 	}
 	var err error
-	outbound.dialer, err = dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions))
-	if err != nil {
-		return nil, err
+	if options.TLS != nil {
+		outbound.tlsConfig, err = dialer.TLSConfig(options.Server, common.PtrValueOrDefault(options.TLS))
+		if err != nil {
+			return nil, err
+		}
 	}
-	outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*TrojanDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
+	if options.Transport != nil {
+		outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
+		if err != nil {
+			return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
+		}
+	}
+	outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
 	if err != nil {
 		return nil, err
 	}
@@ -59,7 +72,7 @@ func (h *Trojan) DialContext(ctx context.Context, network string, destination M.
 		case N.NetworkUDP:
 			h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
 		}
-		return (*TrojanDialer)(h).DialContext(ctx, network, destination)
+		return (*trojanDialer)(h).DialContext(ctx, network, destination)
 	} else {
 		switch N.NetworkName(network) {
 		case N.NetworkTCP:
@@ -74,7 +87,7 @@ func (h *Trojan) DialContext(ctx context.Context, network string, destination M.
 func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
 	if h.multiplexDialer == nil {
 		h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
-		return (*TrojanDialer)(h).ListenPacket(ctx, destination)
+		return (*trojanDialer)(h).ListenPacket(ctx, destination)
 	} else {
 		h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination)
 		return h.multiplexDialer.ListenPacket(ctx, destination)
@@ -89,31 +102,36 @@ func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, met
 	return NewPacketConnection(ctx, h, conn, metadata)
 }
 
-type TrojanDialer Trojan
+type trojanDialer Trojan
 
-func (h *TrojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
+func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
 	ctx, metadata := adapter.AppendContext(ctx)
 	metadata.Outbound = h.tag
 	metadata.Destination = destination
+	var conn net.Conn
+	var err error
+	if h.transport != nil {
+		conn, err = h.transport.DialContext(ctx)
+	} else {
+		conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
+		if err == nil && h.tlsConfig != nil {
+			conn, err = dialer.TLSClient(ctx, conn, h.tlsConfig)
+		}
+	}
+	if err != nil {
+		return nil, err
+	}
 	switch N.NetworkName(network) {
 	case N.NetworkTCP:
-		outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
-		if err != nil {
-			return nil, err
-		}
-		return trojan.NewClientConn(outConn, h.key, destination), nil
+		return trojan.NewClientConn(conn, h.key, destination), nil
 	case N.NetworkUDP:
-		outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
-		if err != nil {
-			return nil, err
-		}
-		return trojan.NewClientPacketConn(outConn, h.key), nil
+		return trojan.NewClientPacketConn(conn, h.key), nil
 	default:
 		return nil, E.Extend(N.ErrUnknownNetwork, network)
 	}
 }
 
-func (h *TrojanDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
+func (h *trojanDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
 	conn, err := h.DialContext(ctx, N.NetworkUDP, destination)
 	if err != nil {
 		return nil, err

+ 2 - 2
test/trojan_test.go

@@ -43,7 +43,7 @@ func TestTrojanOutbound(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					Password: "password",
-					TLSOptions: &option.OutboundTLSOptions{
+					TLS: &option.OutboundTLSOptions{
 						Enabled:         true,
 						ServerName:      "example.org",
 						CertificatePath: certPem,
@@ -107,7 +107,7 @@ func TestTrojanSelf(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					Password: "password",
-					TLSOptions: &option.OutboundTLSOptions{
+					TLS: &option.OutboundTLSOptions{
 						Enabled:         true,
 						ServerName:      "example.org",
 						CertificatePath: certPem,

+ 103 - 13
test/vmess_transport_test.go → test/v2ray_transport_test.go

@@ -11,8 +11,8 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestVMessGRPCSelf(t *testing.T) {
-	testVMessWebscoketSelf(t, &option.V2RayTransportOptions{
+func TestV2RayGRPCSelf(t *testing.T) {
+	testV2RayTransportSelf(t, &option.V2RayTransportOptions{
 		Type: C.V2RayTransportTypeGRPC,
 		GRPCOptions: option.V2RayGRPCOptions{
 			ServiceName: "TunService",
@@ -20,14 +20,14 @@ func TestVMessGRPCSelf(t *testing.T) {
 	})
 }
 
-func TestVMessWebscoketSelf(t *testing.T) {
+func TestV2RayWebscoketSelf(t *testing.T) {
 	t.Run("basic", func(t *testing.T) {
-		testVMessWebscoketSelf(t, &option.V2RayTransportOptions{
+		testV2RayTransportSelf(t, &option.V2RayTransportOptions{
 			Type: C.V2RayTransportTypeWebsocket,
 		})
 	})
 	t.Run("v2ray early data", func(t *testing.T) {
-		testVMessWebscoketSelf(t, &option.V2RayTransportOptions{
+		testV2RayTransportSelf(t, &option.V2RayTransportOptions{
 			Type: C.V2RayTransportTypeWebsocket,
 			WebsocketOptions: option.V2RayWebsocketOptions{
 				MaxEarlyData: 2048,
@@ -35,7 +35,7 @@ func TestVMessWebscoketSelf(t *testing.T) {
 		})
 	})
 	t.Run("xray early data", func(t *testing.T) {
-		testVMessWebscoketSelf(t, &option.V2RayTransportOptions{
+		testV2RayTransportSelf(t, &option.V2RayTransportOptions{
 			Type: C.V2RayTransportTypeWebsocket,
 			WebsocketOptions: option.V2RayWebsocketOptions{
 				MaxEarlyData:        2048,
@@ -45,13 +45,28 @@ func TestVMessWebscoketSelf(t *testing.T) {
 	})
 }
 
-func TestVMessHTTPSelf(t *testing.T) {
-	testVMessWebscoketSelf(t, &option.V2RayTransportOptions{
+func TestV2RayHTTPSelf(t *testing.T) {
+	testV2RayTransportSelf(t, &option.V2RayTransportOptions{
 		Type: C.V2RayTransportTypeHTTP,
 	})
 }
 
-func testVMessWebscoketSelf(t *testing.T, transport *option.V2RayTransportOptions) {
+func TestV2RayHTTPPlainSelf(t *testing.T) {
+	testV2RayTransportNOTLSSelf(t, &option.V2RayTransportOptions{
+		Type: C.V2RayTransportTypeHTTP,
+	})
+}
+
+func testV2RayTransportSelf(t *testing.T, transport *option.V2RayTransportOptions) {
+	t.Run("vmess", func(t *testing.T) {
+		testVMessTransportSelf(t, transport)
+	})
+	t.Run("trojan", func(t *testing.T) {
+		testTrojanTransportSelf(t, transport)
+	})
+}
+
+func testVMessTransportSelf(t *testing.T, transport *option.V2RayTransportOptions) {
 	user, err := uuid.DefaultGenerator.NewV4()
 	require.NoError(t, err)
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
@@ -130,6 +145,84 @@ func testVMessWebscoketSelf(t *testing.T, transport *option.V2RayTransportOption
 	testSuit(t, clientPort, testPort)
 }
 
+func testTrojanTransportSelf(t *testing.T, transport *option.V2RayTransportOptions) {
+	user, err := uuid.DefaultGenerator.NewV4()
+	require.NoError(t, err)
+	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
+	startInstance(t, option.Options{
+		Log: &option.LogOptions{
+			Level: "error",
+		},
+		Inbounds: []option.Inbound{
+			{
+				Type: C.TypeMixed,
+				Tag:  "mixed-in",
+				MixedOptions: option.HTTPMixedInboundOptions{
+					ListenOptions: option.ListenOptions{
+						Listen:     option.ListenAddress(netip.IPv4Unspecified()),
+						ListenPort: clientPort,
+					},
+				},
+			},
+			{
+				Type: C.TypeTrojan,
+				TrojanOptions: option.TrojanInboundOptions{
+					ListenOptions: option.ListenOptions{
+						Listen:     option.ListenAddress(netip.IPv4Unspecified()),
+						ListenPort: serverPort,
+					},
+					Users: []option.TrojanUser{
+						{
+							Name:     "sekai",
+							Password: user.String(),
+						},
+					},
+					TLS: &option.InboundTLSOptions{
+						Enabled:         true,
+						ServerName:      "example.org",
+						CertificatePath: certPem,
+						KeyPath:         keyPem,
+					},
+					Transport: transport,
+				},
+			},
+		},
+		Outbounds: []option.Outbound{
+			{
+				Type: C.TypeDirect,
+			},
+			{
+				Type: C.TypeTrojan,
+				Tag:  "vmess-out",
+				TrojanOptions: option.TrojanOutboundOptions{
+					ServerOptions: option.ServerOptions{
+						Server:     "127.0.0.1",
+						ServerPort: serverPort,
+					},
+					Password: user.String(),
+					TLS: &option.OutboundTLSOptions{
+						Enabled:         true,
+						ServerName:      "example.org",
+						CertificatePath: certPem,
+					},
+					Transport: transport,
+				},
+			},
+		},
+		Route: &option.RouteOptions{
+			Rules: []option.Rule{
+				{
+					DefaultOptions: option.DefaultRule{
+						Inbound:  []string{"mixed-in"},
+						Outbound: "vmess-out",
+					},
+				},
+			},
+		},
+	})
+	testSuit(t, clientPort, testPort)
+}
+
 func TestVMessQUICSelf(t *testing.T) {
 	transport := &option.V2RayTransportOptions{
 		Type: C.V2RayTransportTypeQUIC,
@@ -212,10 +305,7 @@ func TestVMessQUICSelf(t *testing.T) {
 	testSuitQUIC(t, clientPort, testPort)
 }
 
-func TestVMessHTTPNoTLSSelf(t *testing.T) {
-	transport := &option.V2RayTransportOptions{
-		Type: C.V2RayTransportTypeHTTP,
-	}
+func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportOptions) {
 	user, err := uuid.DefaultGenerator.NewV4()
 	require.NoError(t, err)
 	startInstance(t, option.Options{