From 8df547fabb690348e4fde21faf9eacb60696b13a Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 23 Dec 2024 22:28:31 -0500 Subject: [PATCH] Revamp config --- client/go/outline/client.go | 43 ++---- client/go/outline/client_test.go | 18 +-- client/go/outline/config/config.go | 73 ++++++++++ client/go/outline/config/endpoint.go | 48 ++++--- client/go/outline/config/module.go | 132 +++++++++++++------ client/go/outline/config/module_test.go | 9 +- client/go/outline/config/provider.go | 124 ----------------- client/go/outline/config/shadowsocks.go | 120 +++++++++-------- client/go/outline/connectivity.go | 4 +- client/go/outline/electron/main.go | 4 +- client/go/outline/method_channel.go | 6 +- client/go/outline/tun2socks/tunnel_darwin.go | 4 +- go.mod | 2 +- 13 files changed, 296 insertions(+), 291 deletions(-) delete mode 100644 client/go/outline/config/provider.go diff --git a/client/go/outline/client.go b/client/go/outline/client.go index 514bc21b77e..3ca0666e1bb 100644 --- a/client/go/outline/client.go +++ b/client/go/outline/client.go @@ -22,27 +22,27 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport" ) -// Client provides a transparent container for [transport.StreamDialer] and [transport.PacketListener] +// Transport provides a transparent container for [transport.StreamDialer] and [transport.PacketListener] // that is exportable (as an opaque object) via gobind. // It's used by the connectivity test and the tun2socks handlers. -type Client struct { +type Transport struct { *config.Dialer[transport.StreamConn] *config.PacketListener } -// NewClientResult represents the result of [NewClientAndReturnError]. +// NewTransportResult represents the result of [NewClientAndReturnError]. // // We use a struct instead of a tuple to preserve a strongly typed error that gobind recognizes. -type NewClientResult struct { - Client *Client - Error *platerrors.PlatformError +type NewTransportResult struct { + Transport *Transport + Error *platerrors.PlatformError } -// NewClient creates a new Outline client from a configuration string. -func NewClient(transportConfig string) *NewClientResult { +// NewTransport creates a new Outline client from a configuration string. +func NewTransport(transportConfig string) *NewTransportResult { transportYAML, err := config.ParseConfigYAML(transportConfig) if err != nil { - return &NewClientResult{ + return &NewTransportResult{ Error: &platerrors.PlatformError{ Code: platerrors.IllegalConfig, Message: "config is not valid YAML", @@ -51,33 +51,18 @@ func NewClient(transportConfig string) *NewClientResult { } } - providers := config.RegisterDefaultProviders(config.NewProviderContainer()) - - streamDialer, err := providers.StreamDialers.NewInstance(context.Background(), transportYAML) - if err != nil { - return &NewClientResult{ - Error: &platerrors.PlatformError{ - Code: platerrors.IllegalConfig, - Message: "failed to create TCP handler", - Details: platerrors.ErrorDetails{"handler": "tcp"}, - Cause: platerrors.ToPlatformError(err), - }, - } - } - - packetListener, err := providers.PacketListeners.NewInstance(context.Background(), transportYAML) + transportPair, err := config.NewDefaultTransportProvider().NewInstance(context.Background(), transportYAML) if err != nil { - return &NewClientResult{ + return &NewTransportResult{ Error: &platerrors.PlatformError{ Code: platerrors.IllegalConfig, - Message: "failed to create UDP handler", - Details: platerrors.ErrorDetails{"handler": "udp"}, + Message: "failed to create transport", Cause: platerrors.ToPlatformError(err), }, } } - return &NewClientResult{ - Client: &Client{Dialer: streamDialer, PacketListener: packetListener}, + return &NewTransportResult{ + Transport: &Transport{Dialer: transportPair.StreamDialer, PacketListener: transportPair.PacketListener}, } } diff --git a/client/go/outline/client_test.go b/client/go/outline/client_test.go index 24c3f105673..8d7181d6803 100644 --- a/client/go/outline/client_test.go +++ b/client/go/outline/client_test.go @@ -59,7 +59,7 @@ password: SECRET`, firstHop: "example.com:4321", }, { name: "Explicit endpoint", - input: `$type: ss + input: `$type: shadowsocks endpoint: $type: dial address: example.com:4321 @@ -68,7 +68,7 @@ secret: SECRET`, firstHop: "example.com:4321", }, { name: "Multi-hop", - input: `$type: ss + input: `$type: shadowsocks endpoint: $type: dial address: exit.example.com:4321 @@ -80,10 +80,10 @@ secret: SECRET`, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := NewClient(tt.input) - require.Nil(t, result.Error) - require.Equal(t, tt.firstHop, result.Client.Dialer.FirstHop) - require.Equal(t, tt.firstHop, result.Client.PacketListener.FirstHop) + result := NewTransport(tt.input) + require.Nil(t, result.Error, "Got %v", result.Error) + require.Equal(t, tt.firstHop, result.Transport.Dialer.FirstHop) + require.Equal(t, tt.firstHop, result.Transport.PacketListener.FirstHop) }) } } @@ -140,9 +140,9 @@ func Test_NewClientFromJSON_Errors(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NewClient(tt.input) - if got.Error == nil || got.Client != nil { - t.Errorf("NewClientFromJSON() expects an error, got = %v", got.Client) + got := NewTransport(tt.input) + if got.Error == nil || got.Transport != nil { + t.Errorf("NewClientFromJSON() expects an error, got = %v", got.Transport) return } }) diff --git a/client/go/outline/config/config.go b/client/go/outline/config/config.go index a2f2288aeaf..bdd385ce2d5 100644 --- a/client/go/outline/config/config.go +++ b/client/go/outline/config/config.go @@ -16,11 +16,18 @@ package config import ( "bytes" + "context" + "fmt" "gopkg.in/yaml.v3" ) +const ConfigTypeKey = "$type" + type ConfigNode any +type ConfigFunction func(ctx context.Context, input any) (any, error) + +type ParseFunc[ObjectType any] func(ctx context.Context, input any) (ObjectType, error) func ParseConfigYAML(configText string) (ConfigNode, error) { var node any @@ -47,3 +54,69 @@ func mapToAny(in map[string]any, out any) error { decoder.KnownFields(true) return decoder.Decode(out) } + +type TypeProvider[T any] struct { + fallbackHandler ParseFunc[T] + parsers map[string]func(context.Context, map[string]any) (T, error) +} + +var _ ParseFunc[any] = (*TypeProvider[any])(nil).NewInstance + +func NewTypeProvider[T any](fallbackHandler func(context.Context, any) (T, error)) *TypeProvider[T] { + return &TypeProvider[T]{ + fallbackHandler: fallbackHandler, + parsers: make(map[string]func(context.Context, map[string]any) (T, error)), + } +} + +func (p *TypeProvider[T]) NewInstance(ctx context.Context, input any) (T, error) { + var zero T + + // Iterate while the input is a function call. + for { + inMap, ok := input.(map[string]any) + if !ok { + break + } + parserNameAny, ok := inMap[ConfigTypeKey] + if !ok { + break + } + parserName, ok := parserNameAny.(string) + if !ok { + return zero, fmt.Errorf("parser name must be a string, found \"%T\"", parserNameAny) + } + parser, ok := p.parsers[parserName] + if !ok { + return zero, fmt.Errorf("provider \"%v\" for type %T is not registered", parserName, zero) + } + + // $type is embedded in the value: {$type: ..., ...}. + // Need to copy value and remove the type directive. + inputCopy := make(map[string]any, len(inMap)) + for k, v := range inMap { + if k == ConfigTypeKey { + continue + } + inputCopy[k] = v + } + + var err error + input, err = parser(ctx, inputCopy) + if err != nil { + return zero, fmt.Errorf("parser \"%v\" failed: %w", parserName, err) + } + } + + typed, ok := input.(T) + if ok { + return typed, nil + } + + // Input is an intermediate type. We need a fallback handler. + return p.fallbackHandler(ctx, input) +} + +func (p *TypeProvider[T]) RegisterParser(name string, function func(context.Context, map[string]any) (T, error)) { + p.parsers[name] = function +} diff --git a/client/go/outline/config/endpoint.go b/client/go/outline/config/endpoint.go index d5a19509c2d..86136cc9f3d 100644 --- a/client/go/outline/config/endpoint.go +++ b/client/go/outline/config/endpoint.go @@ -24,37 +24,35 @@ import ( type DialEndpointConfig struct { Address string - Dialer ConfigNode + Dialer any } -func registerDirectDialEndpoint[ConnType any](r TypeRegistry[*Endpoint[ConnType]], typeID string, newDialer BuildFunc[*Dialer[ConnType]]) { - r.RegisterType(typeID, func(ctx context.Context, config ConfigNode) (*Endpoint[ConnType], error) { - if config == nil { - return nil, errors.New("endpoint config cannot be nil") - } +func newDirectDialerEndpoint[ConnType any](ctx context.Context, config any, newDialer ParseFunc[*Dialer[ConnType]]) (*Endpoint[ConnType], error) { + if config == nil { + return nil, errors.New("endpoint config cannot be nil") + } - dialParams, err := parseEndpointConfig(config) - if err != nil { - return nil, err - } + dialParams, err := parseEndpointConfig(config) + if err != nil { + return nil, err + } - dialer, err := newDialer(ctx, dialParams.Dialer) - if err != nil { - return nil, fmt.Errorf("failed to create sub-dialer: %w", err) - } + dialer, err := newDialer(ctx, dialParams.Dialer) + if err != nil { + return nil, fmt.Errorf("failed to create sub-dialer: %w", err) + } - endpoint := &Endpoint[ConnType]{ - Connect: func(ctx context.Context) (ConnType, error) { - return dialer.Dial(ctx, dialParams.Address) + endpoint := &Endpoint[ConnType]{ + Connect: func(ctx context.Context) (ConnType, error) { + return dialer.Dial(ctx, dialParams.Address) - }, - ConnectionProviderInfo: dialer.ConnectionProviderInfo, - } - if dialer.ConnType == ConnTypeDirect { - endpoint.ConnectionProviderInfo.FirstHop = dialParams.Address - } - return endpoint, nil - }) + }, + ConnectionProviderInfo: dialer.ConnectionProviderInfo, + } + if dialer.ConnType == ConnTypeDirect { + endpoint.ConnectionProviderInfo.FirstHop = dialParams.Address + } + return endpoint, nil } func parseEndpointConfig(node ConfigNode) (*DialEndpointConfig, error) { diff --git a/client/go/outline/config/module.go b/client/go/outline/config/module.go index 43d47e7e3ba..94f838f92d7 100644 --- a/client/go/outline/config/module.go +++ b/client/go/outline/config/module.go @@ -55,46 +55,104 @@ type Endpoint[ConnType any] struct { Connect ConnectFunc[ConnType] } -// ProviderContainer contains providers for the creation of network objects based on a config. The config is -// extensible by registering providers for different config subtypes. -type ProviderContainer struct { - StreamDialers *ExtensibleProvider[*Dialer[transport.StreamConn]] - PacketDialers *ExtensibleProvider[*Dialer[net.Conn]] - PacketListeners *ExtensibleProvider[*PacketListener] - StreamEndpoints *ExtensibleProvider[*Endpoint[transport.StreamConn]] - PacketEndpoints *ExtensibleProvider[*Endpoint[net.Conn]] +type TransportPair struct { + StreamDialer *Dialer[transport.StreamConn] + PacketListener *PacketListener } -// NewProviderContainer creates a [ProviderContainer] with the base instances properly initialized. -func NewProviderContainer() *ProviderContainer { - defaultStreamDialer := &Dialer[transport.StreamConn]{ConnectionProviderInfo{ConnTypeDirect, ""}, (&transport.TCPDialer{}).DialStream} - defaultPacketDialer := &Dialer[net.Conn]{ConnectionProviderInfo{ConnTypeDirect, ""}, (&transport.UDPDialer{}).DialPacket} - - return &ProviderContainer{ - StreamDialers: NewExtensibleProvider(defaultStreamDialer), - PacketDialers: NewExtensibleProvider(defaultPacketDialer), - PacketListeners: NewExtensibleProvider(&PacketListener{ConnectionProviderInfo{ConnTypeDirect, ""}, &transport.UDPListener{}}), - StreamEndpoints: NewExtensibleProvider[*Endpoint[transport.StreamConn]](nil), - PacketEndpoints: NewExtensibleProvider[*Endpoint[net.Conn]](nil), - } -} - -// RegisterDefaultProviders registers a set of default providers with the providers in [ProviderContainer]. -func RegisterDefaultProviders(c *ProviderContainer) *ProviderContainer { - registerDirectDialEndpoint(c.StreamEndpoints, "string", c.StreamDialers.NewInstance) - registerDirectDialEndpoint(c.StreamEndpoints, "dial", c.StreamDialers.NewInstance) - registerDirectDialEndpoint(c.PacketEndpoints, "string", c.PacketDialers.NewInstance) - registerDirectDialEndpoint(c.PacketEndpoints, "dial", c.PacketDialers.NewInstance) +var _ transport.StreamDialer = (*TransportPair)(nil) +var _ transport.PacketListener = (*TransportPair)(nil) - registerShadowsocksStreamDialer(c.StreamDialers, ProviderTypeDefault, c.StreamEndpoints.NewInstance) - registerShadowsocksStreamDialer(c.StreamDialers, "ss", c.StreamEndpoints.NewInstance) - registerShadowsocksStreamDialer(c.StreamDialers, "string", c.StreamEndpoints.NewInstance) +func (t *TransportPair) DialStream(ctx context.Context, address string) (transport.StreamConn, error) { + return t.StreamDialer.Dial(ctx, address) +} - registerShadowsocksPacketDialer(c.PacketDialers, "ss", c.PacketEndpoints.NewInstance) - registerShadowsocksPacketDialer(c.PacketDialers, "string", c.PacketEndpoints.NewInstance) +func (t *TransportPair) ListenPacket(ctx context.Context) (net.PacketConn, error) { + return t.PacketListener.ListenPacket(ctx) +} - registerShadowsocksPacketListener(c.PacketListeners, ProviderTypeDefault, c.PacketEndpoints.NewInstance) - registerShadowsocksPacketListener(c.PacketListeners, "ss", c.PacketEndpoints.NewInstance) - registerShadowsocksPacketListener(c.PacketListeners, "string", c.PacketEndpoints.NewInstance) - return c +// // NewClientProvider creates a [ProviderContainer] with the base instances properly initialized. +// func NewClientProvider() *ExtensibleProvider[*TransportClient], FunctionRegistry[] { +// clients := NewExtensibleProvider[*TransportClient](nil) +// return clients + +// defaultStreamDialer := &Dialer[transport.StreamConn]{ConnectionProviderInfo{ConnTypeDirect, ""}, (&transport.TCPDialer{}).DialStream} +// defaultPacketDialer := &Dialer[net.Conn]{ConnectionProviderInfo{ConnTypeDirect, ""}, (&transport.UDPDialer{}).DialPacket} + +// return &ProviderContainer{ +// StreamDialers: NewExtensibleProvider(defaultStreamDialer), +// PacketDialers: NewExtensibleProvider(defaultPacketDialer), +// PacketListeners: NewExtensibleProvider(&PacketListener{ConnectionProviderInfo{ConnTypeDirect, ""}, &transport.UDPListener{}}), +// StreamEndpoints: NewExtensibleProvider[*Endpoint[transport.StreamConn]](nil), +// PacketEndpoints: NewExtensibleProvider[*Endpoint[net.Conn]](nil), +// } +// } + +// // RegisterDefaultProviders registers a set of default providers with the providers in [ProviderContainer]. +// func RegisterDefaultProviders(c *ProviderContainer) *ProviderContainer { +// registerDirectDialEndpoint(c.StreamEndpoints, "string", c.StreamDialers.NewInstance) +// registerDirectDialEndpoint(c.StreamEndpoints, "dial", c.StreamDialers.NewInstance) +// registerDirectDialEndpoint(c.PacketEndpoints, "string", c.PacketDialers.NewInstance) +// registerDirectDialEndpoint(c.PacketEndpoints, "dial", c.PacketDialers.NewInstance) + +// registerShadowsocksStreamDialer(c.StreamDialers, ProviderTypeDefault, c.StreamEndpoints.NewInstance) +// registerShadowsocksStreamDialer(c.StreamDialers, "ss", c.StreamEndpoints.NewInstance) +// registerShadowsocksStreamDialer(c.StreamDialers, "string", c.StreamEndpoints.NewInstance) + +// registerShadowsocksPacketDialer(c.PacketDialers, "ss", c.PacketEndpoints.NewInstance) +// registerShadowsocksPacketDialer(c.PacketDialers, "string", c.PacketEndpoints.NewInstance) + +// registerShadowsocksPacketListener(c.PacketListeners, ProviderTypeDefault, c.PacketEndpoints.NewInstance) +// registerShadowsocksPacketListener(c.PacketListeners, "ss", c.PacketEndpoints.NewInstance) +// registerShadowsocksPacketListener(c.PacketListeners, "string", c.PacketEndpoints.NewInstance) +// return c +// } + +func NewDefaultTransportProvider() *TypeProvider[*TransportPair] { + var streamEndpoints *TypeProvider[*Endpoint[transport.StreamConn]] + var packetEndpoints *TypeProvider[*Endpoint[net.Conn]] + + streamDialers := NewTypeProvider(func(ctx context.Context, input any) (*Dialer[transport.StreamConn], error) { + if input == nil { + return &Dialer[transport.StreamConn]{ConnectionProviderInfo{ConnTypeDirect, ""}, (&transport.TCPDialer{}).DialStream}, nil + } + return newShadowsocksStreamDialer(ctx, input, streamEndpoints.NewInstance) + }) + + packetDialers := NewTypeProvider(func(ctx context.Context, input any) (*Dialer[net.Conn], error) { + if input == nil { + return &Dialer[net.Conn]{ConnectionProviderInfo{ConnTypeDirect, ""}, (&transport.UDPDialer{}).DialPacket}, nil + } + return newShadowsocksPacketDialer(ctx, input, packetEndpoints.NewInstance) + }) + + streamEndpoints = NewTypeProvider(func(ctx context.Context, input any) (*Endpoint[transport.StreamConn], error) { + return newDirectDialerEndpoint(ctx, input, streamDialers.NewInstance) + }) + streamEndpoints.RegisterParser("dial", func(ctx context.Context, input map[string]any) (*Endpoint[transport.StreamConn], error) { + return newDirectDialerEndpoint(ctx, input, streamDialers.NewInstance) + }) + + packetEndpoints = NewTypeProvider(func(ctx context.Context, input any) (*Endpoint[net.Conn], error) { + return newDirectDialerEndpoint(ctx, input, packetDialers.NewInstance) + }) + packetEndpoints.RegisterParser("dial", func(ctx context.Context, input map[string]any) (*Endpoint[net.Conn], error) { + return newDirectDialerEndpoint(ctx, input, packetDialers.NewInstance) + }) + + transports := NewTypeProvider(func(ctx context.Context, input any) (*TransportPair, error) { + return newShadowsocksTransport(ctx, input, streamEndpoints.NewInstance, packetEndpoints.NewInstance) + }) + + // Shadowsocks support. + streamDialers.RegisterParser("shadowsocks", func(ctx context.Context, input map[string]any) (*Dialer[transport.StreamConn], error) { + return newShadowsocksStreamDialer(ctx, input, streamEndpoints.NewInstance) + }) + packetDialers.RegisterParser("shadowsocks", func(ctx context.Context, input map[string]any) (*Dialer[net.Conn], error) { + return newShadowsocksPacketDialer(ctx, input, packetEndpoints.NewInstance) + }) + transports.RegisterParser("shadowsocks", func(ctx context.Context, input map[string]any) (*TransportPair, error) { + return newShadowsocksTransport(ctx, input, streamEndpoints.NewInstance, packetEndpoints.NewInstance) + }) + return transports } diff --git a/client/go/outline/config/module_test.go b/client/go/outline/config/module_test.go index cba1551e21a..bc06390cad6 100644 --- a/client/go/outline/config/module_test.go +++ b/client/go/outline/config/module_test.go @@ -14,6 +14,7 @@ package config +/* import ( "context" "testing" @@ -27,7 +28,7 @@ import ( // - Websocket endpoint POC func TestRegisterDefaultProviders(t *testing.T) { - providers := RegisterDefaultProviders(NewProviderContainer()) + providers := RegisterDefaultProviders(NewClientProvider()) node, err := ParseConfigYAML(` $type: ss @@ -45,7 +46,7 @@ secret: SECRET`) } func TestRegisterParseURL(t *testing.T) { - providers := RegisterDefaultProviders(NewProviderContainer()) + providers := RegisterDefaultProviders(NewClientProvider()) node, err := ParseConfigYAML(`ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpaTXJSMW92ZmRBaEQ@example.com:4321/#My%20Server`) require.NoError(t, err) @@ -59,7 +60,7 @@ func TestRegisterParseURL(t *testing.T) { } func TestRegisterParseURLInQuotes(t *testing.T) { - providers := RegisterDefaultProviders(NewProviderContainer()) + providers := RegisterDefaultProviders(NewClientProvider()) node, err := ParseConfigYAML(`"ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpaTXJSMW92ZmRBaEQ@example.com:4321/#My%20Server"`) require.NoError(t, err) @@ -71,3 +72,5 @@ func TestRegisterParseURLInQuotes(t *testing.T) { require.Equal(t, "example.com:4321", d.FirstHop) require.Equal(t, ConnTypeTunneled, d.ConnType) } + +*/ diff --git a/client/go/outline/config/provider.go b/client/go/outline/config/provider.go deleted file mode 100644 index 6b0551faef1..00000000000 --- a/client/go/outline/config/provider.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2024 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -import ( - "context" - "fmt" - "reflect" -) - -const ( - // Provider type for nil configs. - ProviderTypeNil = "nil" - // Provider type for when an explicit type is missing. - ProviderTypeDefault = "" -) - -const ( - ConfigTypeKey = "$type" - ConfigValueKey = "$value" -) - -type BuildFunc[ObjectType any] func(ctx context.Context, config ConfigNode) (ObjectType, error) - -// TypeRegistry registers config types. -type TypeRegistry[ObjectType any] interface { - RegisterType(subtype string, newInstance BuildFunc[ObjectType]) -} - -// ExtensibleProvider creates instances of ObjectType in a way that can be extended via its [TypeRegistry] interface. -type ExtensibleProvider[ObjectType comparable] struct { - builders map[string]BuildFunc[ObjectType] -} - -var ( - _ BuildFunc[any] = (*ExtensibleProvider[any])(nil).NewInstance - _ TypeRegistry[any] = (*ExtensibleProvider[any])(nil) -) - -// NewExtensibleProvider creates an [ExtensibleProvider]. -func NewExtensibleProvider[ObjectType comparable](baseInstance ObjectType) *ExtensibleProvider[ObjectType] { - p := &ExtensibleProvider[ObjectType]{ - builders: make(map[string]BuildFunc[ObjectType]), - } - var zero ObjectType - if baseInstance != zero { - p.RegisterType(ProviderTypeNil, func(ctx context.Context, config ConfigNode) (ObjectType, error) { return baseInstance, nil }) - } - return p -} - -func (p *ExtensibleProvider[ObjectType]) ensureBuildersMap() map[string]BuildFunc[ObjectType] { - if p.builders == nil { - p.builders = make(map[string]BuildFunc[ObjectType]) - } - return p.builders -} - -// RegisterType will register a factory for the given subtype. -func (p *ExtensibleProvider[ObjectType]) RegisterType(subtype string, newInstance BuildFunc[ObjectType]) { - p.ensureBuildersMap()[subtype] = newInstance -} - -// NewInstance creates a new instance of ObjectType according to the config. -func (p *ExtensibleProvider[ObjectType]) NewInstance(ctx context.Context, config ConfigNode) (ObjectType, error) { - var zero ObjectType - var typeName string - var normConfig any - switch typed := config.(type) { - case nil: - typeName = ProviderTypeNil - normConfig = nil - - case map[string]any: - if typeAny, ok := typed[ConfigTypeKey]; ok { - typeName, ok = typeAny.(string) - if !ok { - return zero, fmt.Errorf("subtype must be a string, found %T", typeAny) - } - } else { - typeName = ProviderTypeDefault - } - - // Value is an explicit field: {$type: ..., $value: ...}. - var ok bool - normConfig, ok = typed[ConfigValueKey] - if ok { - break - } - - // $type is embedded in the value: {$type: ..., ...}. - // Need to copy value and remove the type directive. - configCopy := make(map[string]any, len(typed)) - for k, v := range typed { - if len(k) > 0 && k[0] == '$' { - continue - } - configCopy[k] = v - } - normConfig = configCopy - - default: - typeName = reflect.TypeOf(typed).String() - normConfig = typed - } - - newInstance, ok := p.ensureBuildersMap()[typeName] - if !ok { - return zero, fmt.Errorf("config subtype '%v' is not registered", typeName) - } - return newInstance(ctx, normConfig) -} diff --git a/client/go/outline/config/shadowsocks.go b/client/go/outline/config/shadowsocks.go index b2ebf623bdc..e10b4ab998b 100644 --- a/client/go/outline/config/shadowsocks.go +++ b/client/go/outline/config/shadowsocks.go @@ -43,65 +43,77 @@ type LegacyShadowsocksConfig struct { Prefix string } -func registerShadowsocksStreamDialer(r TypeRegistry[*Dialer[transport.StreamConn]], typeID string, newSE BuildFunc[*Endpoint[transport.StreamConn]]) { - r.RegisterType(typeID, func(ctx context.Context, config ConfigNode) (*Dialer[transport.StreamConn], error) { - params, err := newShadowsocksParams(config) - if err != nil { - return nil, err - } - endpoint, err := newSE(ctx, params.Endpoint) - if err != nil { - return nil, err - } - dialer, err := shadowsocks.NewStreamDialer(transport.FuncStreamEndpoint(endpoint.Connect), params.Key) - if err != nil { - return nil, err - } - if params.SaltGenerator != nil { - dialer.SaltGenerator = params.SaltGenerator - } - return &Dialer[transport.StreamConn]{ConnectionProviderInfo{ConnTypeTunneled, endpoint.FirstHop}, dialer.DialStream}, nil - }) +func newShadowsocksTransport(ctx context.Context, config ConfigNode, newSE ParseFunc[*Endpoint[transport.StreamConn]], newPE ParseFunc[*Endpoint[net.Conn]]) (*TransportPair, error) { + params, err := newShadowsocksParams(config) + if err != nil { + return nil, err + } + + se, err := newSE(ctx, params.Endpoint) + if err != nil { + return nil, fmt.Errorf("failed to create StreamEndpoint: %w", err) + } + sd, err := shadowsocks.NewStreamDialer(transport.FuncStreamEndpoint(se.Connect), params.Key) + if err != nil { + return nil, fmt.Errorf("failed to create StreamDialer: %w", err) + } + if params.SaltGenerator != nil { + sd.SaltGenerator = params.SaltGenerator + } + + pe, err := newPE(ctx, params.Endpoint) + if err != nil { + return nil, fmt.Errorf("failed to create PacketEndpoint: %w", err) + } + pl, err := shadowsocks.NewPacketListener(transport.FuncPacketEndpoint(pe.Connect), params.Key) + if err != nil { + return nil, fmt.Errorf("failed to create PacketListener: %w", err) + } + return &TransportPair{ + &Dialer[transport.StreamConn]{ConnectionProviderInfo{ConnTypeTunneled, se.FirstHop}, sd.DialStream}, + &PacketListener{ConnectionProviderInfo{ConnTypeTunneled, pe.FirstHop}, pl}, + }, nil } -func registerShadowsocksPacketDialer(r TypeRegistry[*Dialer[net.Conn]], typeID string, newPE BuildFunc[*Endpoint[net.Conn]]) { - r.RegisterType(typeID, func(ctx context.Context, config ConfigNode) (*Dialer[net.Conn], error) { - params, err := newShadowsocksParams(config) - if err != nil { - return nil, err - } - endpoint, err := newPE(ctx, params.Endpoint) - if err != nil { - return nil, err - } - pl, err := shadowsocks.NewPacketListener(transport.FuncPacketEndpoint(endpoint.Connect), params.Key) - if err != nil { - return nil, err - } - // TODO: support UDP prefix. - dialer := transport.PacketListenerDialer{Listener: pl} - return &Dialer[net.Conn]{ConnectionProviderInfo{ConnTypeTunneled, endpoint.FirstHop}, dialer.DialPacket}, nil +func newShadowsocksStreamDialer(ctx context.Context, config ConfigNode, newSE ParseFunc[*Endpoint[transport.StreamConn]]) (*Dialer[transport.StreamConn], error) { + params, err := newShadowsocksParams(config) + if err != nil { + return nil, err + } - }) + se, err := newSE(ctx, params.Endpoint) + if err != nil { + return nil, fmt.Errorf("failed to create StreamEndpoint: %w", err) + } + sd, err := shadowsocks.NewStreamDialer(transport.FuncStreamEndpoint(se.Connect), params.Key) + if err != nil { + return nil, fmt.Errorf("failed to create StreamDialer: %w", err) + } + if params.SaltGenerator != nil { + sd.SaltGenerator = params.SaltGenerator + } + + return &Dialer[transport.StreamConn]{ConnectionProviderInfo{ConnTypeTunneled, se.FirstHop}, sd.DialStream}, nil } -func registerShadowsocksPacketListener(r TypeRegistry[*PacketListener], typeID string, newPE BuildFunc[*Endpoint[net.Conn]]) { - r.RegisterType(typeID, func(ctx context.Context, config ConfigNode) (*PacketListener, error) { - params, err := newShadowsocksParams(config) - if err != nil { - return nil, err - } - endpoint, err := newPE(ctx, params.Endpoint) - if err != nil { - return nil, err - } - listener, err := shadowsocks.NewPacketListener(transport.FuncPacketEndpoint(endpoint.Connect), params.Key) - if err != nil { - return nil, err - } - // TODO: support UDP prefix. - return &PacketListener{ConnectionProviderInfo{ConnTypeTunneled, endpoint.FirstHop}, listener}, nil - }) +func newShadowsocksPacketDialer(ctx context.Context, config ConfigNode, newPE ParseFunc[*Endpoint[net.Conn]]) (*Dialer[net.Conn], error) { + params, err := newShadowsocksParams(config) + if err != nil { + return nil, err + } + + pe, err := newPE(ctx, params.Endpoint) + if err != nil { + return nil, fmt.Errorf("failed to create PacketEndpoint: %w", err) + } + pl, err := shadowsocks.NewPacketListener(transport.FuncPacketEndpoint(pe.Connect), params.Key) + if err != nil { + return nil, err + } + // TODO: support UDP prefix. + pd := transport.PacketListenerDialer{Listener: pl} + + return &Dialer[net.Conn]{ConnectionProviderInfo{ConnTypeTunneled, pe.FirstHop}, pd.DialPacket}, nil } type shadowsocksParams struct { diff --git a/client/go/outline/connectivity.go b/client/go/outline/connectivity.go index a506e78ba54..e5b800cfc6b 100644 --- a/client/go/outline/connectivity.go +++ b/client/go/outline/connectivity.go @@ -35,12 +35,12 @@ type TCPAndUDPConnectivityResult struct { TCPError, UDPError *platerrors.PlatformError } -// CheckTCPAndUDPConnectivity checks if a [Client] can relay TCP and UDP traffic. +// CheckTCPAndUDPConnectivity checks if a [Transport] can relay TCP and UDP traffic. // // It parallelizes the execution of TCP and UDP checks, and returns a [TCPAndUDPConnectivityResult] // containing a TCP error and a UDP error. // If the connectivity check was successful, the corresponding error field will be nil. -func CheckTCPAndUDPConnectivity(client *Client) *TCPAndUDPConnectivityResult { +func CheckTCPAndUDPConnectivity(client *Transport) *TCPAndUDPConnectivityResult { // Start asynchronous UDP support check. udpErrChan := make(chan error) go func() { diff --git a/client/go/outline/electron/main.go b/client/go/outline/electron/main.go index 34e5ce7da67..31fd62d7788 100644 --- a/client/go/outline/electron/main.go +++ b/client/go/outline/electron/main.go @@ -116,11 +116,11 @@ func main() { if len(*args.transportConfig) == 0 { printErrorAndExit(platerrors.PlatformError{Code: platerrors.IllegalConfig, Message: "transport config missing"}, exitCodeFailure) } - clientResult := outline.NewClient(*args.transportConfig) + clientResult := outline.NewTransport(*args.transportConfig) if clientResult.Error != nil { printErrorAndExit(clientResult.Error, exitCodeFailure) } - client := clientResult.Client + client := clientResult.Transport if *args.checkConnectivity { result := outline.CheckTCPAndUDPConnectivity(client) diff --git a/client/go/outline/method_channel.go b/client/go/outline/method_channel.go index 2e096897793..9bc5cad9c9c 100644 --- a/client/go/outline/method_channel.go +++ b/client/go/outline/method_channel.go @@ -53,14 +53,14 @@ func InvokeMethod(method string, input string) *InvokeMethodResult { } case MethodGetFirstHop: - result := NewClient(input) + result := NewTransport(input) if result.Error != nil { return &InvokeMethodResult{ Error: result.Error, } } - streamFirstHop := result.Client.Dialer.ConnectionProviderInfo.FirstHop - packetFirstHop := result.Client.PacketListener.ConnectionProviderInfo.FirstHop + streamFirstHop := result.Transport.Dialer.ConnectionProviderInfo.FirstHop + packetFirstHop := result.Transport.PacketListener.ConnectionProviderInfo.FirstHop firstHop := "" if streamFirstHop == packetFirstHop { firstHop = streamFirstHop diff --git a/client/go/outline/tun2socks/tunnel_darwin.go b/client/go/outline/tun2socks/tunnel_darwin.go index 81023ec4412..0135faf8f96 100644 --- a/client/go/outline/tun2socks/tunnel_darwin.go +++ b/client/go/outline/tun2socks/tunnel_darwin.go @@ -47,11 +47,11 @@ func init() { // Returns an OutlineTunnel instance that should be used to input packets to the tunnel. // // `tunWriter` is used to output packets to the TUN (VPN). -// `client` is the Outline client (created by [outline.NewClient]). +// `client` is the Outline client (created by [outline.NewTransport]). // `isUDPEnabled` indicates whether the tunnel and/or network enable UDP proxying. // // Sets an error if the tunnel fails to connect. -func ConnectOutlineTunnel(tunWriter TunWriter, client *outline.Client, isUDPEnabled bool) *ConnectOutlineTunnelResult { +func ConnectOutlineTunnel(tunWriter TunWriter, client *outline.Transport, isUDPEnabled bool) *ConnectOutlineTunnelResult { if tunWriter == nil { return &ConnectOutlineTunnelResult{Error: &platerrors.PlatformError{ Code: platerrors.InternalError, diff --git a/go.mod b/go.mod index 808522dec08..7ac0da4dc38 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Jigsaw-Code/outline-apps -go 1.21 +go 1.22 require ( github.com/Jigsaw-Code/outline-sdk v0.0.14-0.20240216220040-f741c57bf854