diff --git a/.github/update_dependencies.sh b/.github/update_dependencies.sh index 92f4f604..13f91ee4 100755 --- a/.github/update_dependencies.sh +++ b/.github/update_dependencies.sh @@ -6,6 +6,7 @@ go get -x github.com/sagernet/sing@$(git -C $PROJECTS/sing rev-parse HEAD) go get -x github.com/sagernet/sing-dns@$(git -C $PROJECTS/sing-dns rev-parse HEAD) go get -x github.com/sagernet/sing-tun@$(git -C $PROJECTS/sing-tun rev-parse HEAD) go get -x github.com/sagernet/sing-shadowsocks@$(git -C $PROJECTS/sing-shadowsocks rev-parse HEAD) +go get -x github.com/sagernet/sing-vmess@$(git -C $PROJECTS/sing-vmess rev-parse HEAD) go mod tidy pushd test go mod tidy diff --git a/box.go b/box.go index 26c1346f..38680d2a 100644 --- a/box.go +++ b/box.go @@ -59,6 +59,15 @@ func New(ctx context.Context, options option.Options) (*Box, error) { TimestampFormat: "-0700 2006-01-02 15:04:05", } logFactory = log.NewFactory(logFormatter, logWriter) + if logOptions.Level != "" { + logLevel, err := log.ParseLevel(logOptions.Level) + if err != nil { + return nil, E.Cause(err, "parse log level") + } + logFactory.SetLevel(logLevel) + } else { + logFactory.SetLevel(log.LevelTrace) + } } router, err := route.NewRouter( diff --git a/constant/proxy.go b/constant/proxy.go index b9649126..305db405 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -1,14 +1,15 @@ package constant const ( + TypeTun = "tun" + TypeRedirect = "redirect" + TypeTProxy = "tproxy" + TypeDNS = "dns" TypeDirect = "direct" TypeBlock = "block" TypeSocks = "socks" TypeHTTP = "http" TypeMixed = "mixed" TypeShadowsocks = "shadowsocks" - TypeTun = "tun" - TypeRedirect = "redirect" - TypeTProxy = "tproxy" - TypeDNS = "dns" + TypeVMess = "vmess" ) diff --git a/go.mod b/go.mod index ff8f4369..578d4ec1 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/goccy/go-json v0.9.10 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/oschwald/maxminddb-golang v1.9.0 - github.com/sagernet/sing v0.0.0-20220717063925-00f98eb6bc34 + github.com/sagernet/sing v0.0.0-20220718035659-3d74b823ed56 github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f github.com/sagernet/sing-tun v0.0.0-20220717030718-f53aabff275f @@ -18,8 +18,11 @@ require ( golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 ) +require github.com/sagernet/sing-vmess v0.0.0-20220718031323-07c377156e4a + require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect; indirectg + github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/google/btree v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect diff --git a/go.sum b/go.sum index 928c92cf..069efa00 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -25,14 +27,16 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.0.0-20220717063925-00f98eb6bc34 h1:1kFruA2QzuH2R6txJXEDSasfdxzsjNyzC4Z1kZjMkHg= -github.com/sagernet/sing v0.0.0-20220717063925-00f98eb6bc34/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= +github.com/sagernet/sing v0.0.0-20220718035659-3d74b823ed56 h1:r6FHrtIE3dG9c6zl487QjxPFgADq/C0AiDwhpAZFKUw= +github.com/sagernet/sing v0.0.0-20220718035659-3d74b823ed56/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZmXp6WpxzyB2xeyRIA1/L8EJKuNntfY= github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk= github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc= github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f/go.mod h1:cDrLwa3zwY8AaW6a4sjipn4xgdIr3CT8TPqSW6iFOi0= github.com/sagernet/sing-tun v0.0.0-20220717030718-f53aabff275f h1:o3YN4sFC7lQznAwutagPqBb23hal7MkgVq/VEvd7Vug= github.com/sagernet/sing-tun v0.0.0-20220717030718-f53aabff275f/go.mod h1:p7QbUBs2ejf6UQsiHyy1xGAWOk9JWQjZTHy8pOmkWmo= +github.com/sagernet/sing-vmess v0.0.0-20220718031323-07c377156e4a h1:durFxTP1xsOMeDt8x0AV/9BXAPa8uMQRKzPaVkGSOS0= +github.com/sagernet/sing-vmess v0.0.0-20220718031323-07c377156e4a/go.mod h1:VjqeHNWtDVoExWInXB7QsCeMp5RozlnJhMgfbW/n4I0= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/inbound/builder.go b/inbound/builder.go index 3074eb07..e6a80ee7 100644 --- a/inbound/builder.go +++ b/inbound/builder.go @@ -16,6 +16,14 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o return nil, E.New("empty inbound config") } switch options.Type { + case C.TypeTun: + return NewTun(ctx, router, logger, options.Tag, options.TunOptions) + case C.TypeRedirect: + return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil + case C.TypeTProxy: + return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil + case C.TypeDNS: + return NewDNS(ctx, router, logger, options.Tag, options.DNSOptions), nil case C.TypeDirect: return NewDirect(ctx, router, logger, options.Tag, options.DirectOptions), nil case C.TypeSocks: @@ -26,14 +34,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o return NewMixed(ctx, router, logger, options.Tag, options.MixedOptions), nil case C.TypeShadowsocks: return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions) - case C.TypeTun: - return NewTun(ctx, router, logger, options.Tag, options.TunOptions) - case C.TypeRedirect: - return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil - case C.TypeTProxy: - return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil - case C.TypeDNS: - return NewDNS(ctx, router, logger, options.Tag, options.DNSOptions), nil + case C.TypeVMess: + return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions) default: return nil, E.New("unknown inbound type: ", options.Type) } diff --git a/inbound/vmess.go b/inbound/vmess.go new file mode 100644 index 00000000..3b70e2ee --- /dev/null +++ b/inbound/vmess.go @@ -0,0 +1,86 @@ +package inbound + +import ( + "context" + "net" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-vmess" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + F "github.com/sagernet/sing/common/format" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.Inbound = (*VMess)(nil) + +type VMess struct { + myInboundAdapter + service *vmess.Service[int] + users []option.VMessUser +} + +func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (*VMess, error) { + inbound := &VMess{ + myInboundAdapter: myInboundAdapter{ + protocol: C.TypeVMess, + network: []string{C.NetworkTCP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, + users: options.Users, + } + service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) + err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, user option.VMessUser) int { + return index + }), common.Map(options.Users, func(user option.VMessUser) string { + return user.UUID + })) + if err != nil { + return nil, err + } + inbound.service = service + inbound.connHandler = inbound + return inbound, nil +} + +func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) +} + +func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + return os.ErrInvalid + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + return os.ErrInvalid + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + return h.router.RoutePacketConnection(ctx, conn, metadata) +} diff --git a/option/inbound.go b/option/inbound.go index 4001c10f..22b6fd51 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -12,15 +12,16 @@ import ( type _Inbound struct { Type string `json:"type"` Tag string `json:"tag,omitempty"` + TunOptions TunInboundOptions `json:"-"` + RedirectOptions RedirectInboundOptions `json:"-"` + TProxyOptions TProxyInboundOptions `json:"-"` + DNSOptions DNSInboundOptions `json:"-"` DirectOptions DirectInboundOptions `json:"-"` SocksOptions SocksInboundOptions `json:"-"` HTTPOptions HTTPMixedInboundOptions `json:"-"` MixedOptions HTTPMixedInboundOptions `json:"-"` ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` - TunOptions TunInboundOptions `json:"-"` - RedirectOptions RedirectInboundOptions `json:"-"` - TProxyOptions TProxyInboundOptions `json:"-"` - DNSOptions DNSInboundOptions `json:"-"` + VMessOptions VMessInboundOptions `json:"-"` } type Inbound _Inbound @@ -28,20 +29,29 @@ type Inbound _Inbound func (h Inbound) Equals(other Inbound) bool { return h.Type == other.Type && h.Tag == other.Tag && + h.TunOptions == other.TunOptions && + h.RedirectOptions == other.RedirectOptions && + h.TProxyOptions == other.TProxyOptions && + h.DNSOptions == other.DNSOptions && h.DirectOptions == other.DirectOptions && h.SocksOptions.Equals(other.SocksOptions) && h.HTTPOptions.Equals(other.HTTPOptions) && h.MixedOptions.Equals(other.MixedOptions) && h.ShadowsocksOptions.Equals(other.ShadowsocksOptions) && - h.TunOptions == other.TunOptions && - h.RedirectOptions == other.RedirectOptions && - h.TProxyOptions == other.TProxyOptions && - h.DNSOptions == other.DNSOptions + h.VMessOptions.Equals(other.VMessOptions) } func (h Inbound) MarshalJSON() ([]byte, error) { var v any switch h.Type { + case C.TypeTun: + v = h.TunOptions + case C.TypeRedirect: + v = h.RedirectOptions + case C.TypeTProxy: + v = h.TProxyOptions + case C.TypeDNS: + v = h.DNSOptions case C.TypeDirect: v = h.DirectOptions case C.TypeSocks: @@ -52,14 +62,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) { v = h.MixedOptions case C.TypeShadowsocks: v = h.ShadowsocksOptions - case C.TypeTun: - v = h.TunOptions - case C.TypeRedirect: - v = h.RedirectOptions - case C.TypeTProxy: - v = h.TProxyOptions - case C.TypeDNS: - v = h.DNSOptions + case C.TypeVMess: + v = h.VMessOptions default: return nil, E.New("unknown inbound type: ", h.Type) } @@ -73,6 +77,14 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { } var v any switch h.Type { + case C.TypeTun: + v = &h.TunOptions + case C.TypeRedirect: + v = &h.RedirectOptions + case C.TypeTProxy: + v = &h.TProxyOptions + case C.TypeDNS: + v = &h.DNSOptions case C.TypeDirect: v = &h.DirectOptions case C.TypeSocks: @@ -83,16 +95,10 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { v = &h.MixedOptions case C.TypeShadowsocks: v = &h.ShadowsocksOptions - case C.TypeTun: - v = &h.TunOptions - case C.TypeRedirect: - v = &h.RedirectOptions - case C.TypeTProxy: - v = &h.TProxyOptions - case C.TypeDNS: - v = &h.DNSOptions + case C.TypeVMess: + v = &h.VMessOptions default: - return nil + return E.New("unknown inbound type: ", h.Type) } err = UnmarshallExcluded(bytes, (*_Inbound)(h), v) if err != nil { @@ -173,6 +179,21 @@ type ShadowsocksDestination struct { ServerOptions } +type VMessInboundOptions struct { + ListenOptions + Users []VMessUser `json:"users,omitempty"` +} + +func (o VMessInboundOptions) Equals(other VMessInboundOptions) bool { + return o.ListenOptions == other.ListenOptions && + common.ComparableSliceEquals(o.Users, other.Users) +} + +type VMessUser struct { + Name string `json:"name"` + UUID string `json:"uuid"` +} + type TunInboundOptions struct { InterfaceName string `json:"interface_name,omitempty"` MTU uint32 `json:"mtu,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index 6bf930c7..9dcd91d8 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -15,6 +15,7 @@ type _Outbound struct { SocksOptions SocksOutboundOptions `json:"-"` HTTPOptions HTTPOutboundOptions `json:"-"` ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` + VMessOptions VMessOutboundOptions `json:"-"` } type Outbound _Outbound @@ -24,14 +25,16 @@ func (h Outbound) MarshalJSON() ([]byte, error) { switch h.Type { case C.TypeDirect: v = h.DirectOptions + case C.TypeBlock: + v = nil case C.TypeSocks: v = h.SocksOptions case C.TypeHTTP: v = h.HTTPOptions case C.TypeShadowsocks: v = h.ShadowsocksOptions - case C.TypeBlock: - v = nil + case C.TypeVMess: + v = h.VMessOptions default: return nil, E.New("unknown outbound type: ", h.Type) } @@ -47,14 +50,16 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error { switch h.Type { case C.TypeDirect: v = &h.DirectOptions + case C.TypeBlock: + v = nil case C.TypeSocks: v = &h.SocksOptions case C.TypeHTTP: v = &h.HTTPOptions case C.TypeShadowsocks: v = &h.ShadowsocksOptions - case C.TypeBlock: - v = nil + case C.TypeVMess: + v = &h.VMessOptions default: return nil } @@ -131,3 +136,14 @@ type ShadowsocksOutboundOptions struct { Password string `json:"password"` Network NetworkList `json:"network,omitempty"` } + +type VMessOutboundOptions struct { + OutboundDialerOptions + ServerOptions + UUID string `json:"uuid"` + Security string `json:"security"` + AlterId int `json:"alter_id,omitempty"` + GlobalPadding bool `json:"global_padding,omitempty"` + AuthenticatedLength bool `json:"authenticated_length,omitempty"` + Network NetworkList `json:"network,omitempty"` +} diff --git a/outbound/builder.go b/outbound/builder.go index 03d42129..7162c2ba 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -24,6 +24,8 @@ func New(router adapter.Router, logger log.ContextLogger, options option.Outboun return NewHTTP(router, logger, options.Tag, options.HTTPOptions), nil case C.TypeShadowsocks: return NewShadowsocks(router, logger, options.Tag, options.ShadowsocksOptions) + case C.TypeVMess: + return NewVMess(router, logger, options.Tag, options.VMessOptions) default: return nil, E.New("unknown outbound type: ", options.Type) } diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index a9044a73..efcbf014 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -71,7 +71,7 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) ctx, metadata := adapter.AppendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination - h.logger.InfoContext(ctx, "outbound packet connection to ", h.serverAddr) + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) outConn, err := h.dialer.DialContext(ctx, "udp", h.serverAddr) if err != nil { return nil, err diff --git a/outbound/vmess.go b/outbound/vmess.go new file mode 100644 index 00000000..54ef32e4 --- /dev/null +++ b/outbound/vmess.go @@ -0,0 +1,89 @@ +package outbound + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-vmess" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.Outbound = (*VMess)(nil) + +type VMess struct { + myOutboundAdapter + dialer N.Dialer + client *vmess.Client + serverAddr M.Socksaddr +} + +func NewVMess(router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { + var clientOptions []vmess.ClientOption + if options.GlobalPadding { + clientOptions = append(clientOptions, vmess.ClientWithGlobalPadding()) + } + if options.AuthenticatedLength { + clientOptions = append(clientOptions, vmess.ClientWithAuthenticatedLength()) + } + client, err := vmess.NewClient(options.UUID, options.Security, options.AlterId, clientOptions...) + if err != nil { + return nil, err + } + return &VMess{ + myOutboundAdapter{ + protocol: C.TypeDirect, + logger: logger, + tag: tag, + network: options.Network.Build(), + }, + dialer.NewOutbound(router, options.OutboundDialerOptions), + client, + options.ServerOptions.Build(), + }, nil +} + +func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.AppendContext(ctx) + metadata.Outbound = h.tag + metadata.Destination = destination + switch network { + case C.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + outConn, err := h.dialer.DialContext(ctx, C.NetworkTCP, h.serverAddr) + if err != nil { + return nil, err + } + return h.client.DialEarlyConn(outConn, destination), nil + case C.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + outConn, err := h.dialer.DialContext(ctx, C.NetworkTCP, h.serverAddr) + if err != nil { + return nil, err + } + return h.client.DialEarlyPacketConn(outConn, destination), nil + default: + panic("unknown network " + network) + } +} + +func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + conn, err := h.DialContext(ctx, C.NetworkUDP, destination) + if err != nil { + return nil, err + } + return conn.(vmess.PacketConn), nil +} + +func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return NewEarlyConnection(ctx, h, conn, metadata) +} + +func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewPacketConnection(ctx, h, conn, metadata) +} diff --git a/test/box_test.go b/test/box_test.go index 4f1366ba..e6106028 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -46,15 +46,21 @@ func startInstance(t *testing.T, options option.Options) { func testSuit(t *testing.T, clientPort uint16, testPort uint16) { dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "") - dialTCP := func(port uint16) (net.Conn, error) { - return dialer.DialContext(context.Background(), "tcp", M.ParseSocksaddrHostPort("127.0.0.1", port)) + dialTCP := func() (net.Conn, error) { + return dialer.DialContext(context.Background(), "tcp", M.ParseSocksaddrHostPort("127.0.0.1", testPort)) } - dialUDP := func(port uint16) (net.PacketConn, error) { - return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", port)) + dialUDP := func() (net.PacketConn, error) { + return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", testPort)) } - require.NoError(t, testPingPongWithConn(t, dialTCP)) - require.NoError(t, testLargeDataWithConn(t, dialTCP)) - require.NoError(t, testPingPongWithPacketConn(t, dialUDP)) - require.NoError(t, testLargeDataWithPacketConn(t, dialUDP)) - require.NoError(t, testPacketConnTimeout(t, dialUDP)) + t.Run("tcp", func(t *testing.T) { + t.Parallel() + require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP)) + }) + t.Run("udp", func(t *testing.T) { + t.Parallel() + require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP)) + }) + // require.NoError(t, testPingPongWithConn(t, testPort, dialTCP)) + // require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP)) + // require.NoError(t, testPacketConnTimeout(t, dialUDP)) } diff --git a/test/clash_test.go b/test/clash_test.go index 47a09036..833dab73 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -152,15 +152,14 @@ func newLargeDataPair() (chan hashPair, chan hashPair, func(t *testing.T) error) return pingCh, pongCh, test } -func testPingPongWithConn(t *testing.T, cc func(port uint16) (net.Conn, error)) error { - port := mkPort(t) +func testPingPongWithConn(t *testing.T, port uint16, cc func() (net.Conn, error)) error { l, err := listen("tcp", ":"+F.ToString(port)) if err != nil { return err } defer l.Close() - c, err := cc(port) + c, err := cc() if err != nil { return err } @@ -199,9 +198,7 @@ func testPingPongWithConn(t *testing.T, cc func(port uint16) (net.Conn, error)) return test(t) } -func testPingPongWithPacketConn(t *testing.T, pcc func(port uint16) (net.PacketConn, error)) error { - port := mkPort(t) - +func testPingPongWithPacketConn(t *testing.T, port uint16, pcc func() (net.PacketConn, error)) error { l, err := listenPacket("udp", ":"+F.ToString(port)) require.NoError(t, err) defer l.Close() @@ -222,7 +219,7 @@ func testPingPongWithPacketConn(t *testing.T, pcc func(port uint16) (net.PacketC } }() - pc, err := pcc(port) + pc, err := pcc() if err != nil { return err } @@ -249,8 +246,7 @@ type hashPair struct { recvHash map[int][]byte } -func testLargeDataWithConn(t *testing.T, cc func(port uint16) (net.Conn, error)) error { - port := mkPort(t) +func testLargeDataWithConn(t *testing.T, port uint16, cc func() (net.Conn, error)) error { l, err := listen("tcp", ":"+F.ToString(port)) require.NoError(t, err) defer l.Close() @@ -279,7 +275,7 @@ func testLargeDataWithConn(t *testing.T, cc func(port uint16) (net.Conn, error)) return hashMap, nil } - c, err := cc(port) + c, err := cc() if err != nil { return err } @@ -347,8 +343,7 @@ func testLargeDataWithConn(t *testing.T, cc func(port uint16) (net.Conn, error)) return test(t) } -func testLargeDataWithPacketConn(t *testing.T, pcc func(port uint16) (net.PacketConn, error)) error { - port := mkPort(t) +func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.PacketConn, error)) error { l, err := listenPacket("udp", ":"+F.ToString(port)) require.NoError(t, err) defer l.Close() @@ -363,24 +358,22 @@ func testLargeDataWithPacketConn(t *testing.T, pcc func(port uint16) (net.Packet hashMap := map[int][]byte{} mux := sync.Mutex{} for i := 0; i < times; i++ { - go func(idx int) { - buf := make([]byte, chunkSize) - if _, err := rand.Read(buf[1:]); err != nil { - t.Log(err.Error()) - return - } - buf[0] = byte(idx) + buf := make([]byte, chunkSize) + if _, err = rand.Read(buf[1:]); err != nil { + t.Log(err.Error()) + continue + } + buf[0] = byte(i) - hash := md5.Sum(buf) - mux.Lock() - hashMap[idx] = hash[:] - mux.Unlock() + hash := md5.Sum(buf) + mux.Lock() + hashMap[i] = hash[:] + mux.Unlock() - if _, err := pc.WriteTo(buf, addr); err != nil { - t.Log(err.Error()) - return - } - }(i) + if _, err = pc.WriteTo(buf, addr); err != nil { + t.Log(err) + continue + } } return hashMap, nil @@ -414,7 +407,7 @@ func testLargeDataWithPacketConn(t *testing.T, pcc func(port uint16) (net.Packet } }() - pc, err := pcc(port) + pc, err := pcc() if err != nil { return err } @@ -449,8 +442,8 @@ func testLargeDataWithPacketConn(t *testing.T, pcc func(port uint16) (net.Packet return test(t) } -func testPacketConnTimeout(t *testing.T, pcc func(port uint16) (net.PacketConn, error)) error { - pc, err := pcc(mkPort(t)) +func testPacketConnTimeout(t *testing.T, pcc func() (net.PacketConn, error)) error { + pc, err := pcc() if err != nil { return err } diff --git a/test/go.mod b/test/go.mod index 5a721365..cdeff46b 100644 --- a/test/go.mod +++ b/test/go.mod @@ -2,19 +2,21 @@ module test go 1.18 -require ( - github.com/docker/docker v20.10.17+incompatible - github.com/docker/go-connections v0.4.0 - github.com/sagernet/sing v0.0.0-20220717063925-00f98eb6bc34 - github.com/sagernet/sing-box v0.0.0 - github.com/stretchr/testify v1.8.0 - golang.org/x/net v0.0.0-20220708220712-1185a9018129 -) +require github.com/sagernet/sing-box v0.0.0 replace github.com/sagernet/sing-box => ../ require ( - github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/docker/docker v20.10.17+incompatible + github.com/docker/go-connections v0.4.0 + github.com/gofrs/uuid v4.2.0+incompatible + github.com/sagernet/sing v0.0.0-20220718035659-3d74b823ed56 + github.com/stretchr/testify v1.8.0 + golang.org/x/net v0.0.0-20220708220712-1185a9018129 +) + +require ( + github.com/Microsoft/go-winio v0.5.1 // indirect github.com/database64128/tfo-go v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect @@ -35,12 +37,13 @@ require ( github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 // indirect github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f // indirect github.com/sagernet/sing-tun v0.0.0-20220717030718-f53aabff275f // indirect + github.com/sagernet/sing-vmess v0.0.0-20220718031323-07c377156e4a // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/vishvananda/netlink v1.1.0 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect - golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.3.0 // indirect gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d // indirect diff --git a/test/go.sum b/test/go.sum index e9d94e29..484c5afb 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,7 +1,7 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/database64128/tfo-go v1.1.0 h1:VO0polyGNSAmr99nYw9GQeMz7ZOcQ/QbjlTwniHwfTQ= @@ -19,6 +19,8 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -52,14 +54,16 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sagernet/sing v0.0.0-20220717063925-00f98eb6bc34 h1:1kFruA2QzuH2R6txJXEDSasfdxzsjNyzC4Z1kZjMkHg= -github.com/sagernet/sing v0.0.0-20220717063925-00f98eb6bc34/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= +github.com/sagernet/sing v0.0.0-20220718035659-3d74b823ed56 h1:r6FHrtIE3dG9c6zl487QjxPFgADq/C0AiDwhpAZFKUw= +github.com/sagernet/sing v0.0.0-20220718035659-3d74b823ed56/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZmXp6WpxzyB2xeyRIA1/L8EJKuNntfY= github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk= github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc= github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f/go.mod h1:cDrLwa3zwY8AaW6a4sjipn4xgdIr3CT8TPqSW6iFOi0= github.com/sagernet/sing-tun v0.0.0-20220717030718-f53aabff275f h1:o3YN4sFC7lQznAwutagPqBb23hal7MkgVq/VEvd7Vug= github.com/sagernet/sing-tun v0.0.0-20220717030718-f53aabff275f/go.mod h1:p7QbUBs2ejf6UQsiHyy1xGAWOk9JWQjZTHy8pOmkWmo= +github.com/sagernet/sing-vmess v0.0.0-20220718031323-07c377156e4a h1:durFxTP1xsOMeDt8x0AV/9BXAPa8uMQRKzPaVkGSOS0= +github.com/sagernet/sing-vmess v0.0.0-20220718031323-07c377156e4a/go.mod h1:VjqeHNWtDVoExWInXB7QsCeMp5RozlnJhMgfbW/n4I0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -107,8 +111,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JC golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 98cb5a15..3a681458 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -14,6 +14,7 @@ import ( ) func TestShadowsocks(t *testing.T) { + t.Parallel() for _, method := range []string{ "aes-128-gcm", "aes-256-gcm", @@ -32,6 +33,7 @@ func TestShadowsocks(t *testing.T) { } func TestShadowsocks2022(t *testing.T) { + t.Parallel() for _, method16 := range []string{ "2022-blake3-aes-128-gcm", } { @@ -74,7 +76,7 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass }) startInstance(t, option.Options{ Log: &option.LogOption{ - Disabled: true, + Level: "error", }, Inbounds: []option.Inbound{ { @@ -106,7 +108,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas }) startInstance(t, option.Options{ Log: &option.LogOption{ - Disabled: true, + Level: "error", }, Inbounds: []option.Inbound{ { @@ -143,7 +145,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { testPort := mkPort(t) startInstance(t, option.Options{ Log: &option.LogOption{ - Disabled: true, + Level: "error", }, Inbounds: []option.Inbound{ { diff --git a/test/vmess_test.go b/test/vmess_test.go new file mode 100644 index 00000000..12cba263 --- /dev/null +++ b/test/vmess_test.go @@ -0,0 +1,129 @@ +package main + +import ( + "net/netip" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + + "github.com/gofrs/uuid" + "github.com/stretchr/testify/require" +) + +func TestVMessSelf(t *testing.T) { + t.Parallel() + for _, security := range []string{ + "zero", + } { + t.Run(security, func(t *testing.T) { + testVMessSelf0(t, security) + }) + } + for _, security := range []string{ + "aes-128-gcm", "chacha20-poly1305", "aes-128-cfb", + } { + t.Run(security, func(t *testing.T) { + testVMessSelf1(t, security) + }) + } +} + +func testVMessSelf0(t *testing.T, security string) { + t.Parallel() + user, err := uuid.DefaultGenerator.NewV4() + require.NoError(t, err) + t.Run("default", func(t *testing.T) { + testVMessSelf2(t, security, user.String(), false, false) + }) + t.Run("padding", func(t *testing.T) { + testVMessSelf2(t, security, user.String(), true, false) + }) +} + +func testVMessSelf1(t *testing.T, security string) { + t.Parallel() + user, err := uuid.DefaultGenerator.NewV4() + require.NoError(t, err) + t.Run("default", func(t *testing.T) { + testVMessSelf2(t, security, user.String(), false, false) + }) + t.Run("padding", func(t *testing.T) { + testVMessSelf2(t, security, user.String(), true, false) + }) + t.Run("authid", func(t *testing.T) { + testVMessSelf2(t, security, user.String(), false, true) + }) + t.Run("padding-authid", func(t *testing.T) { + testVMessSelf2(t, security, user.String(), true, true) + }) +} + +func testVMessSelf2(t *testing.T, security string, uuid string, globalPadding bool, authenticatedLength bool) { + t.Parallel() + serverPort := mkPort(t) + clientPort := mkPort(t) + testPort := mkPort(t) + startInstance(t, option.Options{ + Log: &option.LogOption{ + 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.TypeVMess, + VMessOptions: option.VMessInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.VMessUser{ + { + Name: "sekai", + UUID: uuid, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeVMess, + Tag: "vmess-out", + VMessOptions: option.VMessOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Security: security, + UUID: uuid, + GlobalPadding: globalPadding, + AuthenticatedLength: authenticatedLength, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "vmess-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +}