From b15bb9b561928fbb07c20bc3290ca8f853de7994 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 25 Oct 2024 18:15:26 -0400 Subject: [PATCH 01/35] More extensible config --- x/configurl/config.go | 235 ++++++++++++++++++++--------------- x/configurl/dns.go | 56 ++++++--- x/configurl/override.go | 58 +++++---- x/configurl/override_test.go | 10 +- x/configurl/socks5.go | 65 +++++----- x/configurl/split.go | 24 ++-- 6 files changed, 259 insertions(+), 189 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 0c3c0b97..88708093 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -15,8 +15,10 @@ package configurl import ( + "context" "errors" "fmt" + "net" "net/url" "strconv" "strings" @@ -28,38 +30,87 @@ import ( // ConfigToDialer enables the creation of stream and packet dialers based on a config. The config is // extensible by registering wrappers for config subtypes. type ConfigToDialer struct { - // Base StreamDialer to create direct stream connections. If you need direct stream connections, this must not be nil. - BaseStreamDialer transport.StreamDialer - // Base PacketDialer to create direct packet connections. If you need direct packet connections, this must not be nil. - BasePacketDialer transport.PacketDialer - sdBuilders map[string]NewStreamDialerFunc - pdBuilders map[string]NewPacketDialerFunc + NewBaseStreamDialer func(ctx context.Context) (transport.StreamDialer, error) + NewBasePacketDialer func(ctx context.Context) (transport.PacketDialer, error) + NewBasePacketConn func(ctx context.Context) (net.PacketConn, error) + + sdBuilders map[string]NewStreamDialerFunc + pdBuilders map[string]NewPacketDialerFunc + pcBuilders map[string]NewPacketConnFunc +} + +type ConfigToStreamDialer interface { + NewStreamDialer(ctx context.Context, config *Config) (transport.StreamDialer, error) } -// NewStreamDialerFunc wraps a Dialer based on the wrapConfig. The innerSD and innerPD functions can provide a base Stream and Packet Dialers if needed. -type NewStreamDialerFunc func(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), wrapConfig *url.URL) (transport.StreamDialer, error) +// NewStreamDialerFunc creates a [transport.StreamDialer] based on the config. +type NewStreamDialerFunc func(ctx context.Context, config *Config) (transport.StreamDialer, error) + +// NewPacketDialerFunc creates a [transport.PacketDialer] based on the config. +type NewPacketDialerFunc func(ctx context.Context, config *Config) (transport.PacketDialer, error) + +// NewPacketConnFunc creates a [net.PacketConn] based on the wrapConfig. The innerSD and innerPD functions can provide a base Stream and Packet Dialers if needed. +type NewPacketConnFunc func(ctx context.Context, config *Config) (net.PacketConn, error) -// NewPacketDialerFunc wraps a Dialer based on the wrapConfig. The innerSD and innerPD functions can provide a base Stream and Packet Dialers if needed. -type NewPacketDialerFunc func(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), wrapConfig *url.URL) (transport.PacketDialer, error) +// Transport config. +type Config struct { + URL url.URL + BaseConfig *Config +} + +func ParseConfig(configText string) (*Config, error) { + config := &Config{} + parts := strings.Split(strings.TrimSpace(configText), "|") + if len(parts) == 1 && parts[0] == "" { + return nil, nil + } + + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "" { + return nil, errors.New("empty config part") + } + // Make it ":" if it's only "" to parse as a URL. + if !strings.Contains(part, ":") { + part += ":" + } + url, err := url.Parse(part) + if err != nil { + return nil, fmt.Errorf("part is not a valid URL: %w", err) + } + config = &Config{URL: *url, BaseConfig: config} + } + return config, nil +} // NewDefaultConfigToDialer creates a [ConfigToDialer] with a set of default wrappers already registered. func NewDefaultConfigToDialer() *ConfigToDialer { p := new(ConfigToDialer) - p.BaseStreamDialer = &transport.TCPDialer{} - p.BasePacketDialer = &transport.UDPDialer{} + tcpDialer := &transport.TCPDialer{} + p.NewBaseStreamDialer = func(ctx context.Context) (transport.StreamDialer, error) { + return tcpDialer, nil + } + udpDialer := &transport.UDPDialer{} + p.NewBasePacketDialer = func(ctx context.Context) (transport.PacketDialer, error) { + return udpDialer, nil + } - // Please keep the list in alphabetical order. - p.RegisterStreamDialerType("do53", wrapStreamDialerWithDO53) + p.NewBasePacketConn = func(ctx context.Context) (net.PacketConn, error) { + return net.ListenUDP("", &net.UDPAddr{}) + } - p.RegisterStreamDialerType("doh", wrapStreamDialerWithDOH) + // Please keep the list in alphabetical order. + p.RegisterStreamDialerType("do53", newDO53StreamDialerFactory(p.NewStreamDialer, p.NewPacketDialer)) + p.RegisterStreamDialerType("doh", newDOHStreamDialerFactory(p.NewStreamDialer)) - p.RegisterStreamDialerType("override", wrapStreamDialerWithOverride) - p.RegisterPacketDialerType("override", wrapPacketDialerWithOverride) + p.RegisterStreamDialerType("override", newOverrideStreamDialerFactory(p.NewStreamDialer)) + p.RegisterPacketDialerType("override", newOverridePacketDialerFactory(p.NewPacketDialer)) - p.RegisterStreamDialerType("socks5", wrapStreamDialerWithSOCKS5) - p.RegisterPacketDialerType("socks5", wrapPacketDialerWithSOCKS5) + p.RegisterStreamDialerType("socks5", newSOCKS5StreamDialerFactory(p.NewStreamDialer)) + p.RegisterPacketDialerType("socks5", newSOCKS5PacketDialerFactory(p.NewStreamDialer, p.NewPacketDialer)) + p.RegisterPacketConnType("socks5", newSOCKS5PacketConnFactory(p.NewStreamDialer, p.NewPacketDialer)) - p.RegisterStreamDialerType("split", wrapStreamDialerWithSplit) + p.RegisterStreamDialerType("split", newSplitStreamDialerFactory(p.NewStreamDialer)) p.RegisterStreamDialerType("ss", wrapStreamDialerWithShadowsocks) p.RegisterPacketDialerType("ss", wrapPacketDialerWithShadowsocks) @@ -85,7 +136,7 @@ func NewDefaultConfigToDialer() *ConfigToDialer { return p } -// RegisterStreamDialerType will register a wrapper for stream dialers under the given subtype. +// RegisterStreamDialerType will register a factory for stream dialers under the given subtype. func (p *ConfigToDialer) RegisterStreamDialerType(subtype string, newDialer NewStreamDialerFunc) error { if p.sdBuilders == nil { p.sdBuilders = make(map[string]NewStreamDialerFunc) @@ -98,7 +149,7 @@ func (p *ConfigToDialer) RegisterStreamDialerType(subtype string, newDialer NewS return nil } -// RegisterPacketDialerType will register a wrapper for packet dialers under the given subtype. +// RegisterPacketDialerType will register a factory for packet dialers under the given subtype. func (p *ConfigToDialer) RegisterPacketDialerType(subtype string, newDialer NewPacketDialerFunc) error { if p.pdBuilders == nil { p.pdBuilders = make(map[string]NewPacketDialerFunc) @@ -111,92 +162,76 @@ func (p *ConfigToDialer) RegisterPacketDialerType(subtype string, newDialer NewP return nil } -func parseConfig(configText string) ([]*url.URL, error) { - parts := strings.Split(strings.TrimSpace(configText), "|") - if len(parts) == 1 && parts[0] == "" { - return []*url.URL{}, nil +// RegisterPacketConnType will register a factory for packet conns under the given subtype. +func (p *ConfigToDialer) RegisterPacketConnType(subtype string, newPacketConn NewPacketConnFunc) error { + if p.pcBuilders == nil { + p.pcBuilders = make(map[string]NewPacketConnFunc) } - urls := make([]*url.URL, 0, len(parts)) - for _, part := range parts { - part = strings.TrimSpace(part) - if part == "" { - return nil, errors.New("empty config part") - } - // Make it ":" if it's only "" to parse as a URL. - if !strings.Contains(part, ":") { - part += ":" - } - url, err := url.Parse(part) - if err != nil { - return nil, fmt.Errorf("part is not a valid URL: %w", err) - } - urls = append(urls, url) - } - return urls, nil -} -// NewStreamDialer creates a [Dialer] according to transportConfig, using dialer as the -// base [Dialer]. The given dialer must not be nil. -func (p *ConfigToDialer) NewStreamDialer(transportConfig string) (transport.StreamDialer, error) { - parts, err := parseConfig(transportConfig) - if err != nil { - return nil, err + if _, found := p.pcBuilders[subtype]; found { + return fmt.Errorf("config parser %v for PacketConn added twice", subtype) } - return p.newStreamDialer(parts) + p.pcBuilders[subtype] = newPacketConn + return nil } -// NewPacketDialer creates a [Dialer] according to transportConfig, using dialer as the -// base [Dialer]. The given dialer must not be nil. -func (p *ConfigToDialer) NewPacketDialer(transportConfig string) (transport.PacketDialer, error) { - parts, err := parseConfig(transportConfig) - if err != nil { - return nil, err +func (p *ConfigToDialer) newBaseStreamDialer(ctx context.Context) (transport.StreamDialer, error) { + if p.NewBaseStreamDialer == nil { + return nil, errors.New("base stream dialer not configured") } - return p.newPacketDialer(parts) + return p.NewBaseStreamDialer(ctx) } -func (p *ConfigToDialer) newStreamDialer(configParts []*url.URL) (transport.StreamDialer, error) { - if len(configParts) == 0 { - if p.BaseStreamDialer == nil { - return nil, fmt.Errorf("base StreamDialer must not be nil") - } - return p.BaseStreamDialer, nil +// NewStreamDialer creates a [transport.StreamDialer] according to the config. +func (p *ConfigToDialer) NewStreamDialer(ctx context.Context, config *Config) (transport.StreamDialer, error) { + if config == nil { + return p.newBaseStreamDialer(ctx) } - thisURL := configParts[len(configParts)-1] - innerConfig := configParts[:len(configParts)-1] - newDialer, ok := p.sdBuilders[thisURL.Scheme] + + newDialer, ok := p.sdBuilders[config.URL.Scheme] if !ok { - return nil, fmt.Errorf("config scheme '%v' is not supported for Stream Dialers", thisURL.Scheme) - } - newSD := func() (transport.StreamDialer, error) { - return p.newStreamDialer(innerConfig) + return nil, fmt.Errorf("config scheme '%v' is not supported for Stream Dialers", config.URL.Scheme) } - newPD := func() (transport.PacketDialer, error) { - return p.newPacketDialer(innerConfig) + return newDialer(ctx, config) +} + +func (p *ConfigToDialer) newBasePacketDialer(ctx context.Context) (transport.PacketDialer, error) { + if p.NewBasePacketDialer == nil { + return nil, errors.New("base packet dialer not configured") } - return newDialer(newSD, newPD, thisURL) + return p.NewBasePacketDialer(ctx) } -func (p *ConfigToDialer) newPacketDialer(configParts []*url.URL) (transport.PacketDialer, error) { - if len(configParts) == 0 { - if p.BasePacketDialer == nil { - return nil, fmt.Errorf("base PacketDialer must not be nil") - } - return p.BasePacketDialer, nil +func (p *ConfigToDialer) NewPacketDialer(ctx context.Context, config *Config) (transport.PacketDialer, error) { + if config == nil { + return p.newBasePacketDialer(ctx) } - thisURL := configParts[len(configParts)-1] - innerConfig := configParts[:len(configParts)-1] - newDialer, ok := p.pdBuilders[thisURL.Scheme] + + newDialer, ok := p.pdBuilders[config.URL.Scheme] if !ok { - return nil, fmt.Errorf("config scheme '%v' is not supported for Packet Dialers", thisURL.Scheme) + return nil, fmt.Errorf("config scheme '%v' is not supported for Packet Dialers", config.URL.Scheme) } - newSD := func() (transport.StreamDialer, error) { - return p.newStreamDialer(innerConfig) + return newDialer(ctx, config) +} + +func (p *ConfigToDialer) newBasePacketConn(ctx context.Context) (net.PacketConn, error) { + if p.NewBasePacketConn == nil { + return nil, errors.New("base packet conn not configured") } - newPD := func() (transport.PacketDialer, error) { - return p.newPacketDialer(innerConfig) + return p.NewBasePacketConn(ctx) +} + +// NewPacketConn creates a [net.PacketConn] according to transportConfig, using dialer as the +// base [Dialer]. The given dialer must not be nil. +func (p *ConfigToDialer) NewPacketConn(ctx context.Context, config *Config) (net.PacketConn, error) { + if config == nil { + return p.newBasePacketConn(ctx) } - return newDialer(newSD, newPD, thisURL) + newPacketConn, ok := p.pcBuilders[config.URL.Scheme] + if !ok { + return nil, fmt.Errorf("config scheme '%v' is not supported for Stream Dialers", config.URL.Scheme) + } + return newPacketConn(ctx, config) } // NewpacketListener creates a new [transport.PacketListener] according to the given config, @@ -225,38 +260,40 @@ func NewPacketListener(transportConfig string) (transport.PacketListener, error) } } -func SanitizeConfig(transportConfig string) (string, error) { - parts, err := parseConfig(transportConfig) +func SanitizeConfig(configStr string) (string, error) { + config, err := ParseConfig(configStr) if err != nil { return "", err } // Do nothing if the config is empty - if len(parts) == 0 { + if config == nil { return "", nil } // Iterate through each part - textParts := make([]string, len(parts)) - for i, u := range parts { - scheme := strings.ToLower(u.Scheme) + textParts := make([]string, 0, 1) + for config != nil { + scheme := strings.ToLower(config.URL.Scheme) + var part string switch scheme { case "ss": - textParts[i], err = sanitizeShadowsocksURL(u) + part, err = sanitizeShadowsocksURL(&config.URL) if err != nil { return "", err } case "socks5": - textParts[i], err = sanitizeSocks5URL(u) + part, err = sanitizeSocks5URL(&config.URL) if err != nil { return "", err } case "override", "split", "tls", "tlsfrag": // No sanitization needed - textParts[i] = u.String() + part = config.URL.String() default: - textParts[i] = scheme + "://UNKNOWN" + part = scheme + "://UNKNOWN" } + textParts = append(textParts, part) } // Join the parts back into a string return strings.Join(textParts, "|"), nil diff --git a/x/configurl/dns.go b/x/configurl/dns.go index 205dc8fd..b4388e3c 100644 --- a/x/configurl/dns.go +++ b/x/configurl/dns.go @@ -27,16 +27,27 @@ import ( "golang.org/x/net/dns/dnsmessage" ) -func wrapStreamDialerWithDO53(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err - } - pd, err := innerPD() - if err != nil { - return nil, err +func newDO53StreamDialerFactory(newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + if config == nil { + return nil, fmt.Errorf("emtpy do53 config") + } + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + pd, err := newPD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + resolver, err := newDO53Resolver(config.URL, sd, pd) + + return dns.NewStreamDialer(resolver, sd) } - query := configURL.Opaque +} + +func newDO53Resolver(config url.URL, sd transport.StreamDialer, pd transport.PacketDialer) (dns.Resolver, error) { + query := config.Opaque values, err := url.ParseQuery(query) if err != nil { return nil, err @@ -75,16 +86,26 @@ func wrapStreamDialerWithDO53(innerSD func() (transport.StreamDialer, error), in // See https://datatracker.ietf.org/doc/html/rfc1123#page-75. return tcpResolver.Query(ctx, q) }) - return dns.NewStreamDialer(resolver, sd) + return resolver, nil } -func wrapStreamDialerWithDOH(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - query := configURL.Opaque - values, err := url.ParseQuery(query) - if err != nil { - return nil, err +func newDOHStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + if config == nil { + return nil, fmt.Errorf("emtpy doh config") + } + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + resolver, err := newDOHResolver(config.URL, sd) + return dns.NewStreamDialer(resolver, sd) } - sd, err := innerSD() +} + +func newDOHResolver(config url.URL, sd transport.StreamDialer) (dns.Resolver, error) { + query := config.Opaque + values, err := url.ParseQuery(query) if err != nil { return nil, err } @@ -119,6 +140,5 @@ func wrapStreamDialerWithDOH(innerSD func() (transport.StreamDialer, error), inn port = "443" } dohURL := url.URL{Scheme: "https", Host: net.JoinHostPort(name, port), Path: "/dns-query"} - resolver := dns.NewHTTPSResolver(sd, address, dohURL.String()) - return dns.NewStreamDialer(resolver, sd) + return dns.NewHTTPSResolver(sd, address, dohURL.String()), nil } diff --git a/x/configurl/override.go b/x/configurl/override.go index 3f34ea63..3d4cfc30 100644 --- a/x/configurl/override.go +++ b/x/configurl/override.go @@ -24,7 +24,7 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport" ) -func newOverrideFromURL(configURL *url.URL) (func(string) (string, error), error) { +func newOverrideFromURL(configURL url.URL) (func(string) (string, error), error) { query := configURL.Opaque values, err := url.ParseQuery(query) if err != nil { @@ -66,38 +66,42 @@ func newOverrideFromURL(configURL *url.URL) (func(string) (string, error), error }, nil } -func wrapStreamDialerWithOverride(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err - } - override, err := newOverrideFromURL(configURL) - if err != nil { - return nil, err - } - return transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { - addr, err := override(addr) +func newOverrideStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err } - return sd.DialStream(ctx, addr) - }), nil + override, err := newOverrideFromURL(config.URL) + if err != nil { + return nil, err + } + return transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { + addr, err := override(addr) + if err != nil { + return nil, err + } + return sd.DialStream(ctx, addr) + }), nil + } } -func wrapPacketDialerWithOverride(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), configURL *url.URL) (transport.PacketDialer, error) { - pd, err := innerPD() - if err != nil { - return nil, err - } - override, err := newOverrideFromURL(configURL) - if err != nil { - return nil, err - } - return transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { - addr, err := override(addr) +func newOverridePacketDialerFactory(newPD NewPacketDialerFunc) NewPacketDialerFunc { + return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { + pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err } - return pd.DialPacket(ctx, addr) - }), nil + override, err := newOverrideFromURL(config.URL) + if err != nil { + return nil, err + } + return transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { + addr, err := override(addr) + if err != nil { + return nil, err + } + return pd.DialPacket(ctx, addr) + }), nil + } } diff --git a/x/configurl/override_test.go b/x/configurl/override_test.go index 0069de91..6a7e122e 100644 --- a/x/configurl/override_test.go +++ b/x/configurl/override_test.go @@ -25,7 +25,7 @@ func Test_newOverrideFromURL(t *testing.T) { t.Run("Host Override", func(t *testing.T) { cfgUrl, err := url.Parse("override:host=www.google.com") require.NoError(t, err) - override, err := newOverrideFromURL(cfgUrl) + override, err := newOverrideFromURL(*cfgUrl) require.NoError(t, err) addr, err := override("www.youtube.com:443") require.NoError(t, err) @@ -34,7 +34,7 @@ func Test_newOverrideFromURL(t *testing.T) { t.Run("Port Override", func(t *testing.T) { cfgUrl, err := url.Parse("override:port=853") require.NoError(t, err) - override, err := newOverrideFromURL(cfgUrl) + override, err := newOverrideFromURL(*cfgUrl) require.NoError(t, err) addr, err := override("8.8.8.8:53") require.NoError(t, err) @@ -43,7 +43,7 @@ func Test_newOverrideFromURL(t *testing.T) { t.Run("Full Override", func(t *testing.T) { cfgUrl, err := url.Parse("override:host=8.8.8.8&port=853") require.NoError(t, err) - override, err := newOverrideFromURL(cfgUrl) + override, err := newOverrideFromURL(*cfgUrl) require.NoError(t, err) addr, err := override("dns.google:53") require.NoError(t, err) @@ -53,7 +53,7 @@ func Test_newOverrideFromURL(t *testing.T) { t.Run("Host Override", func(t *testing.T) { cfgUrl, err := url.Parse("override:host=www.google.com") require.NoError(t, err) - override, err := newOverrideFromURL(cfgUrl) + override, err := newOverrideFromURL(*cfgUrl) require.NoError(t, err) _, err = override("foo bar") require.Error(t, err) @@ -61,7 +61,7 @@ func Test_newOverrideFromURL(t *testing.T) { t.Run("Full Override", func(t *testing.T) { cfgUrl, err := url.Parse("override:host=8.8.8.8&port=853") require.NoError(t, err) - override, err := newOverrideFromURL(cfgUrl) + override, err := newOverrideFromURL(*cfgUrl) require.NoError(t, err) addr, err := override("foo bar") require.NoError(t, err) diff --git a/x/configurl/socks5.go b/x/configurl/socks5.go index 8fc6e238..5ad252ce 100644 --- a/x/configurl/socks5.go +++ b/x/configurl/socks5.go @@ -15,46 +15,60 @@ package configurl import ( - "net/url" + "context" + "net" "github.com/Jigsaw-Code/outline-sdk/transport" "github.com/Jigsaw-Code/outline-sdk/transport/socks5" ) -func wrapStreamDialerWithSOCKS5(innerSD func() (transport.StreamDialer, error), _ func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err - } - endpoint := transport.StreamDialerEndpoint{Dialer: sd, Address: configURL.Host} - client, err := socks5.NewClient(&endpoint) - if err != nil { - return nil, err +func newSOCKS5StreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + return newSOCKS5Client(ctx, *config, newSD) } - userInfo := configURL.User - if userInfo != nil { - username := userInfo.Username() - password, _ := userInfo.Password() - err := client.SetCredentials([]byte(username), []byte(password)) +} + +func newSOCKS5PacketDialerFactory(newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) NewPacketDialerFunc { + return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { + client, err := newSOCKS5Client(ctx, *config, newSD) + if err != nil { + return nil, err + } + pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err } + client.EnablePacket(pd) + return transport.PacketListenerDialer{Listener: client}, nil } +} - return client, nil +func newSOCKS5PacketConnFactory(newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) NewPacketConnFunc { + return func(ctx context.Context, config *Config) (net.PacketConn, error) { + client, err := newSOCKS5Client(ctx, *config, newSD) + if err != nil { + return nil, err + } + pd, err := newPD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + client.EnablePacket(pd) + return client.ListenPacket(ctx) + } } -func wrapPacketDialerWithSOCKS5(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), configURL *url.URL) (transport.PacketDialer, error) { - sd, err := innerSD() +func newSOCKS5Client(ctx context.Context, config Config, newSD NewStreamDialerFunc) (*socks5.Client, error) { + sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err } - streamEndpoint := transport.StreamDialerEndpoint{Dialer: sd, Address: configURL.Host} - client, err := socks5.NewClient(&streamEndpoint) + endpoint := transport.StreamDialerEndpoint{Dialer: sd, Address: config.URL.Host} + client, err := socks5.NewClient(&endpoint) if err != nil { return nil, err } - userInfo := configURL.User + userInfo := config.URL.User if userInfo != nil { username := userInfo.Username() password, _ := userInfo.Password() @@ -63,12 +77,5 @@ func wrapPacketDialerWithSOCKS5(innerSD func() (transport.StreamDialer, error), return nil, err } } - - pd, err := innerPD() - if err != nil { - return nil, err - } - client.EnablePacket(pd) - packetDialer := transport.PacketListenerDialer{Listener: client} - return packetDialer, nil + return client, nil } diff --git a/x/configurl/split.go b/x/configurl/split.go index feca4d75..6b7566f2 100644 --- a/x/configurl/split.go +++ b/x/configurl/split.go @@ -15,23 +15,25 @@ package configurl import ( + "context" "fmt" - "net/url" "strconv" "github.com/Jigsaw-Code/outline-sdk/transport" "github.com/Jigsaw-Code/outline-sdk/transport/split" ) -func wrapStreamDialerWithSplit(innerSD func() (transport.StreamDialer, error), _ func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err +func newSplitStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + prefixBytesStr := config.URL.Opaque + prefixBytes, err := strconv.Atoi(prefixBytesStr) + if err != nil { + return nil, fmt.Errorf("prefixBytes is not a number: %v. Split config should be in split: format", prefixBytesStr) + } + return split.NewStreamDialer(sd, int64(prefixBytes)) } - prefixBytesStr := configURL.Opaque - prefixBytes, err := strconv.Atoi(prefixBytesStr) - if err != nil { - return nil, fmt.Errorf("prefixBytes is not a number: %v. Split config should be in split: format", prefixBytesStr) - } - return split.NewStreamDialer(sd, int64(prefixBytes)) } From 845dfa6317187e480a38e4ae6a09186c459b1292 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 25 Oct 2024 18:24:58 -0400 Subject: [PATCH 02/35] Add shadowsocks --- x/configurl/config.go | 5 +- x/configurl/shadowsocks.go | 101 +++++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 88708093..15627c7f 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -112,8 +112,9 @@ func NewDefaultConfigToDialer() *ConfigToDialer { p.RegisterStreamDialerType("split", newSplitStreamDialerFactory(p.NewStreamDialer)) - p.RegisterStreamDialerType("ss", wrapStreamDialerWithShadowsocks) - p.RegisterPacketDialerType("ss", wrapPacketDialerWithShadowsocks) + p.RegisterStreamDialerType("ss", newShadowsocksStreamDialerFactory(p.NewStreamDialer)) + p.RegisterPacketDialerType("ss", newShadowsocksPacketDialerFactory(p.NewPacketDialer)) + p.RegisterPacketConnType("ss", newShadowsocksPacketConnFactory(p.NewPacketDialer)) p.RegisterStreamDialerType("tls", wrapStreamDialerWithTLS) diff --git a/x/configurl/shadowsocks.go b/x/configurl/shadowsocks.go index 246460f3..ce71640b 100644 --- a/x/configurl/shadowsocks.go +++ b/x/configurl/shadowsocks.go @@ -15,9 +15,11 @@ package configurl import ( + "context" "encoding/base64" "errors" "fmt" + "net" "net/url" "strings" @@ -25,52 +27,65 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" ) -func wrapStreamDialerWithShadowsocks(innerSD func() (transport.StreamDialer, error), _ func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err - } - config, err := parseShadowsocksURL(configURL) - if err != nil { - return nil, err - } - endpoint := &transport.StreamDialerEndpoint{Dialer: sd, Address: config.serverAddress} - dialer, err := shadowsocks.NewStreamDialer(endpoint, config.cryptoKey) - if err != nil { - return nil, err - } - if len(config.prefix) > 0 { - dialer.SaltGenerator = shadowsocks.NewPrefixSaltGenerator(config.prefix) +func newShadowsocksStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + ssConfig, err := parseShadowsocksURL(config.URL) + if err != nil { + return nil, err + } + endpoint := &transport.StreamDialerEndpoint{Dialer: sd, Address: ssConfig.serverAddress} + dialer, err := shadowsocks.NewStreamDialer(endpoint, ssConfig.cryptoKey) + if err != nil { + return nil, err + } + if len(ssConfig.prefix) > 0 { + dialer.SaltGenerator = shadowsocks.NewPrefixSaltGenerator(ssConfig.prefix) + } + return dialer, nil } - return dialer, nil } -func wrapPacketDialerWithShadowsocks(_ func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), configURL *url.URL) (transport.PacketDialer, error) { - pd, err := innerPD() - if err != nil { - return nil, err - } - config, err := parseShadowsocksURL(configURL) - if err != nil { - return nil, err - } - endpoint := &transport.PacketDialerEndpoint{Dialer: pd, Address: config.serverAddress} - listener, err := shadowsocks.NewPacketListener(endpoint, config.cryptoKey) - if err != nil { - return nil, err +func newShadowsocksPacketDialerFactory(newPD NewPacketDialerFunc) NewPacketDialerFunc { + return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { + pd, err := newPD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + ssConfig, err := parseShadowsocksURL(config.URL) + if err != nil { + return nil, err + } + endpoint := &transport.PacketDialerEndpoint{Dialer: pd, Address: ssConfig.serverAddress} + pl, err := shadowsocks.NewPacketListener(endpoint, ssConfig.cryptoKey) + if err != nil { + return nil, err + } + // TODO: support UDP prefix. + return transport.PacketListenerDialer{Listener: pl}, nil } - dialer := transport.PacketListenerDialer{Listener: listener} - return dialer, nil } -func newShadowsocksPacketListenerFromURL(configURL *url.URL) (transport.PacketListener, error) { - config, err := parseShadowsocksURL(configURL) - if err != nil { - return nil, err +func newShadowsocksPacketConnFactory(newPD NewPacketDialerFunc) NewPacketConnFunc { + return func(ctx context.Context, config *Config) (net.PacketConn, error) { + pd, err := newPD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + ssConfig, err := parseShadowsocksURL(config.URL) + if err != nil { + return nil, err + } + endpoint := &transport.PacketDialerEndpoint{Dialer: pd, Address: ssConfig.serverAddress} + pl, err := shadowsocks.NewPacketListener(endpoint, ssConfig.cryptoKey) + if err != nil { + return nil, err + } + return pl.ListenPacket(ctx) } - // TODO: accept an inner dialer from the caller and pass it to UDPEndpoint - ep := &transport.UDPEndpoint{Address: config.serverAddress} - return shadowsocks.NewPacketListener(ep, config.cryptoKey) } type shadowsocksConfig struct { @@ -79,7 +94,7 @@ type shadowsocksConfig struct { prefix []byte } -func parseShadowsocksURL(url *url.URL) (*shadowsocksConfig, error) { +func parseShadowsocksURL(url url.URL) (*shadowsocksConfig, error) { // attempt to decode as SIP002 URI format and // fall back to legacy base64 format if decoding fails config, err := parseShadowsocksSIP002URL(url) @@ -91,7 +106,7 @@ func parseShadowsocksURL(url *url.URL) (*shadowsocksConfig, error) { // parseShadowsocksLegacyBase64URL parses URL based on legacy base64 format: // https://shadowsocks.org/doc/configs.html#uri-and-qr-code -func parseShadowsocksLegacyBase64URL(url *url.URL) (*shadowsocksConfig, error) { +func parseShadowsocksLegacyBase64URL(url url.URL) (*shadowsocksConfig, error) { config := &shadowsocksConfig{} if url.Host == "" { return nil, errors.New("host not specified") @@ -138,7 +153,7 @@ func parseShadowsocksLegacyBase64URL(url *url.URL) (*shadowsocksConfig, error) { // parseShadowsocksSIP002URL parses URL based on SIP002 format: // https://shadowsocks.org/doc/sip002.html -func parseShadowsocksSIP002URL(url *url.URL) (*shadowsocksConfig, error) { +func parseShadowsocksSIP002URL(url url.URL) (*shadowsocksConfig, error) { config := &shadowsocksConfig{} if url.Host == "" { return nil, errors.New("host not specified") @@ -188,7 +203,7 @@ func parseStringPrefix(utf8Str string) ([]byte, error) { return rawBytes, nil } -func sanitizeShadowsocksURL(u *url.URL) (string, error) { +func sanitizeShadowsocksURL(u url.URL) (string, error) { config, err := parseShadowsocksURL(u) if err != nil { return "", err From 470bdd3fac0cef981cef0c9b1ad5fd1952639f4d Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 25 Oct 2024 18:50:35 -0400 Subject: [PATCH 03/35] Add TLS --- x/configurl/config.go | 34 ++++------------------------------ x/configurl/tls.go | 23 +++++++++++++---------- x/configurl/tls_test.go | 31 ++++++++++++++++--------------- 3 files changed, 33 insertions(+), 55 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 15627c7f..e5420627 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -116,14 +116,14 @@ func NewDefaultConfigToDialer() *ConfigToDialer { p.RegisterPacketDialerType("ss", newShadowsocksPacketDialerFactory(p.NewPacketDialer)) p.RegisterPacketConnType("ss", newShadowsocksPacketConnFactory(p.NewPacketDialer)) - p.RegisterStreamDialerType("tls", wrapStreamDialerWithTLS) + p.RegisterStreamDialerType("tls", newTLSStreamDialerFactory(p.NewStreamDialer)) - p.RegisterStreamDialerType("tlsfrag", func(innerSD func() (transport.StreamDialer, error), innerPD func() (transport.PacketDialer, error), wrapConfig *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() + p.RegisterStreamDialerType("tlsfrag", func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := p.NewStreamDialer(ctx, config.BaseConfig) if err != nil { return nil, err } - lenStr := wrapConfig.Opaque + lenStr := config.URL.Opaque fixedLen, err := strconv.Atoi(lenStr) if err != nil { return nil, fmt.Errorf("invalid tlsfrag option: %v. It should be in tlsfrag: format", lenStr) @@ -235,32 +235,6 @@ func (p *ConfigToDialer) NewPacketConn(ctx context.Context, config *Config) (net return newPacketConn(ctx, config) } -// NewpacketListener creates a new [transport.PacketListener] according to the given config, -// the config must contain only one "ss://" segment. -// TODO: make NewPacketListener configurable. -func NewPacketListener(transportConfig string) (transport.PacketListener, error) { - parts, err := parseConfig(transportConfig) - if err != nil { - return nil, err - } - if len(parts) == 0 { - return nil, errors.New("config is required") - } - if len(parts) > 1 { - return nil, errors.New("multi-part config is not supported") - } - - url := parts[0] - // Please keep scheme list sorted. - switch strings.ToLower(url.Scheme) { - case "ss": - // TODO: support nested dialer, the last part must be "ss://" - return newShadowsocksPacketListenerFromURL(url) - default: - return nil, fmt.Errorf("config scheme '%v' is not supported", url.Scheme) - } -} - func SanitizeConfig(configStr string) (string, error) { config, err := ParseConfig(configStr) if err != nil { diff --git a/x/configurl/tls.go b/x/configurl/tls.go index 2b29a1c3..f6e57d65 100644 --- a/x/configurl/tls.go +++ b/x/configurl/tls.go @@ -15,6 +15,7 @@ package configurl import ( + "context" "fmt" "net/url" "strings" @@ -23,7 +24,7 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/tls" ) -func parseOptions(configURL *url.URL) ([]tls.ClientOption, error) { +func parseOptions(configURL url.URL) ([]tls.ClientOption, error) { query := configURL.Opaque values, err := url.ParseQuery(query) if err != nil { @@ -50,14 +51,16 @@ func parseOptions(configURL *url.URL) ([]tls.ClientOption, error) { return options, nil } -func wrapStreamDialerWithTLS(innerSD func() (transport.StreamDialer, error), _ func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err - } - options, err := parseOptions(configURL) - if err != nil { - return nil, err +func newTLSStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + options, err := parseOptions(config.URL) + if err != nil { + return nil, err + } + return tls.NewStreamDialer(sd, options...) } - return tls.NewStreamDialer(sd, options...) } diff --git a/x/configurl/tls_test.go b/x/configurl/tls_test.go index d9cdcc58..f507a201 100644 --- a/x/configurl/tls_test.go +++ b/x/configurl/tls_test.go @@ -15,7 +15,7 @@ package configurl import ( - "net/url" + "context" "testing" "github.com/Jigsaw-Code/outline-sdk/transport" @@ -24,16 +24,17 @@ import ( ) func TestTLS(t *testing.T) { - tlsURL, err := url.Parse("tls") + config, err := ParseConfig("tls") require.NoError(t, err) - _, err = wrapStreamDialerWithTLS(func() (transport.StreamDialer, error) { return &transport.TCPDialer{}, nil }, nil, tlsURL) + newSD := func(context.Context, *Config) (transport.StreamDialer, error) { return &transport.TCPDialer{}, nil } + _, err = newTLSStreamDialerFactory(newSD)(context.Background(), config) require.NoError(t, err) } func TestTLS_SNI(t *testing.T) { - tlsURL, err := url.Parse("tls:sni=www.google.com") + tlsURL, err := ParseConfig("tls:sni=www.google.com") require.NoError(t, err) - options, err := parseOptions(tlsURL) + options, err := parseOptions(tlsURL.URL) require.NoError(t, err) cfg := tls.ClientConfig{ServerName: "host", CertificateName: "host"} for _, option := range options { @@ -44,9 +45,9 @@ func TestTLS_SNI(t *testing.T) { } func TestTLS_NoSNI(t *testing.T) { - tlsURL, err := url.Parse("tls:sni=") + config, err := ParseConfig("tls:sni=") require.NoError(t, err) - options, err := parseOptions(tlsURL) + options, err := parseOptions(config.URL) require.NoError(t, err) cfg := tls.ClientConfig{ServerName: "host", CertificateName: "host"} for _, option := range options { @@ -57,16 +58,16 @@ func TestTLS_NoSNI(t *testing.T) { } func TestTLS_MultipleSNI(t *testing.T) { - tlsURL, err := url.Parse("tls:sni=www.google.com&sni=second") + config, err := ParseConfig("tls:sni=www.google.com&sni=second") require.NoError(t, err) - _, err = parseOptions(tlsURL) + _, err = parseOptions(config.URL) require.Error(t, err) } func TestTLS_CertName(t *testing.T) { - tlsURL, err := url.Parse("tls:certname=www.google.com") + config, err := ParseConfig("tls:certname=www.google.com") require.NoError(t, err) - options, err := parseOptions(tlsURL) + options, err := parseOptions(config.URL) require.NoError(t, err) cfg := tls.ClientConfig{ServerName: "host", CertificateName: "host"} for _, option := range options { @@ -77,9 +78,9 @@ func TestTLS_CertName(t *testing.T) { } func TestTLS_Combined(t *testing.T) { - tlsURL, err := url.Parse("tls:SNI=sni.example.com&CertName=certname.example.com") + config, err := ParseConfig("tls:SNI=sni.example.com&CertName=certname.example.com") require.NoError(t, err) - options, err := parseOptions(tlsURL) + options, err := parseOptions(config.URL) require.NoError(t, err) cfg := tls.ClientConfig{ServerName: "host", CertificateName: "host"} for _, option := range options { @@ -90,8 +91,8 @@ func TestTLS_Combined(t *testing.T) { } func TestTLS_UnsupportedOption(t *testing.T) { - tlsURL, err := url.Parse("tls:unsupported") + config, err := ParseConfig("tls:unsupported") require.NoError(t, err) - _, err = parseOptions(tlsURL) + _, err = parseOptions(config.URL) require.Error(t, err) } From bb0a82704cdb49893fc07326c0834e7cf910ce1b Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 25 Oct 2024 18:57:07 -0400 Subject: [PATCH 04/35] Fixes --- x/configurl/config.go | 2 +- x/configurl/shadowsocks_test.go | 78 ++++++++++++++++----------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index e5420627..36eacc8c 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -253,7 +253,7 @@ func SanitizeConfig(configStr string) (string, error) { var part string switch scheme { case "ss": - part, err = sanitizeShadowsocksURL(&config.URL) + part, err = sanitizeShadowsocksURL(config.URL) if err != nil { return "", err } diff --git a/x/configurl/shadowsocks_test.go b/x/configurl/shadowsocks_test.go index 093574a2..ae394e0b 100644 --- a/x/configurl/shadowsocks_test.go +++ b/x/configurl/shadowsocks_test.go @@ -25,7 +25,7 @@ import ( func Test_sanitizeShadowsocksURL(t *testing.T) { ssURL, err := url.Parse("ss://YWVzLTEyOC1nY206dGVzdA@192.168.100.1:8888") require.NoError(t, err) - sanitized, err := sanitizeShadowsocksURL(ssURL) + sanitized, err := sanitizeShadowsocksURL(*ssURL) require.NoError(t, err) require.Equal(t, "ss://REDACTED@192.168.100.1:8888", sanitized) } @@ -33,116 +33,116 @@ func Test_sanitizeShadowsocksURL(t *testing.T) { func Test_sanitizeShadowsocksURL_withPrefix(t *testing.T) { ssURL, err := url.Parse("ss://YWVzLTEyOC1nY206dGVzdA@192.168.100.1:8888?prefix=foo") require.NoError(t, err) - sanitized, err := sanitizeShadowsocksURL(ssURL) + sanitized, err := sanitizeShadowsocksURL(*ssURL) require.NoError(t, err) require.Equal(t, "ss://REDACTED@192.168.100.1:8888?prefix=foo", sanitized) } func TestParseShadowsocksURLFullyEncoded(t *testing.T) { encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) - urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") + config, err := ParseConfig("ss://" + string(encoded) + "#outline-123") require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - config, err := parseShadowsocksURL(urls[0]) + ssConfig, err := parseShadowsocksURL(config.URL) require.NoError(t, err) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) + require.Equal(t, "example.com:1234", ssConfig.serverAddress) + require.Equal(t, "HTTP/1.1 ", string(ssConfig.prefix)) } func TestParseShadowsocksURLUserInfoEncoded(t *testing.T) { encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567")) - urls, err := parseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123") + config, err := ParseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123") require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - config, err := parseShadowsocksURL(urls[0]) + ssConfig, err := parseShadowsocksURL(config.URL) require.NoError(t, err) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) + require.Equal(t, "example.com:1234", ssConfig.serverAddress) + require.Equal(t, "HTTP/1.1 ", string(ssConfig.prefix)) } func TestParseShadowsocksURLUserInfoLegacyEncoded(t *testing.T) { encoded := base64.StdEncoding.EncodeToString([]byte("aes-256-gcm:shadowsocks")) - urls, err := parseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123") + config, err := ParseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123") require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - config, err := parseShadowsocksURL(urls[0]) + ssConfig, err := parseShadowsocksURL(config.URL) require.NoError(t, err) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) + require.Equal(t, "example.com:1234", ssConfig.serverAddress) + require.Equal(t, "HTTP/1.1 ", string(ssConfig.prefix)) } func TestLegacyEncodedShadowsocksURL(t *testing.T) { configString := "ss://YWVzLTEyOC1nY206c2hhZG93c29ja3M=@example.com:1234" - urls, err := parseConfig(configString) + config, err := ParseConfig(configString) require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - config, err := parseShadowsocksURL(urls[0]) + ssConfig, err := parseShadowsocksURL(config.URL) require.NoError(t, err) - require.Equal(t, "example.com:1234", config.serverAddress) + require.Equal(t, "example.com:1234", ssConfig.serverAddress) } func TestParseShadowsocksURLNoEncoding(t *testing.T) { configString := "ss://aes-256-gcm:1234567@example.com:1234" - urls, err := parseConfig(configString) + config, err := ParseConfig(configString) require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - config, err := parseShadowsocksURL(urls[0]) + ssConfig, err := parseShadowsocksURL(config.URL) require.NoError(t, err) - require.Equal(t, "example.com:1234", config.serverAddress) + require.Equal(t, "example.com:1234", ssConfig.serverAddress) } func TestParseShadowsocksURLInvalidCipherInfoFails(t *testing.T) { configString := "ss://aes-256-gcm1234567@example.com:1234" - urls, err := parseConfig(configString) + config, err := ParseConfig(configString) require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - _, err = parseShadowsocksURL(urls[0]) + _, err = parseShadowsocksURL(config.URL) require.Error(t, err) } func TestParseShadowsocksURLUnsupportedCypherFails(t *testing.T) { configString := "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwnTpLeTUyN2duU3FEVFB3R0JpQ1RxUnlT@example.com:1234" - urls, err := parseConfig(configString) + config, err := ParseConfig(configString) require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - _, err = parseShadowsocksURL(urls[0]) + _, err = parseShadowsocksURL(config.URL) require.Error(t, err) } func TestParseShadowsocksLegacyBase64URL(t *testing.T) { encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) - urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") + config, err := ParseConfig("ss://" + string(encoded) + "#outline-123") require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - config, err := parseShadowsocksLegacyBase64URL(urls[0]) + ssConfig, err := parseShadowsocksLegacyBase64URL(config.URL) require.NoError(t, err) - require.Equal(t, "example.com:1234", config.serverAddress) - require.Equal(t, "HTTP/1.1 ", string(config.prefix)) + require.Equal(t, "example.com:1234", ssConfig.serverAddress) + require.Equal(t, "HTTP/1.1 ", string(ssConfig.prefix)) } func TestParseShadowsocksSIP002URLUnsuccessful(t *testing.T) { encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20")) - urls, err := parseConfig("ss://" + string(encoded) + "#outline-123") + config, err := ParseConfig("ss://" + string(encoded) + "#outline-123") require.NoError(t, err) - require.Equal(t, 1, len(urls)) + require.Nil(t, config.BaseConfig) - _, err = parseShadowsocksSIP002URL(urls[0]) + _, err = parseShadowsocksURL(config.URL) require.Error(t, err) } From 550384bc1956352a8d10e69795c656f7335e0b7b Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 25 Oct 2024 19:07:35 -0400 Subject: [PATCH 05/35] Add Websocket --- x/configurl/config.go | 4 +- x/configurl/websocket.go | 106 ++++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 36eacc8c..0a3b9fdc 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -131,8 +131,8 @@ func NewDefaultConfigToDialer() *ConfigToDialer { return tlsfrag.NewFixedLenStreamDialer(sd, fixedLen) }) - p.RegisterStreamDialerType("ws", wrapStreamDialerWithWebSocket) - p.RegisterPacketDialerType("ws", wrapPacketDialerWithWebSocket) + p.RegisterStreamDialerType("ws", newWebsocketStreamDialerFactory(p.NewStreamDialer)) + p.RegisterPacketDialerType("ws", newWebsocketPacketDialerFactory(p.NewStreamDialer)) return p } diff --git a/x/configurl/websocket.go b/x/configurl/websocket.go index 49861d15..659fba15 100644 --- a/x/configurl/websocket.go +++ b/x/configurl/websocket.go @@ -31,7 +31,7 @@ type wsConfig struct { udpPath string } -func parseWSConfig(configURL *url.URL) (*wsConfig, error) { +func parseWSConfig(configURL url.URL) (*wsConfig, error) { query := configURL.Opaque values, err := url.ParseQuery(query) if err != nil { @@ -71,66 +71,70 @@ func (c *wsToStreamConn) CloseWrite() error { return c.Close() } -func wrapStreamDialerWithWebSocket(innerSD func() (transport.StreamDialer, error), _ func() (transport.PacketDialer, error), configURL *url.URL) (transport.StreamDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err - } - config, err := parseWSConfig(configURL) - if err != nil { - return nil, err - } - if config.tcpPath == "" { - return nil, errors.New("must specify tcp_path") - } - return transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { - wsURL := url.URL{Scheme: "ws", Host: addr, Path: config.tcpPath} - origin := url.URL{Scheme: "http", Host: addr} - wsCfg, err := websocket.NewConfig(wsURL.String(), origin.String()) +func newWebsocketStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { + return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) if err != nil { - return nil, fmt.Errorf("failed to create websocket config: %w", err) + return nil, err } - baseConn, err := sd.DialStream(ctx, addr) + wsConfig, err := parseWSConfig(config.URL) if err != nil { - return nil, fmt.Errorf("failed to connect to websocket endpoint: %w", err) + return nil, err } - wsConn, err := websocket.NewClient(wsCfg, baseConn) - if err != nil { - baseConn.Close() - return nil, fmt.Errorf("failed to create websocket client: %w", err) + if wsConfig.tcpPath == "" { + return nil, errors.New("must specify tcp_path") } - return &wsToStreamConn{wsConn}, nil - }), nil + return transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { + wsURL := url.URL{Scheme: "ws", Host: addr, Path: wsConfig.tcpPath} + origin := url.URL{Scheme: "http", Host: addr} + wsCfg, err := websocket.NewConfig(wsURL.String(), origin.String()) + if err != nil { + return nil, fmt.Errorf("failed to create websocket config: %w", err) + } + baseConn, err := sd.DialStream(ctx, addr) + if err != nil { + return nil, fmt.Errorf("failed to connect to websocket endpoint: %w", err) + } + wsConn, err := websocket.NewClient(wsCfg, baseConn) + if err != nil { + baseConn.Close() + return nil, fmt.Errorf("failed to create websocket client: %w", err) + } + return &wsToStreamConn{wsConn}, nil + }), nil + } } -func wrapPacketDialerWithWebSocket(innerSD func() (transport.StreamDialer, error), _ func() (transport.PacketDialer, error), configURL *url.URL) (transport.PacketDialer, error) { - sd, err := innerSD() - if err != nil { - return nil, err - } - config, err := parseWSConfig(configURL) - if err != nil { - return nil, err - } - if config.udpPath == "" { - return nil, errors.New("must specify udp_path") - } - return transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { - wsURL := url.URL{Scheme: "ws", Host: addr, Path: config.udpPath} - origin := url.URL{Scheme: "http", Host: addr} - wsCfg, err := websocket.NewConfig(wsURL.String(), origin.String()) +func newWebsocketPacketDialerFactory(newSD NewStreamDialerFunc) NewPacketDialerFunc { + return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) if err != nil { - return nil, fmt.Errorf("failed to create websocket config: %w", err) + return nil, err } - baseConn, err := sd.DialStream(ctx, addr) + wsConfig, err := parseWSConfig(config.URL) if err != nil { - return nil, fmt.Errorf("failed to connect to websocket endpoint: %w", err) + return nil, err } - wsConn, err := websocket.NewClient(wsCfg, baseConn) - if err != nil { - baseConn.Close() - return nil, fmt.Errorf("failed to create websocket client: %w", err) + if wsConfig.udpPath == "" { + return nil, errors.New("must specify udp_path") } - return wsConn, nil - }), nil + return transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { + wsURL := url.URL{Scheme: "ws", Host: addr, Path: wsConfig.udpPath} + origin := url.URL{Scheme: "http", Host: addr} + wsCfg, err := websocket.NewConfig(wsURL.String(), origin.String()) + if err != nil { + return nil, fmt.Errorf("failed to create websocket config: %w", err) + } + baseConn, err := sd.DialStream(ctx, addr) + if err != nil { + return nil, fmt.Errorf("failed to connect to websocket endpoint: %w", err) + } + wsConn, err := websocket.NewClient(wsCfg, baseConn) + if err != nil { + baseConn.Close() + return nil, fmt.Errorf("failed to create websocket client: %w", err) + } + return wsConn, nil + }), nil + } } From 5826709d48ef2a2967407d8df894fd27aded1585 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 17:46:28 -0400 Subject: [PATCH 06/35] Cleanup --- x/configurl/config.go | 157 +++++++++++------- x/configurl/dns.go | 46 +++-- x/configurl/override.go | 80 ++++----- x/configurl/shadowsocks.go | 11 +- x/configurl/socks5.go | 21 ++- .../outline-cli/outline_packet_proxy.go | 2 +- 6 files changed, 177 insertions(+), 140 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 0a3b9fdc..c74df1d9 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "net" "net/url" "strconv" "strings" @@ -30,17 +29,43 @@ import ( // ConfigToDialer enables the creation of stream and packet dialers based on a config. The config is // extensible by registering wrappers for config subtypes. type ConfigToDialer struct { - NewBaseStreamDialer func(ctx context.Context) (transport.StreamDialer, error) - NewBasePacketDialer func(ctx context.Context) (transport.PacketDialer, error) - NewBasePacketConn func(ctx context.Context) (net.PacketConn, error) + BaseStreamDialer transport.StreamDialer + BasePacketDialer transport.PacketDialer + BasePacketListener transport.PacketListener sdBuilders map[string]NewStreamDialerFunc pdBuilders map[string]NewPacketDialerFunc - pcBuilders map[string]NewPacketConnFunc + plBuilders map[string]NewPacketListenerFunc } +var ( + _ ConfigToStreamDialer = (*ConfigToDialer)(nil) + _ ConfigToPacketDialer = (*ConfigToDialer)(nil) + _ ConfigToPacketListener = (*ConfigToDialer)(nil) +) + +// ConfigToObject creates an object from a config. +type ConfigToObject[ObjectType any] interface { + NewObject(ctx context.Context, config *Config) (ObjectType, error) + RegisterType(subtype string, newObject func(ctx context.Context, config *Config) (ObjectType, error)) error +} + +// ConfigToStreamDialer creates a [transport.StreamDialer] from a config. type ConfigToStreamDialer interface { - NewStreamDialer(ctx context.Context, config *Config) (transport.StreamDialer, error) + NewStreamDialerFromConfig(ctx context.Context, config *Config) (transport.StreamDialer, error) + RegisterStreamDialerType(subtype string, newDialer NewStreamDialerFunc) error +} + +// ConfigToPacketDialer creates a [transport.PacketDialer] from a config. +type ConfigToPacketDialer interface { + NewPacketDialerFromConfig(ctx context.Context, config *Config) (transport.PacketDialer, error) + RegisterPacketDialerType(subtype string, newDialer NewPacketDialerFunc) error +} + +// ConfigToPacketListener creates a [transport.PacketListener] from a config. +type ConfigToPacketListener interface { + NewPacketListenerFromConfig(ctx context.Context, config *Config) (transport.PacketListener, error) + RegisterPacketListenerType(subtype string, newDialer NewPacketListenerFunc) error } // NewStreamDialerFunc creates a [transport.StreamDialer] based on the config. @@ -49,8 +74,8 @@ type NewStreamDialerFunc func(ctx context.Context, config *Config) (transport.St // NewPacketDialerFunc creates a [transport.PacketDialer] based on the config. type NewPacketDialerFunc func(ctx context.Context, config *Config) (transport.PacketDialer, error) -// NewPacketConnFunc creates a [net.PacketConn] based on the wrapConfig. The innerSD and innerPD functions can provide a base Stream and Packet Dialers if needed. -type NewPacketConnFunc func(ctx context.Context, config *Config) (net.PacketConn, error) +// NewPacketListenerFunc creates a [net.PacketConn] based on the config. +type NewPacketListenerFunc func(ctx context.Context, config *Config) (transport.PacketListener, error) // Transport config. type Config struct { @@ -86,40 +111,32 @@ func ParseConfig(configText string) (*Config, error) { // NewDefaultConfigToDialer creates a [ConfigToDialer] with a set of default wrappers already registered. func NewDefaultConfigToDialer() *ConfigToDialer { p := new(ConfigToDialer) - tcpDialer := &transport.TCPDialer{} - p.NewBaseStreamDialer = func(ctx context.Context) (transport.StreamDialer, error) { - return tcpDialer, nil - } - udpDialer := &transport.UDPDialer{} - p.NewBasePacketDialer = func(ctx context.Context) (transport.PacketDialer, error) { - return udpDialer, nil - } - p.NewBasePacketConn = func(ctx context.Context) (net.PacketConn, error) { - return net.ListenUDP("", &net.UDPAddr{}) - } + p.BaseStreamDialer = &transport.TCPDialer{} + p.BasePacketDialer = &transport.UDPDialer{} + p.BasePacketListener = &transport.UDPListener{} // Please keep the list in alphabetical order. - p.RegisterStreamDialerType("do53", newDO53StreamDialerFactory(p.NewStreamDialer, p.NewPacketDialer)) - p.RegisterStreamDialerType("doh", newDOHStreamDialerFactory(p.NewStreamDialer)) + registerDO53StreamDialer(p, "do53", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) + registerDOHStreamDialer(p, "doh", p.NewStreamDialerFromConfig) - p.RegisterStreamDialerType("override", newOverrideStreamDialerFactory(p.NewStreamDialer)) - p.RegisterPacketDialerType("override", newOverridePacketDialerFactory(p.NewPacketDialer)) + registerOverrideStreamDialer(p, "override", p.NewStreamDialerFromConfig) + registerOverridePacketDialer(p, "override", p.NewPacketDialerFromConfig) - p.RegisterStreamDialerType("socks5", newSOCKS5StreamDialerFactory(p.NewStreamDialer)) - p.RegisterPacketDialerType("socks5", newSOCKS5PacketDialerFactory(p.NewStreamDialer, p.NewPacketDialer)) - p.RegisterPacketConnType("socks5", newSOCKS5PacketConnFactory(p.NewStreamDialer, p.NewPacketDialer)) + registerSOCKS5StreamDialer(p, "socks5", p.NewStreamDialerFromConfig) + registerSOCKS5PacketDialer(p, "socks5", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) + registerSOCKS5PacketListener(p, "socks5", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) - p.RegisterStreamDialerType("split", newSplitStreamDialerFactory(p.NewStreamDialer)) + p.RegisterStreamDialerType("split", newSplitStreamDialerFactory(p.NewStreamDialerFromConfig)) - p.RegisterStreamDialerType("ss", newShadowsocksStreamDialerFactory(p.NewStreamDialer)) - p.RegisterPacketDialerType("ss", newShadowsocksPacketDialerFactory(p.NewPacketDialer)) - p.RegisterPacketConnType("ss", newShadowsocksPacketConnFactory(p.NewPacketDialer)) + p.RegisterStreamDialerType("ss", newShadowsocksStreamDialerFactory(p.NewStreamDialerFromConfig)) + p.RegisterPacketDialerType("ss", newShadowsocksPacketDialerFactory(p.NewPacketDialerFromConfig)) + p.RegisterPacketListenerType("ss", newShadowsocksPacketListenerFactory(p.NewPacketDialerFromConfig)) - p.RegisterStreamDialerType("tls", newTLSStreamDialerFactory(p.NewStreamDialer)) + p.RegisterStreamDialerType("tls", newTLSStreamDialerFactory(p.NewStreamDialerFromConfig)) p.RegisterStreamDialerType("tlsfrag", func(ctx context.Context, config *Config) (transport.StreamDialer, error) { - sd, err := p.NewStreamDialer(ctx, config.BaseConfig) + sd, err := p.NewStreamDialerFromConfig(ctx, config.BaseConfig) if err != nil { return nil, err } @@ -131,8 +148,8 @@ func NewDefaultConfigToDialer() *ConfigToDialer { return tlsfrag.NewFixedLenStreamDialer(sd, fixedLen) }) - p.RegisterStreamDialerType("ws", newWebsocketStreamDialerFactory(p.NewStreamDialer)) - p.RegisterPacketDialerType("ws", newWebsocketPacketDialerFactory(p.NewStreamDialer)) + p.RegisterStreamDialerType("ws", newWebsocketStreamDialerFactory(p.NewStreamDialerFromConfig)) + p.RegisterPacketDialerType("ws", newWebsocketPacketDialerFactory(p.NewStreamDialerFromConfig)) return p } @@ -163,30 +180,35 @@ func (p *ConfigToDialer) RegisterPacketDialerType(subtype string, newDialer NewP return nil } -// RegisterPacketConnType will register a factory for packet conns under the given subtype. -func (p *ConfigToDialer) RegisterPacketConnType(subtype string, newPacketConn NewPacketConnFunc) error { - if p.pcBuilders == nil { - p.pcBuilders = make(map[string]NewPacketConnFunc) +// RegisterPacketListenerType will register a factory for packet listeners under the given subtype. +func (p *ConfigToDialer) RegisterPacketListenerType(subtype string, newPacketListener NewPacketListenerFunc) error { + if p.plBuilders == nil { + p.plBuilders = make(map[string]NewPacketListenerFunc) } - if _, found := p.pcBuilders[subtype]; found { + if _, found := p.plBuilders[subtype]; found { return fmt.Errorf("config parser %v for PacketConn added twice", subtype) } - p.pcBuilders[subtype] = newPacketConn + p.plBuilders[subtype] = newPacketListener return nil } -func (p *ConfigToDialer) newBaseStreamDialer(ctx context.Context) (transport.StreamDialer, error) { - if p.NewBaseStreamDialer == nil { - return nil, errors.New("base stream dialer not configured") +// NewStreamDialer creates a [transport.StreamDialer] according to the config text. +func (p *ConfigToDialer) NewStreamDialer(configText string) (transport.StreamDialer, error) { + config, err := ParseConfig(configText) + if err != nil { + return nil, err } - return p.NewBaseStreamDialer(ctx) + return p.NewStreamDialerFromConfig(context.Background(), config) } -// NewStreamDialer creates a [transport.StreamDialer] according to the config. -func (p *ConfigToDialer) NewStreamDialer(ctx context.Context, config *Config) (transport.StreamDialer, error) { +// NewStreamDialerFromConfig creates a [transport.StreamDialer] according to the config. +func (p *ConfigToDialer) NewStreamDialerFromConfig(ctx context.Context, config *Config) (transport.StreamDialer, error) { if config == nil { - return p.newBaseStreamDialer(ctx) + if p.BaseStreamDialer == nil { + return nil, errors.New("base stream dialer not configured") + } + return p.BaseStreamDialer, nil } newDialer, ok := p.sdBuilders[config.URL.Scheme] @@ -196,16 +218,21 @@ func (p *ConfigToDialer) NewStreamDialer(ctx context.Context, config *Config) (t return newDialer(ctx, config) } -func (p *ConfigToDialer) newBasePacketDialer(ctx context.Context) (transport.PacketDialer, error) { - if p.NewBasePacketDialer == nil { - return nil, errors.New("base packet dialer not configured") +// NewPacketDialer creates a [transport.PacketDialer] according to the config text. +func (p *ConfigToDialer) NewPacketDialer(configText string) (transport.PacketDialer, error) { + config, err := ParseConfig(configText) + if err != nil { + return nil, err } - return p.NewBasePacketDialer(ctx) + return p.NewPacketDialerFromConfig(context.Background(), config) } -func (p *ConfigToDialer) NewPacketDialer(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func (p *ConfigToDialer) NewPacketDialerFromConfig(ctx context.Context, config *Config) (transport.PacketDialer, error) { if config == nil { - return p.newBasePacketDialer(ctx) + if p.BasePacketDialer == nil { + return nil, errors.New("base packet dialer not configured") + } + return p.BasePacketDialer, nil } newDialer, ok := p.pdBuilders[config.URL.Scheme] @@ -215,24 +242,28 @@ func (p *ConfigToDialer) NewPacketDialer(ctx context.Context, config *Config) (t return newDialer(ctx, config) } -func (p *ConfigToDialer) newBasePacketConn(ctx context.Context) (net.PacketConn, error) { - if p.NewBasePacketConn == nil { - return nil, errors.New("base packet conn not configured") +// NewPacketListner creates a [transport.PacketListener] according to the config text. +func (p *ConfigToDialer) NewPacketListener(configText string) (transport.PacketListener, error) { + config, err := ParseConfig(configText) + if err != nil { + return nil, err } - return p.NewBasePacketConn(ctx) + return p.NewPacketListenerFromConfig(context.Background(), config) } -// NewPacketConn creates a [net.PacketConn] according to transportConfig, using dialer as the -// base [Dialer]. The given dialer must not be nil. -func (p *ConfigToDialer) NewPacketConn(ctx context.Context, config *Config) (net.PacketConn, error) { +// NewPacketListenerFromconfig creates a [transport.PacketListener] according to config. +func (p *ConfigToDialer) NewPacketListenerFromConfig(ctx context.Context, config *Config) (transport.PacketListener, error) { if config == nil { - return p.newBasePacketConn(ctx) + if p.BasePacketListener == nil { + return nil, errors.New("base packet listener not configured") + } + return p.BasePacketListener, nil } - newPacketConn, ok := p.pcBuilders[config.URL.Scheme] + newPacketListener, ok := p.plBuilders[config.URL.Scheme] if !ok { return nil, fmt.Errorf("config scheme '%v' is not supported for Stream Dialers", config.URL.Scheme) } - return newPacketConn(ctx, config) + return newPacketListener(ctx, config) } func SanitizeConfig(configStr string) (string, error) { diff --git a/x/configurl/dns.go b/x/configurl/dns.go index b4388e3c..64988a84 100644 --- a/x/configurl/dns.go +++ b/x/configurl/dns.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "io" "net" "net/url" "strings" @@ -27,8 +28,8 @@ import ( "golang.org/x/net/dns/dnsmessage" ) -func newDO53StreamDialerFactory(newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerDO53StreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { + c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { if config == nil { return nil, fmt.Errorf("emtpy do53 config") } @@ -36,14 +37,39 @@ func newDO53StreamDialerFactory(newSD NewStreamDialerFunc, newPD NewPacketDialer if err != nil { return nil, err } + if closer, ok := sd.(io.Closer); ok { + defer closer.Close() + } pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err } + if closer, ok := pd.(io.Closer); ok { + defer closer.Close() + } resolver, err := newDO53Resolver(config.URL, sd, pd) + if err != nil { + return nil, err + } + return dns.NewStreamDialer(resolver, sd) + }) +} +func registerDOHStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { + c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + if config == nil { + return nil, fmt.Errorf("emtpy doh config") + } + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + resolver, err := newDOHResolver(config.URL, sd) + if err != nil { + return nil, err + } return dns.NewStreamDialer(resolver, sd) - } + }) } func newDO53Resolver(config url.URL, sd transport.StreamDialer, pd transport.PacketDialer) (dns.Resolver, error) { @@ -89,20 +115,6 @@ func newDO53Resolver(config url.URL, sd transport.StreamDialer, pd transport.Pac return resolver, nil } -func newDOHStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { - if config == nil { - return nil, fmt.Errorf("emtpy doh config") - } - sd, err := newSD(ctx, config.BaseConfig) - if err != nil { - return nil, err - } - resolver, err := newDOHResolver(config.URL, sd) - return dns.NewStreamDialer(resolver, sd) - } -} - func newDOHResolver(config url.URL, sd transport.StreamDialer) (dns.Resolver, error) { query := config.Opaque values, err := url.ParseQuery(query) diff --git a/x/configurl/override.go b/x/configurl/override.go index 3d4cfc30..dc136749 100644 --- a/x/configurl/override.go +++ b/x/configurl/override.go @@ -24,6 +24,46 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport" ) +func registerOverrideStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { + c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + override, err := newOverrideFromURL(config.URL) + if err != nil { + return nil, err + } + return transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { + addr, err := override(addr) + if err != nil { + return nil, err + } + return sd.DialStream(ctx, addr) + }), nil + }) +} + +func registerOverridePacketDialer(c ConfigToPacketDialer, typeID string, newPD NewPacketDialerFunc) { + c.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { + pd, err := newPD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + override, err := newOverrideFromURL(config.URL) + if err != nil { + return nil, err + } + return transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { + addr, err := override(addr) + if err != nil { + return nil, err + } + return pd.DialPacket(ctx, addr) + }), nil + }) +} + func newOverrideFromURL(configURL url.URL) (func(string) (string, error), error) { query := configURL.Opaque values, err := url.ParseQuery(query) @@ -65,43 +105,3 @@ func newOverrideFromURL(configURL url.URL) (func(string) (string, error), error) return net.JoinHostPort(host, port), nil }, nil } - -func newOverrideStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { - sd, err := newSD(ctx, config.BaseConfig) - if err != nil { - return nil, err - } - override, err := newOverrideFromURL(config.URL) - if err != nil { - return nil, err - } - return transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { - addr, err := override(addr) - if err != nil { - return nil, err - } - return sd.DialStream(ctx, addr) - }), nil - } -} - -func newOverridePacketDialerFactory(newPD NewPacketDialerFunc) NewPacketDialerFunc { - return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { - pd, err := newPD(ctx, config.BaseConfig) - if err != nil { - return nil, err - } - override, err := newOverrideFromURL(config.URL) - if err != nil { - return nil, err - } - return transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { - addr, err := override(addr) - if err != nil { - return nil, err - } - return pd.DialPacket(ctx, addr) - }), nil - } -} diff --git a/x/configurl/shadowsocks.go b/x/configurl/shadowsocks.go index ce71640b..9a3e6cc0 100644 --- a/x/configurl/shadowsocks.go +++ b/x/configurl/shadowsocks.go @@ -19,7 +19,6 @@ import ( "encoding/base64" "errors" "fmt" - "net" "net/url" "strings" @@ -69,8 +68,8 @@ func newShadowsocksPacketDialerFactory(newPD NewPacketDialerFunc) NewPacketDiale } } -func newShadowsocksPacketConnFactory(newPD NewPacketDialerFunc) NewPacketConnFunc { - return func(ctx context.Context, config *Config) (net.PacketConn, error) { +func newShadowsocksPacketListenerFactory(newPD NewPacketDialerFunc) NewPacketListenerFunc { + return func(ctx context.Context, config *Config) (transport.PacketListener, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -80,11 +79,7 @@ func newShadowsocksPacketConnFactory(newPD NewPacketDialerFunc) NewPacketConnFun return nil, err } endpoint := &transport.PacketDialerEndpoint{Dialer: pd, Address: ssConfig.serverAddress} - pl, err := shadowsocks.NewPacketListener(endpoint, ssConfig.cryptoKey) - if err != nil { - return nil, err - } - return pl.ListenPacket(ctx) + return shadowsocks.NewPacketListener(endpoint, ssConfig.cryptoKey) } } diff --git a/x/configurl/socks5.go b/x/configurl/socks5.go index 5ad252ce..6fbdac05 100644 --- a/x/configurl/socks5.go +++ b/x/configurl/socks5.go @@ -16,20 +16,19 @@ package configurl import ( "context" - "net" "github.com/Jigsaw-Code/outline-sdk/transport" "github.com/Jigsaw-Code/outline-sdk/transport/socks5" ) -func newSOCKS5StreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerSOCKS5StreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { + c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { return newSOCKS5Client(ctx, *config, newSD) - } + }) } -func newSOCKS5PacketDialerFactory(newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) NewPacketDialerFunc { - return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerSOCKS5PacketDialer(c ConfigToPacketDialer, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { + c.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { client, err := newSOCKS5Client(ctx, *config, newSD) if err != nil { return nil, err @@ -40,11 +39,11 @@ func newSOCKS5PacketDialerFactory(newSD NewStreamDialerFunc, newPD NewPacketDial } client.EnablePacket(pd) return transport.PacketListenerDialer{Listener: client}, nil - } + }) } -func newSOCKS5PacketConnFactory(newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) NewPacketConnFunc { - return func(ctx context.Context, config *Config) (net.PacketConn, error) { +func registerSOCKS5PacketListener(c ConfigToPacketListener, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { + c.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { client, err := newSOCKS5Client(ctx, *config, newSD) if err != nil { return nil, err @@ -54,8 +53,8 @@ func newSOCKS5PacketConnFactory(newSD NewStreamDialerFunc, newPD NewPacketDialer return nil, err } client.EnablePacket(pd) - return client.ListenPacket(ctx) - } + return client, nil + }) } func newSOCKS5Client(ctx context.Context, config Config, newSD NewStreamDialerFunc) (*socks5.Client, error) { diff --git a/x/examples/outline-cli/outline_packet_proxy.go b/x/examples/outline-cli/outline_packet_proxy.go index fe202b8e..eb522f86 100644 --- a/x/examples/outline-cli/outline_packet_proxy.go +++ b/x/examples/outline-cli/outline_packet_proxy.go @@ -36,7 +36,7 @@ type outlinePacketProxy struct { func newOutlinePacketProxy(transportConfig string) (opp *outlinePacketProxy, err error) { opp = &outlinePacketProxy{} - if opp.remotePl, err = configurl.NewPacketListener(transportConfig); err != nil { + if opp.remotePl, err = configurl.NewDefaultConfigToDialer().NewPacketListener(transportConfig); err != nil { return nil, fmt.Errorf("failed to create UDP packet listener: %w", err) } if opp.remote, err = network.NewPacketProxyFromPacketListener(opp.remotePl); err != nil { From 22a052644d4a18c9e82596369293895b8c5d6619 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 17:49:51 -0400 Subject: [PATCH 07/35] Refactor split --- x/configurl/config.go | 2 +- x/configurl/split.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index c74df1d9..bfef53c0 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -127,7 +127,7 @@ func NewDefaultConfigToDialer() *ConfigToDialer { registerSOCKS5PacketDialer(p, "socks5", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) registerSOCKS5PacketListener(p, "socks5", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) - p.RegisterStreamDialerType("split", newSplitStreamDialerFactory(p.NewStreamDialerFromConfig)) + registerSplitStreamDialer(p, "split", p.NewStreamDialerFromConfig) p.RegisterStreamDialerType("ss", newShadowsocksStreamDialerFactory(p.NewStreamDialerFromConfig)) p.RegisterPacketDialerType("ss", newShadowsocksPacketDialerFactory(p.NewPacketDialerFromConfig)) diff --git a/x/configurl/split.go b/x/configurl/split.go index 6b7566f2..658c7dc1 100644 --- a/x/configurl/split.go +++ b/x/configurl/split.go @@ -23,8 +23,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/split" ) -func newSplitStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerSplitStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { + c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -35,5 +35,5 @@ func newSplitStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc return nil, fmt.Errorf("prefixBytes is not a number: %v. Split config should be in split: format", prefixBytesStr) } return split.NewStreamDialer(sd, int64(prefixBytes)) - } + }) } From f2938cfbc289a9efd70d400a221c53b04541bf6b Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 17:55:25 -0400 Subject: [PATCH 08/35] Update Shadowsocks --- x/configurl/config.go | 6 +++--- x/configurl/shadowsocks.go | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index bfef53c0..6efb756e 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -129,9 +129,9 @@ func NewDefaultConfigToDialer() *ConfigToDialer { registerSplitStreamDialer(p, "split", p.NewStreamDialerFromConfig) - p.RegisterStreamDialerType("ss", newShadowsocksStreamDialerFactory(p.NewStreamDialerFromConfig)) - p.RegisterPacketDialerType("ss", newShadowsocksPacketDialerFactory(p.NewPacketDialerFromConfig)) - p.RegisterPacketListenerType("ss", newShadowsocksPacketListenerFactory(p.NewPacketDialerFromConfig)) + registerShadowsocksStreamDialer(p, "ss", p.NewStreamDialerFromConfig) + registerShadowsocksPacketDialer(p, "ss", p.NewPacketDialerFromConfig) + registerShadowsocksPacketListener(p, "ss", p.NewPacketDialerFromConfig) p.RegisterStreamDialerType("tls", newTLSStreamDialerFactory(p.NewStreamDialerFromConfig)) diff --git a/x/configurl/shadowsocks.go b/x/configurl/shadowsocks.go index 9a3e6cc0..0445c9a9 100644 --- a/x/configurl/shadowsocks.go +++ b/x/configurl/shadowsocks.go @@ -26,8 +26,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" ) -func newShadowsocksStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerShadowsocksStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { + c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -45,11 +45,11 @@ func newShadowsocksStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDiale dialer.SaltGenerator = shadowsocks.NewPrefixSaltGenerator(ssConfig.prefix) } return dialer, nil - } + }) } -func newShadowsocksPacketDialerFactory(newPD NewPacketDialerFunc) NewPacketDialerFunc { - return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerShadowsocksPacketDialer(c ConfigToPacketDialer, typeID string, newPD NewPacketDialerFunc) { + c.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -65,11 +65,11 @@ func newShadowsocksPacketDialerFactory(newPD NewPacketDialerFunc) NewPacketDiale } // TODO: support UDP prefix. return transport.PacketListenerDialer{Listener: pl}, nil - } + }) } -func newShadowsocksPacketListenerFactory(newPD NewPacketDialerFunc) NewPacketListenerFunc { - return func(ctx context.Context, config *Config) (transport.PacketListener, error) { +func registerShadowsocksPacketListener(c ConfigToPacketListener, typeID string, newPD NewPacketDialerFunc) { + c.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -80,7 +80,7 @@ func newShadowsocksPacketListenerFactory(newPD NewPacketDialerFunc) NewPacketLis } endpoint := &transport.PacketDialerEndpoint{Dialer: pd, Address: ssConfig.serverAddress} return shadowsocks.NewPacketListener(endpoint, ssConfig.cryptoKey) - } + }) } type shadowsocksConfig struct { From 17182e01a8f8530338554732230ded358fbd8982 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 17:56:04 -0400 Subject: [PATCH 09/35] Remove ConfigToObject for now --- x/configurl/config.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 6efb756e..96161f09 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -44,12 +44,6 @@ var ( _ ConfigToPacketListener = (*ConfigToDialer)(nil) ) -// ConfigToObject creates an object from a config. -type ConfigToObject[ObjectType any] interface { - NewObject(ctx context.Context, config *Config) (ObjectType, error) - RegisterType(subtype string, newObject func(ctx context.Context, config *Config) (ObjectType, error)) error -} - // ConfigToStreamDialer creates a [transport.StreamDialer] from a config. type ConfigToStreamDialer interface { NewStreamDialerFromConfig(ctx context.Context, config *Config) (transport.StreamDialer, error) From 77906f15df5ef85941a8b434d355fa76a84a5dec Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 18:10:45 -0400 Subject: [PATCH 10/35] Introduce registry type --- x/configurl/config.go | 23 +++++++++++++++++++---- x/configurl/dns.go | 8 ++++---- x/configurl/override.go | 8 ++++---- x/configurl/shadowsocks.go | 12 ++++++------ x/configurl/socks5.go | 12 ++++++------ x/configurl/split.go | 4 ++-- x/configurl/tls.go | 28 ++++++++++++++-------------- x/configurl/tls_test.go | 10 ---------- 8 files changed, 55 insertions(+), 50 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 96161f09..d074b088 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -40,26 +40,41 @@ type ConfigToDialer struct { var ( _ ConfigToStreamDialer = (*ConfigToDialer)(nil) + _ StreamDialerRegistry = (*ConfigToDialer)(nil) _ ConfigToPacketDialer = (*ConfigToDialer)(nil) + _ PacketDialerRegistry = (*ConfigToDialer)(nil) _ ConfigToPacketListener = (*ConfigToDialer)(nil) + _ PacketListenerRegistry = (*ConfigToDialer)(nil) ) // ConfigToStreamDialer creates a [transport.StreamDialer] from a config. type ConfigToStreamDialer interface { NewStreamDialerFromConfig(ctx context.Context, config *Config) (transport.StreamDialer, error) - RegisterStreamDialerType(subtype string, newDialer NewStreamDialerFunc) error +} + +// StreamDialerRegistry registers [transport.StreamDialer] types. +type StreamDialerRegistry interface { + RegisterStreamDialerType(subtype string, newInstance NewStreamDialerFunc) error } // ConfigToPacketDialer creates a [transport.PacketDialer] from a config. type ConfigToPacketDialer interface { NewPacketDialerFromConfig(ctx context.Context, config *Config) (transport.PacketDialer, error) - RegisterPacketDialerType(subtype string, newDialer NewPacketDialerFunc) error +} + +// PacketDialerRegistry registers [transport.PacketDialer] types. +type PacketDialerRegistry interface { + RegisterPacketDialerType(subtype string, newInstance NewPacketDialerFunc) error } // ConfigToPacketListener creates a [transport.PacketListener] from a config. type ConfigToPacketListener interface { NewPacketListenerFromConfig(ctx context.Context, config *Config) (transport.PacketListener, error) - RegisterPacketListenerType(subtype string, newDialer NewPacketListenerFunc) error +} + +// PacketListenerRegistry registers [transport.PacketListener] types. +type PacketListenerRegistry interface { + RegisterPacketListenerType(subtype string, newInstance NewPacketListenerFunc) error } // NewStreamDialerFunc creates a [transport.StreamDialer] based on the config. @@ -127,7 +142,7 @@ func NewDefaultConfigToDialer() *ConfigToDialer { registerShadowsocksPacketDialer(p, "ss", p.NewPacketDialerFromConfig) registerShadowsocksPacketListener(p, "ss", p.NewPacketDialerFromConfig) - p.RegisterStreamDialerType("tls", newTLSStreamDialerFactory(p.NewStreamDialerFromConfig)) + registerTLSStreamDialer(p, "tls", p.NewStreamDialerFromConfig) p.RegisterStreamDialerType("tlsfrag", func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := p.NewStreamDialerFromConfig(ctx, config.BaseConfig) diff --git a/x/configurl/dns.go b/x/configurl/dns.go index 64988a84..2a84ff35 100644 --- a/x/configurl/dns.go +++ b/x/configurl/dns.go @@ -28,8 +28,8 @@ import ( "golang.org/x/net/dns/dnsmessage" ) -func registerDO53StreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { - c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerDO53StreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { if config == nil { return nil, fmt.Errorf("emtpy do53 config") } @@ -55,8 +55,8 @@ func registerDO53StreamDialer(c ConfigToStreamDialer, typeID string, newSD NewSt }) } -func registerDOHStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { - c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerDOHStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { if config == nil { return nil, fmt.Errorf("emtpy doh config") } diff --git a/x/configurl/override.go b/x/configurl/override.go index dc136749..b0c129c8 100644 --- a/x/configurl/override.go +++ b/x/configurl/override.go @@ -24,8 +24,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport" ) -func registerOverrideStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { - c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerOverrideStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -44,8 +44,8 @@ func registerOverrideStreamDialer(c ConfigToStreamDialer, typeID string, newSD N }) } -func registerOverridePacketDialer(c ConfigToPacketDialer, typeID string, newPD NewPacketDialerFunc) { - c.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerOverridePacketDialer(r PacketDialerRegistry, typeID string, newPD NewPacketDialerFunc) { + r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/shadowsocks.go b/x/configurl/shadowsocks.go index 0445c9a9..849971e1 100644 --- a/x/configurl/shadowsocks.go +++ b/x/configurl/shadowsocks.go @@ -26,8 +26,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" ) -func registerShadowsocksStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { - c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerShadowsocksStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -48,8 +48,8 @@ func registerShadowsocksStreamDialer(c ConfigToStreamDialer, typeID string, newS }) } -func registerShadowsocksPacketDialer(c ConfigToPacketDialer, typeID string, newPD NewPacketDialerFunc) { - c.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerShadowsocksPacketDialer(r PacketDialerRegistry, typeID string, newPD NewPacketDialerFunc) { + r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -68,8 +68,8 @@ func registerShadowsocksPacketDialer(c ConfigToPacketDialer, typeID string, newP }) } -func registerShadowsocksPacketListener(c ConfigToPacketListener, typeID string, newPD NewPacketDialerFunc) { - c.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { +func registerShadowsocksPacketListener(r PacketListenerRegistry, typeID string, newPD NewPacketDialerFunc) { + r.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/socks5.go b/x/configurl/socks5.go index 6fbdac05..eda2abf9 100644 --- a/x/configurl/socks5.go +++ b/x/configurl/socks5.go @@ -21,14 +21,14 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/socks5" ) -func registerSOCKS5StreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { - c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerSOCKS5StreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { return newSOCKS5Client(ctx, *config, newSD) }) } -func registerSOCKS5PacketDialer(c ConfigToPacketDialer, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { - c.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerSOCKS5PacketDialer(r PacketDialerRegistry, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { + r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { client, err := newSOCKS5Client(ctx, *config, newSD) if err != nil { return nil, err @@ -42,8 +42,8 @@ func registerSOCKS5PacketDialer(c ConfigToPacketDialer, typeID string, newSD New }) } -func registerSOCKS5PacketListener(c ConfigToPacketListener, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { - c.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { +func registerSOCKS5PacketListener(r PacketListenerRegistry, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { + r.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { client, err := newSOCKS5Client(ctx, *config, newSD) if err != nil { return nil, err diff --git a/x/configurl/split.go b/x/configurl/split.go index 658c7dc1..dbc9b7e1 100644 --- a/x/configurl/split.go +++ b/x/configurl/split.go @@ -23,8 +23,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/split" ) -func registerSplitStreamDialer(c ConfigToStreamDialer, typeID string, newSD NewStreamDialerFunc) { - c.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerSplitStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/tls.go b/x/configurl/tls.go index f6e57d65..95031c7f 100644 --- a/x/configurl/tls.go +++ b/x/configurl/tls.go @@ -24,6 +24,20 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/tls" ) +func registerTLSStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + options, err := parseOptions(config.URL) + if err != nil { + return nil, err + } + return tls.NewStreamDialer(sd, options...) + }) +} + func parseOptions(configURL url.URL) ([]tls.ClientOption, error) { query := configURL.Opaque values, err := url.ParseQuery(query) @@ -50,17 +64,3 @@ func parseOptions(configURL url.URL) ([]tls.ClientOption, error) { } return options, nil } - -func newTLSStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { - sd, err := newSD(ctx, config.BaseConfig) - if err != nil { - return nil, err - } - options, err := parseOptions(config.URL) - if err != nil { - return nil, err - } - return tls.NewStreamDialer(sd, options...) - } -} diff --git a/x/configurl/tls_test.go b/x/configurl/tls_test.go index f507a201..773fd97f 100644 --- a/x/configurl/tls_test.go +++ b/x/configurl/tls_test.go @@ -15,22 +15,12 @@ package configurl import ( - "context" "testing" - "github.com/Jigsaw-Code/outline-sdk/transport" "github.com/Jigsaw-Code/outline-sdk/transport/tls" "github.com/stretchr/testify/require" ) -func TestTLS(t *testing.T) { - config, err := ParseConfig("tls") - require.NoError(t, err) - newSD := func(context.Context, *Config) (transport.StreamDialer, error) { return &transport.TCPDialer{}, nil } - _, err = newTLSStreamDialerFactory(newSD)(context.Background(), config) - require.NoError(t, err) -} - func TestTLS_SNI(t *testing.T) { tlsURL, err := ParseConfig("tls:sni=www.google.com") require.NoError(t, err) From ffc0654e096df1bd1f5a28f89135c4a444140295 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 18:16:50 -0400 Subject: [PATCH 11/35] Finish --- x/configurl/config.go | 19 +++---------------- x/configurl/tlsfrag.go | 39 +++++++++++++++++++++++++++++++++++++++ x/configurl/websocket.go | 12 ++++++------ 3 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 x/configurl/tlsfrag.go diff --git a/x/configurl/config.go b/x/configurl/config.go index d074b088..2c427f8d 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -19,11 +19,9 @@ import ( "errors" "fmt" "net/url" - "strconv" "strings" "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/transport/tlsfrag" ) // ConfigToDialer enables the creation of stream and packet dialers based on a config. The config is @@ -144,21 +142,10 @@ func NewDefaultConfigToDialer() *ConfigToDialer { registerTLSStreamDialer(p, "tls", p.NewStreamDialerFromConfig) - p.RegisterStreamDialerType("tlsfrag", func(ctx context.Context, config *Config) (transport.StreamDialer, error) { - sd, err := p.NewStreamDialerFromConfig(ctx, config.BaseConfig) - if err != nil { - return nil, err - } - lenStr := config.URL.Opaque - fixedLen, err := strconv.Atoi(lenStr) - if err != nil { - return nil, fmt.Errorf("invalid tlsfrag option: %v. It should be in tlsfrag: format", lenStr) - } - return tlsfrag.NewFixedLenStreamDialer(sd, fixedLen) - }) + registerTLSFragStreamDialer(p, "tlsfrag", p.NewStreamDialerFromConfig) - p.RegisterStreamDialerType("ws", newWebsocketStreamDialerFactory(p.NewStreamDialerFromConfig)) - p.RegisterPacketDialerType("ws", newWebsocketPacketDialerFactory(p.NewStreamDialerFromConfig)) + registerWebsocketStreamDialer(p, "ws", p.NewStreamDialerFromConfig) + registerWebsocketPacketDialer(p, "ws", p.NewStreamDialerFromConfig) return p } diff --git a/x/configurl/tlsfrag.go b/x/configurl/tlsfrag.go new file mode 100644 index 00000000..687fbfc2 --- /dev/null +++ b/x/configurl/tlsfrag.go @@ -0,0 +1,39 @@ +// Copyright 2023 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 +// +// https://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 configurl + +import ( + "context" + "fmt" + "strconv" + + "github.com/Jigsaw-Code/outline-sdk/transport" + "github.com/Jigsaw-Code/outline-sdk/transport/tlsfrag" +) + +func registerTLSFragStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + sd, err := newSD(ctx, config.BaseConfig) + if err != nil { + return nil, err + } + lenStr := config.URL.Opaque + fixedLen, err := strconv.Atoi(lenStr) + if err != nil { + return nil, fmt.Errorf("invalid tlsfrag option: %v. It should be in tlsfrag: format", lenStr) + } + return tlsfrag.NewFixedLenStreamDialer(sd, fixedLen) + }) +} diff --git a/x/configurl/websocket.go b/x/configurl/websocket.go index 659fba15..99e64130 100644 --- a/x/configurl/websocket.go +++ b/x/configurl/websocket.go @@ -71,8 +71,8 @@ func (c *wsToStreamConn) CloseWrite() error { return c.Close() } -func newWebsocketStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerFunc { - return func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerWebsocketStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -102,11 +102,11 @@ func newWebsocketStreamDialerFactory(newSD NewStreamDialerFunc) NewStreamDialerF } return &wsToStreamConn{wsConn}, nil }), nil - } + }) } -func newWebsocketPacketDialerFactory(newSD NewStreamDialerFunc) NewPacketDialerFunc { - return func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerWebsocketPacketDialer(r PacketDialerRegistry, typeID string, newSD NewStreamDialerFunc) { + r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -136,5 +136,5 @@ func newWebsocketPacketDialerFactory(newSD NewStreamDialerFunc) NewPacketDialerF } return wsConn, nil }), nil - } + }) } From 7df7d2c851508fe53e567fa069e8c7957625a20d Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 23:58:28 -0400 Subject: [PATCH 12/35] Add QUIC --- x/examples/fetch/main.go | 110 ++++++++++++++++++++++++++++++--------- x/go.mod | 26 +++++---- x/go.sum | 44 +++++++++------- 3 files changed, 125 insertions(+), 55 deletions(-) diff --git a/x/examples/fetch/main.go b/x/examples/fetch/main.go index 634385a5..b5ee2c71 100644 --- a/x/examples/fetch/main.go +++ b/x/examples/fetch/main.go @@ -17,10 +17,12 @@ package main import ( "bufio" "context" + "crypto/tls" "flag" "fmt" "io" "log" + "log/slog" "net" "net/http" "net/textproto" @@ -30,10 +32,12 @@ import ( "time" "github.com/Jigsaw-Code/outline-sdk/x/configurl" + "github.com/lmittmann/tint" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" + "golang.org/x/term" ) -var debugLog log.Logger = *log.New(io.Discard, "", 0) - type stringArrayFlagValue []string func (v *stringArrayFlagValue) String() string { @@ -52,8 +56,23 @@ func init() { } } +func overrideAddress(original string, newHost string, newPort string) (string, error) { + host, port, err := net.SplitHostPort(original) + if err != nil { + return "", fmt.Errorf("invalid address: %w", err) + } + if newHost != "" { + host = newHost + } + if newPort != "" { + port = newPort + } + return net.JoinHostPort(host, port), nil +} + func main() { verboseFlag := flag.Bool("v", false, "Enable debug output") + protoFlag := flag.String("proto", "h1", "HTTP version to use (h1, h2, h3)") transportFlag := flag.String("transport", "", "Transport config") addressFlag := flag.String("address", "", "Address to connect to. If empty, use the URL authority") methodFlag := flag.String("method", "GET", "The HTTP method to use") @@ -63,9 +82,15 @@ func main() { flag.Parse() + logLevel := slog.LevelInfo if *verboseFlag { - debugLog = *log.New(os.Stderr, "[DEBUG] ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) + logLevel = slog.LevelDebug } + slog.SetDefault(slog.New(tint.NewHandler( + os.Stderr, + &tint.Options{NoColor: !term.IsTerminal(int(os.Stderr.Fd())), Level: logLevel}, + ))) + var overrideHost, overridePort string if *addressFlag != "" { var err error @@ -84,32 +109,67 @@ func main() { os.Exit(1) } - dialer, err := configurl.NewDefaultConfigToDialer().NewStreamDialer(*transportFlag) - if err != nil { - log.Fatalf("Could not create dialer: %v\n", err) + httpClient := &http.Client{ + Timeout: time.Duration(*timeoutSecFlag) * time.Second, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, } - dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { - host, port, err := net.SplitHostPort(addr) + + configModule := configurl.NewDefaultConfigToDialer() + if *protoFlag == "h1" || *protoFlag == "h2" { + dialer, err := configModule.NewStreamDialer(*transportFlag) if err != nil { - return nil, fmt.Errorf("invalid address: %w", err) + log.Fatalf("Could not create dialer: %v\n", err) } - if overrideHost != "" { - host = overrideHost + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + addressToDial, err := overrideAddress(addr, overrideHost, overridePort) + if err != nil { + return nil, fmt.Errorf("invalid address: %w", err) + } + if !strings.HasPrefix(network, "tcp") { + return nil, fmt.Errorf("protocol not supported: %v", network) + } + return dialer.DialStream(ctx, addressToDial) } - if overridePort != "" { - port = overridePort + if *protoFlag == "h1" { + httpClient.Transport = &http.Transport{ + DialContext: dialContext, + TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1"}}, + } + } else if *protoFlag == "h2" { + httpClient.Transport = &http.Transport{ + DialContext: dialContext, + TLSClientConfig: &tls.Config{NextProtos: []string{"h2"}}, + ForceAttemptHTTP2: true, + } } - if !strings.HasPrefix(network, "tcp") { - return nil, fmt.Errorf("protocol not supported: %v", network) + } else if *protoFlag == "h3" { + listener, err := configModule.NewPacketListener(*transportFlag) + if err != nil { + log.Fatalf("Could not create listener: %v\n", err) } - return dialer.DialStream(ctx, net.JoinHostPort(host, port)) - } - httpClient := &http.Client{ - Transport: &http.Transport{DialContext: dialContext}, - Timeout: time.Duration(*timeoutSecFlag) * time.Second, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, + conn, err := listener.ListenPacket(context.Background()) + if err != nil { + log.Fatalf("Could not create PacketConn: %v\n", err) + } + tr := &quic.Transport{ + Conn: conn, + } + defer tr.Close() + httpClient.Transport = &http3.Transport{ + EnableDatagrams: true, + Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) { + a, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + return tr.DialEarly(ctx, a, tlsConf, quicConf) + }, + Logger: slog.Default(), + } + } else { + log.Fatalln("Invalid HTTP protocol: ", *protoFlag) } req, err := http.NewRequest(*methodFlag, url, nil) @@ -133,8 +193,10 @@ func main() { defer resp.Body.Close() if *verboseFlag { + slog.Info("HTTP Proto", "version", resp.Proto) + slog.Info("HTTP Status", "status", resp.Status) for k, v := range resp.Header { - debugLog.Printf("%v: %v", k, v) + slog.Debug("Header", "key", k, "value", v) } } diff --git a/x/go.mod b/x/go.mod index a1dfe72a..261ff50c 100644 --- a/x/go.mod +++ b/x/go.mod @@ -1,6 +1,8 @@ module github.com/Jigsaw-Code/outline-sdk/x -go 1.21 +go 1.22 + +toolchain go1.22.1 require ( github.com/Jigsaw-Code/outline-sdk v0.0.17-0.20240726212635-470a9290ec57 @@ -12,11 +14,13 @@ require ( github.com/stretchr/testify v1.9.0 github.com/vishvananda/netlink v1.1.0 golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b - golang.org/x/net v0.25.0 - golang.org/x/sys v0.20.0 - golang.org/x/term v0.20.0 + golang.org/x/net v0.28.0 + golang.org/x/sys v0.23.0 + golang.org/x/term v0.23.0 ) +require github.com/quic-go/quic-go v0.48.1 + require ( filippo.io/bigmod v0.0.1 // indirect filippo.io/keygen v0.0.0-20230306160926-5201437acf8e // indirect @@ -57,7 +61,7 @@ require ( github.com/pion/transport/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 // indirect github.com/refraction-networking/ed25519 v0.1.2 // indirect github.com/refraction-networking/gotapdance v1.7.10 // indirect @@ -71,12 +75,12 @@ require ( github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 // indirect gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.21.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/x/go.sum b/x/go.sum index 033308fb..862d800d 100644 --- a/x/go.sum +++ b/x/go.sum @@ -147,8 +147,10 @@ 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/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA= +github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 h1:m2ZH6WV69otVmBpWbk8et3MypHFsjcYXTNrknQKS/PY= github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2/go.mod h1:7KuAtYfSL0K0WpCScjN9YKiOZ4AQ/8IzSjUtVwWbSv8= github.com/refraction-networking/ed25519 v0.1.2 h1:08kJZUkAlY7a7cZGosl1teGytV+QEoNxPO7NnRvAB+g= @@ -204,10 +206,10 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b/go.mod h1:EiXZlVfUTaAyySFVJb9rsODuiO+WXu8HrUuySb7nYFw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -223,14 +225,14 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -246,8 +248,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -255,30 +257,32 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= 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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From d3712d197892cb08b30948b15c1dddac1c005258 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 28 Oct 2024 23:59:05 -0400 Subject: [PATCH 13/35] Fix --- x/configurl/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 2c427f8d..628bbe67 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -91,12 +91,12 @@ type Config struct { } func ParseConfig(configText string) (*Config, error) { - config := &Config{} parts := strings.Split(strings.TrimSpace(configText), "|") if len(parts) == 1 && parts[0] == "" { return nil, nil } + var config *Config = nil for _, part := range parts { part = strings.TrimSpace(part) if part == "" { From 67a190c3d79b74402a66c6a582b9810c2dc1e855 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 29 Oct 2024 00:17:37 -0400 Subject: [PATCH 14/35] Fix test --- x/configurl/config.go | 15 +++++++++------ x/configurl/shadowsocks_test.go | 5 ++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 628bbe67..7e7aaa94 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -273,11 +273,10 @@ func SanitizeConfig(configStr string) (string, error) { return "", nil } - // Iterate through each part - textParts := make([]string, 0, 1) + var sanitized string for config != nil { - scheme := strings.ToLower(config.URL.Scheme) var part string + scheme := strings.ToLower(config.URL.Scheme) switch scheme { case "ss": part, err = sanitizeShadowsocksURL(config.URL) @@ -295,10 +294,14 @@ func SanitizeConfig(configStr string) (string, error) { default: part = scheme + "://UNKNOWN" } - textParts = append(textParts, part) + if sanitized == "" { + sanitized = part + } else { + sanitized = part + "|" + sanitized + } + config = config.BaseConfig } - // Join the parts back into a string - return strings.Join(textParts, "|"), nil + return sanitized, nil } func sanitizeSocks5URL(u *url.URL) (string, error) { diff --git a/x/configurl/shadowsocks_test.go b/x/configurl/shadowsocks_test.go index ae394e0b..e24245c5 100644 --- a/x/configurl/shadowsocks_test.go +++ b/x/configurl/shadowsocks_test.go @@ -142,7 +142,6 @@ func TestParseShadowsocksSIP002URLUnsuccessful(t *testing.T) { require.NoError(t, err) require.Nil(t, config.BaseConfig) - _, err = parseShadowsocksURL(config.URL) - - require.Error(t, err) + _, err = parseShadowsocksSIP002URL(config.URL) + require.Error(t, err, "URL is %v", config.URL.String()) } From 6803ddede4f5e64ba2c09ab035cff08c816845da Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 29 Oct 2024 00:18:24 -0400 Subject: [PATCH 15/35] Fix name --- x/configurl/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 7e7aaa94..63e563cf 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -284,7 +284,7 @@ func SanitizeConfig(configStr string) (string, error) { return "", err } case "socks5": - part, err = sanitizeSocks5URL(&config.URL) + part, err = sanitizeSOCKS5URL(&config.URL) if err != nil { return "", err } @@ -304,7 +304,7 @@ func SanitizeConfig(configStr string) (string, error) { return sanitized, nil } -func sanitizeSocks5URL(u *url.URL) (string, error) { +func sanitizeSOCKS5URL(u *url.URL) (string, error) { const redactedPlaceholder = "REDACTED" if u.User != nil { u.User = url.User(redactedPlaceholder) From 92eca9d1a1012ca9d6ab4a1d3507b8e48ca15436 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 29 Oct 2024 16:10:01 -0400 Subject: [PATCH 16/35] Use Generics --- x/configurl/config.go | 288 +++--------------- x/configurl/dns.go | 8 +- x/configurl/doc.go | 24 +- x/configurl/module.go | 159 ++++++++++ .../{config_test.go => module_test.go} | 0 x/configurl/override.go | 8 +- x/configurl/shadowsocks.go | 12 +- x/configurl/socks5.go | 14 +- x/configurl/split.go | 4 +- x/configurl/tls.go | 4 +- x/configurl/tlsfrag.go | 4 +- x/configurl/websocket.go | 8 +- x/examples/fetch-speed/main.go | 2 +- x/examples/fetch/main.go | 2 +- x/examples/http2transport/main.go | 2 +- x/examples/outline-cli/outline_device.go | 5 +- .../outline-cli/outline_packet_proxy.go | 2 +- x/examples/resolve/main.go | 6 +- x/examples/smart-proxy/main.go | 6 +- x/examples/test-connectivity/main.go | 10 +- x/examples/ws2endpoint/main.go | 6 +- x/httpproxy/connect_handler.go | 10 +- x/mobileproxy/mobileproxy.go | 4 +- x/smart/stream_dialer.go | 6 +- 24 files changed, 275 insertions(+), 319 deletions(-) create mode 100644 x/configurl/module.go rename x/configurl/{config_test.go => module_test.go} (100%) diff --git a/x/configurl/config.go b/x/configurl/config.go index 63e563cf..d0854924 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -20,69 +20,63 @@ import ( "fmt" "net/url" "strings" - - "github.com/Jigsaw-Code/outline-sdk/transport" ) -// ConfigToDialer enables the creation of stream and packet dialers based on a config. The config is -// extensible by registering wrappers for config subtypes. -type ConfigToDialer struct { - BaseStreamDialer transport.StreamDialer - BasePacketDialer transport.PacketDialer - BasePacketListener transport.PacketListener - - sdBuilders map[string]NewStreamDialerFunc - pdBuilders map[string]NewPacketDialerFunc - plBuilders map[string]NewPacketListenerFunc +type ExtensibleProvider[ObjectType comparable] struct { + BaseInstance ObjectType + builders map[string]BuildFunc[ObjectType] } -var ( - _ ConfigToStreamDialer = (*ConfigToDialer)(nil) - _ StreamDialerRegistry = (*ConfigToDialer)(nil) - _ ConfigToPacketDialer = (*ConfigToDialer)(nil) - _ PacketDialerRegistry = (*ConfigToDialer)(nil) - _ ConfigToPacketListener = (*ConfigToDialer)(nil) - _ PacketListenerRegistry = (*ConfigToDialer)(nil) -) +type BuildFunc[ObjectType any] func(ctx context.Context, config *Config) (ObjectType, error) -// ConfigToStreamDialer creates a [transport.StreamDialer] from a config. -type ConfigToStreamDialer interface { - NewStreamDialerFromConfig(ctx context.Context, config *Config) (transport.StreamDialer, error) +func (p *ExtensibleProvider[ObjectType]) buildersMap() map[string]BuildFunc[ObjectType] { + if p.builders == nil { + p.builders = make(map[string]BuildFunc[ObjectType]) + } + return p.builders } -// StreamDialerRegistry registers [transport.StreamDialer] types. -type StreamDialerRegistry interface { - RegisterStreamDialerType(subtype string, newInstance NewStreamDialerFunc) error +// RegisterType will register a factory for the given subtype. +func (p *ExtensibleProvider[ObjectType]) RegisterType(subtype string, newDialer BuildFunc[ObjectType]) error { + builders := p.buildersMap() + if _, found := builders[subtype]; found { + return fmt.Errorf("type %v registered twice", subtype) + } + builders[subtype] = newDialer + return nil } -// ConfigToPacketDialer creates a [transport.PacketDialer] from a config. -type ConfigToPacketDialer interface { - NewPacketDialerFromConfig(ctx context.Context, config *Config) (transport.PacketDialer, error) -} +// NewInstance creates a new instance according to the config. +func (p *ExtensibleProvider[ObjectType]) NewInstance(ctx context.Context, config *Config) (ObjectType, error) { + var zero ObjectType + if config == nil { + if p.BaseInstance == zero { + return zero, errors.New("base instance is not configured") + } + return p.BaseInstance, nil + } -// PacketDialerRegistry registers [transport.PacketDialer] types. -type PacketDialerRegistry interface { - RegisterPacketDialerType(subtype string, newInstance NewPacketDialerFunc) error + newDialer, ok := p.buildersMap()[config.URL.Scheme] + if !ok { + return zero, fmt.Errorf("config type '%v' is not registered", config.URL.Scheme) + } + return newDialer(ctx, config) } -// ConfigToPacketListener creates a [transport.PacketListener] from a config. -type ConfigToPacketListener interface { - NewPacketListenerFromConfig(ctx context.Context, config *Config) (transport.PacketListener, error) -} +var ( + _ Provider[any] = (*ExtensibleProvider[any])(nil) + _ TypeRegistry[any] = (*ExtensibleProvider[any])(nil) +) -// PacketListenerRegistry registers [transport.PacketListener] types. -type PacketListenerRegistry interface { - RegisterPacketListenerType(subtype string, newInstance NewPacketListenerFunc) error +// Provider creates an instance from a config. +type Provider[ObjectType any] interface { + NewInstance(ctx context.Context, config *Config) (ObjectType, error) } -// NewStreamDialerFunc creates a [transport.StreamDialer] based on the config. -type NewStreamDialerFunc func(ctx context.Context, config *Config) (transport.StreamDialer, error) - -// NewPacketDialerFunc creates a [transport.PacketDialer] based on the config. -type NewPacketDialerFunc func(ctx context.Context, config *Config) (transport.PacketDialer, error) - -// NewPacketListenerFunc creates a [net.PacketConn] based on the config. -type NewPacketListenerFunc func(ctx context.Context, config *Config) (transport.PacketListener, error) +// TypeRegistry registers config types. +type TypeRegistry[ObjectType any] interface { + RegisterType(subtype string, newInstance BuildFunc[ObjectType]) error +} // Transport config. type Config struct { @@ -114,201 +108,3 @@ func ParseConfig(configText string) (*Config, error) { } return config, nil } - -// NewDefaultConfigToDialer creates a [ConfigToDialer] with a set of default wrappers already registered. -func NewDefaultConfigToDialer() *ConfigToDialer { - p := new(ConfigToDialer) - - p.BaseStreamDialer = &transport.TCPDialer{} - p.BasePacketDialer = &transport.UDPDialer{} - p.BasePacketListener = &transport.UDPListener{} - - // Please keep the list in alphabetical order. - registerDO53StreamDialer(p, "do53", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) - registerDOHStreamDialer(p, "doh", p.NewStreamDialerFromConfig) - - registerOverrideStreamDialer(p, "override", p.NewStreamDialerFromConfig) - registerOverridePacketDialer(p, "override", p.NewPacketDialerFromConfig) - - registerSOCKS5StreamDialer(p, "socks5", p.NewStreamDialerFromConfig) - registerSOCKS5PacketDialer(p, "socks5", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) - registerSOCKS5PacketListener(p, "socks5", p.NewStreamDialerFromConfig, p.NewPacketDialerFromConfig) - - registerSplitStreamDialer(p, "split", p.NewStreamDialerFromConfig) - - registerShadowsocksStreamDialer(p, "ss", p.NewStreamDialerFromConfig) - registerShadowsocksPacketDialer(p, "ss", p.NewPacketDialerFromConfig) - registerShadowsocksPacketListener(p, "ss", p.NewPacketDialerFromConfig) - - registerTLSStreamDialer(p, "tls", p.NewStreamDialerFromConfig) - - registerTLSFragStreamDialer(p, "tlsfrag", p.NewStreamDialerFromConfig) - - registerWebsocketStreamDialer(p, "ws", p.NewStreamDialerFromConfig) - registerWebsocketPacketDialer(p, "ws", p.NewStreamDialerFromConfig) - - return p -} - -// RegisterStreamDialerType will register a factory for stream dialers under the given subtype. -func (p *ConfigToDialer) RegisterStreamDialerType(subtype string, newDialer NewStreamDialerFunc) error { - if p.sdBuilders == nil { - p.sdBuilders = make(map[string]NewStreamDialerFunc) - } - - if _, found := p.sdBuilders[subtype]; found { - return fmt.Errorf("config parser %v for StreamDialer added twice", subtype) - } - p.sdBuilders[subtype] = newDialer - return nil -} - -// RegisterPacketDialerType will register a factory for packet dialers under the given subtype. -func (p *ConfigToDialer) RegisterPacketDialerType(subtype string, newDialer NewPacketDialerFunc) error { - if p.pdBuilders == nil { - p.pdBuilders = make(map[string]NewPacketDialerFunc) - } - - if _, found := p.pdBuilders[subtype]; found { - return fmt.Errorf("config parser %v for StreamDialer added twice", subtype) - } - p.pdBuilders[subtype] = newDialer - return nil -} - -// RegisterPacketListenerType will register a factory for packet listeners under the given subtype. -func (p *ConfigToDialer) RegisterPacketListenerType(subtype string, newPacketListener NewPacketListenerFunc) error { - if p.plBuilders == nil { - p.plBuilders = make(map[string]NewPacketListenerFunc) - } - - if _, found := p.plBuilders[subtype]; found { - return fmt.Errorf("config parser %v for PacketConn added twice", subtype) - } - p.plBuilders[subtype] = newPacketListener - return nil -} - -// NewStreamDialer creates a [transport.StreamDialer] according to the config text. -func (p *ConfigToDialer) NewStreamDialer(configText string) (transport.StreamDialer, error) { - config, err := ParseConfig(configText) - if err != nil { - return nil, err - } - return p.NewStreamDialerFromConfig(context.Background(), config) -} - -// NewStreamDialerFromConfig creates a [transport.StreamDialer] according to the config. -func (p *ConfigToDialer) NewStreamDialerFromConfig(ctx context.Context, config *Config) (transport.StreamDialer, error) { - if config == nil { - if p.BaseStreamDialer == nil { - return nil, errors.New("base stream dialer not configured") - } - return p.BaseStreamDialer, nil - } - - newDialer, ok := p.sdBuilders[config.URL.Scheme] - if !ok { - return nil, fmt.Errorf("config scheme '%v' is not supported for Stream Dialers", config.URL.Scheme) - } - return newDialer(ctx, config) -} - -// NewPacketDialer creates a [transport.PacketDialer] according to the config text. -func (p *ConfigToDialer) NewPacketDialer(configText string) (transport.PacketDialer, error) { - config, err := ParseConfig(configText) - if err != nil { - return nil, err - } - return p.NewPacketDialerFromConfig(context.Background(), config) -} - -func (p *ConfigToDialer) NewPacketDialerFromConfig(ctx context.Context, config *Config) (transport.PacketDialer, error) { - if config == nil { - if p.BasePacketDialer == nil { - return nil, errors.New("base packet dialer not configured") - } - return p.BasePacketDialer, nil - } - - newDialer, ok := p.pdBuilders[config.URL.Scheme] - if !ok { - return nil, fmt.Errorf("config scheme '%v' is not supported for Packet Dialers", config.URL.Scheme) - } - return newDialer(ctx, config) -} - -// NewPacketListner creates a [transport.PacketListener] according to the config text. -func (p *ConfigToDialer) NewPacketListener(configText string) (transport.PacketListener, error) { - config, err := ParseConfig(configText) - if err != nil { - return nil, err - } - return p.NewPacketListenerFromConfig(context.Background(), config) -} - -// NewPacketListenerFromconfig creates a [transport.PacketListener] according to config. -func (p *ConfigToDialer) NewPacketListenerFromConfig(ctx context.Context, config *Config) (transport.PacketListener, error) { - if config == nil { - if p.BasePacketListener == nil { - return nil, errors.New("base packet listener not configured") - } - return p.BasePacketListener, nil - } - newPacketListener, ok := p.plBuilders[config.URL.Scheme] - if !ok { - return nil, fmt.Errorf("config scheme '%v' is not supported for Stream Dialers", config.URL.Scheme) - } - return newPacketListener(ctx, config) -} - -func SanitizeConfig(configStr string) (string, error) { - config, err := ParseConfig(configStr) - if err != nil { - return "", err - } - - // Do nothing if the config is empty - if config == nil { - return "", nil - } - - var sanitized string - for config != nil { - var part string - scheme := strings.ToLower(config.URL.Scheme) - switch scheme { - case "ss": - part, err = sanitizeShadowsocksURL(config.URL) - if err != nil { - return "", err - } - case "socks5": - part, err = sanitizeSOCKS5URL(&config.URL) - if err != nil { - return "", err - } - case "override", "split", "tls", "tlsfrag": - // No sanitization needed - part = config.URL.String() - default: - part = scheme + "://UNKNOWN" - } - if sanitized == "" { - sanitized = part - } else { - sanitized = part + "|" + sanitized - } - config = config.BaseConfig - } - return sanitized, nil -} - -func sanitizeSOCKS5URL(u *url.URL) (string, error) { - const redactedPlaceholder = "REDACTED" - if u.User != nil { - u.User = url.User(redactedPlaceholder) - return u.String(), nil - } - return u.String(), nil -} diff --git a/x/configurl/dns.go b/x/configurl/dns.go index 2a84ff35..2314dbd1 100644 --- a/x/configurl/dns.go +++ b/x/configurl/dns.go @@ -28,8 +28,8 @@ import ( "golang.org/x/net/dns/dnsmessage" ) -func registerDO53StreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerDO53StreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer], newPD BuildFunc[transport.PacketDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { if config == nil { return nil, fmt.Errorf("emtpy do53 config") } @@ -55,8 +55,8 @@ func registerDO53StreamDialer(r StreamDialerRegistry, typeID string, newSD NewSt }) } -func registerDOHStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerDOHStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { if config == nil { return nil, fmt.Errorf("emtpy doh config") } diff --git a/x/configurl/doc.go b/x/configurl/doc.go index 70cfd6b4..ad63376a 100644 --- a/x/configurl/doc.go +++ b/x/configurl/doc.go @@ -13,11 +13,11 @@ // limitations under the License. /* -Package config provides convenience functions to create dialer objects based on a text config. +Package configurl provides convenience functions to create network objects based on a text config. This is experimental and mostly for illustrative purposes at this point. -Configurable transports simplifies the way you create and manage transports. -With the config package, you can use [NewPacketDialer] and [NewStreamDialer] to create dialers using a simple text string. +Configurable strategies simplifies the way you create and manage strategies. +With the configurl package, you can use [NewPacketDialer], [NewStreamDialer] and [NewPacketListener] to create objects using a simple text string. Key Benefits: @@ -129,19 +129,19 @@ DPI Evasion - To add packet splitting to a Shadowsocks server for enhanced DPI e split:2|ss://[USERINFO]@[HOST]:[PORT] -Defining custom transport - You can define your custom transport by implementing and registering the [NewStreamDialerFunc] and [NewPacketDialerFunc] functions: +Defining custom strategies - You can define your custom strategy by implementing and registering [BuildFunc[ObjectType]] functions: - // create new config parser - // p := new(ConfigToDialer) + // Create new config parser. + // p := new(ConfigModule) // or - p := NewDefaultConfigToDialer() - // register your custom dialer - p.RegisterPacketDialerWrapper("custom", wrapStreamDialerWithCustom) - p.RegisterStreamDialerWrapper("custom", wrapPacketDialerWithCustom) + p := NewDefaultConfigModule() + // Register your custom dialer. + p.StreamDialers.RegisterType("custom", newStreamDialerWithCustom) + p.PacketDialers.RegisterType("custom", newPacketDialerWithCustom) // then use it - dialer, err := p.NewStreamDialer(innerDialer, "custom://config") + dialer, err := p.NewStreamDialer(context.Background(), "custom://config") -where wrapStreamDialerWithCustom and wrapPacketDialerWithCustom implement [NewPacketDialerFunc] and [NewStreamDialerFunc]. +where newStreamDialerWithCustom and newPacketDialerWithCustom implement [BuildFunc[transport.StreamDialer]] and [BuildFunc[transport.PacketDialer]]. [Onion Routing]: https://en.wikipedia.org/wiki/Onion_routing */ diff --git a/x/configurl/module.go b/x/configurl/module.go new file mode 100644 index 00000000..ea6f6a3f --- /dev/null +++ b/x/configurl/module.go @@ -0,0 +1,159 @@ +// 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 +// +// https://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 configurl + +import ( + "context" + "net/url" + "strings" + + "github.com/Jigsaw-Code/outline-sdk/transport" +) + +// ConfigModule enables the creation of network objects based on a config. The config is +// extensible by registering providers for config subtypes. +type ConfigModule struct { + StreamDialers ExtensibleProvider[transport.StreamDialer] + PacketDialers ExtensibleProvider[transport.PacketDialer] + PacketListeners ExtensibleProvider[transport.PacketListener] +} + +// NewDefaultConfigModule creates a [ConfigModule] with a set of default wrappers already registered. +func NewDefaultConfigModule() *ConfigModule { + p := new(ConfigModule) + + p.StreamDialers.BaseInstance = &transport.TCPDialer{} + p.PacketDialers.BaseInstance = &transport.UDPDialer{} + p.PacketListeners.BaseInstance = &transport.UDPListener{} + + // Please keep the list in alphabetical order. + registerDO53StreamDialer(&p.StreamDialers, "do53", p.StreamDialers.NewInstance, p.PacketDialers.NewInstance) + registerDOHStreamDialer(&p.StreamDialers, "doh", p.StreamDialers.NewInstance) + + registerOverrideStreamDialer(&p.StreamDialers, "override", p.StreamDialers.NewInstance) + registerOverridePacketDialer(&p.PacketDialers, "override", p.PacketDialers.NewInstance) + + registerSOCKS5StreamDialer(&p.StreamDialers, "socks5", p.StreamDialers.NewInstance) + registerSOCKS5PacketDialer(&p.PacketDialers, "socks5", p.StreamDialers.NewInstance, p.PacketDialers.NewInstance) + registerSOCKS5PacketListener(&p.PacketListeners, "socks5", p.StreamDialers.NewInstance, p.PacketDialers.NewInstance) + + registerSplitStreamDialer(&p.StreamDialers, "split", p.StreamDialers.NewInstance) + + registerShadowsocksStreamDialer(&p.StreamDialers, "ss", p.StreamDialers.NewInstance) + registerShadowsocksPacketDialer(&p.PacketDialers, "ss", p.PacketDialers.NewInstance) + registerShadowsocksPacketListener(&p.PacketListeners, "ss", p.PacketDialers.NewInstance) + + registerTLSStreamDialer(&p.StreamDialers, "tls", p.StreamDialers.NewInstance) + + registerTLSFragStreamDialer(&p.StreamDialers, "tlsfrag", p.StreamDialers.NewInstance) + + registerWebsocketStreamDialer(&p.StreamDialers, "ws", p.StreamDialers.NewInstance) + registerWebsocketPacketDialer(&p.PacketDialers, "ws", p.StreamDialers.NewInstance) + + return p +} + +// RegisterStreamDialerType will register a factory for stream dialers under the given subtype. +func (p *ConfigModule) RegisterStreamDialerType(subtype string, newInstance BuildFunc[transport.StreamDialer]) error { + return p.StreamDialers.RegisterType(subtype, newInstance) +} + +// RegisterPacketDialerType will register a factory for packet dialers under the given subtype. +func (p *ConfigModule) RegisterPacketDialerType(subtype string, newInstance BuildFunc[transport.PacketDialer]) error { + return p.PacketDialers.RegisterType(subtype, newInstance) +} + +// RegisterPacketListenerType will register a factory for packet listeners under the given subtype. +func (p *ConfigModule) RegisterPacketListenerType(subtype string, newInstance BuildFunc[transport.PacketListener]) error { + return p.PacketListeners.RegisterType(subtype, newInstance) +} + +// NewStreamDialer creates a [transport.StreamDialer] according to the config text. +func (p *ConfigModule) NewStreamDialer(ctx context.Context, configText string) (transport.StreamDialer, error) { + config, err := ParseConfig(configText) + if err != nil { + return nil, err + } + return p.StreamDialers.NewInstance(ctx, config) +} + +// NewPacketDialer creates a [transport.PacketDialer] according to the config text. +func (p *ConfigModule) NewPacketDialer(ctx context.Context, configText string) (transport.PacketDialer, error) { + config, err := ParseConfig(configText) + if err != nil { + return nil, err + } + return p.PacketDialers.NewInstance(ctx, config) +} + +// NewPacketListner creates a [transport.PacketListener] according to the config text. +func (p *ConfigModule) NewPacketListener(ctx context.Context, configText string) (transport.PacketListener, error) { + config, err := ParseConfig(configText) + if err != nil { + return nil, err + } + return p.PacketListeners.NewInstance(ctx, config) +} + +func SanitizeConfig(configStr string) (string, error) { + config, err := ParseConfig(configStr) + if err != nil { + return "", err + } + + // Do nothing if the config is empty + if config == nil { + return "", nil + } + + var sanitized string + for config != nil { + var part string + scheme := strings.ToLower(config.URL.Scheme) + switch scheme { + case "ss": + part, err = sanitizeShadowsocksURL(config.URL) + if err != nil { + return "", err + } + case "socks5": + part, err = sanitizeSOCKS5URL(&config.URL) + if err != nil { + return "", err + } + case "override", "split", "tls", "tlsfrag": + // No sanitization needed + part = config.URL.String() + default: + part = scheme + "://UNKNOWN" + } + if sanitized == "" { + sanitized = part + } else { + sanitized = part + "|" + sanitized + } + config = config.BaseConfig + } + return sanitized, nil +} + +func sanitizeSOCKS5URL(u *url.URL) (string, error) { + const redactedPlaceholder = "REDACTED" + if u.User != nil { + u.User = url.User(redactedPlaceholder) + return u.String(), nil + } + return u.String(), nil +} diff --git a/x/configurl/config_test.go b/x/configurl/module_test.go similarity index 100% rename from x/configurl/config_test.go rename to x/configurl/module_test.go diff --git a/x/configurl/override.go b/x/configurl/override.go index b0c129c8..743aae31 100644 --- a/x/configurl/override.go +++ b/x/configurl/override.go @@ -24,8 +24,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport" ) -func registerOverrideStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerOverrideStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -44,8 +44,8 @@ func registerOverrideStreamDialer(r StreamDialerRegistry, typeID string, newSD N }) } -func registerOverridePacketDialer(r PacketDialerRegistry, typeID string, newPD NewPacketDialerFunc) { - r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerOverridePacketDialer(r TypeRegistry[transport.PacketDialer], typeID string, newPD BuildFunc[transport.PacketDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/shadowsocks.go b/x/configurl/shadowsocks.go index 849971e1..594483e8 100644 --- a/x/configurl/shadowsocks.go +++ b/x/configurl/shadowsocks.go @@ -26,8 +26,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" ) -func registerShadowsocksStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerShadowsocksStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -48,8 +48,8 @@ func registerShadowsocksStreamDialer(r StreamDialerRegistry, typeID string, newS }) } -func registerShadowsocksPacketDialer(r PacketDialerRegistry, typeID string, newPD NewPacketDialerFunc) { - r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerShadowsocksPacketDialer(r TypeRegistry[transport.PacketDialer], typeID string, newPD BuildFunc[transport.PacketDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -68,8 +68,8 @@ func registerShadowsocksPacketDialer(r PacketDialerRegistry, typeID string, newP }) } -func registerShadowsocksPacketListener(r PacketListenerRegistry, typeID string, newPD NewPacketDialerFunc) { - r.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { +func registerShadowsocksPacketListener(r TypeRegistry[transport.PacketListener], typeID string, newPD BuildFunc[transport.PacketDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/socks5.go b/x/configurl/socks5.go index eda2abf9..cbb8de51 100644 --- a/x/configurl/socks5.go +++ b/x/configurl/socks5.go @@ -21,14 +21,14 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/socks5" ) -func registerSOCKS5StreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerSOCKS5StreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { return newSOCKS5Client(ctx, *config, newSD) }) } -func registerSOCKS5PacketDialer(r PacketDialerRegistry, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { - r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerSOCKS5PacketDialer(r TypeRegistry[transport.PacketDialer], typeID string, newSD BuildFunc[transport.StreamDialer], newPD BuildFunc[transport.PacketDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { client, err := newSOCKS5Client(ctx, *config, newSD) if err != nil { return nil, err @@ -42,8 +42,8 @@ func registerSOCKS5PacketDialer(r PacketDialerRegistry, typeID string, newSD New }) } -func registerSOCKS5PacketListener(r PacketListenerRegistry, typeID string, newSD NewStreamDialerFunc, newPD NewPacketDialerFunc) { - r.RegisterPacketListenerType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { +func registerSOCKS5PacketListener(r TypeRegistry[transport.PacketListener], typeID string, newSD BuildFunc[transport.StreamDialer], newPD BuildFunc[transport.PacketDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.PacketListener, error) { client, err := newSOCKS5Client(ctx, *config, newSD) if err != nil { return nil, err @@ -57,7 +57,7 @@ func registerSOCKS5PacketListener(r PacketListenerRegistry, typeID string, newSD }) } -func newSOCKS5Client(ctx context.Context, config Config, newSD NewStreamDialerFunc) (*socks5.Client, error) { +func newSOCKS5Client(ctx context.Context, config Config, newSD BuildFunc[transport.StreamDialer]) (*socks5.Client, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/split.go b/x/configurl/split.go index dbc9b7e1..e6d89b3a 100644 --- a/x/configurl/split.go +++ b/x/configurl/split.go @@ -23,8 +23,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/split" ) -func registerSplitStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerSplitStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/tls.go b/x/configurl/tls.go index 95031c7f..7517cc43 100644 --- a/x/configurl/tls.go +++ b/x/configurl/tls.go @@ -24,8 +24,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/tls" ) -func registerTLSStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerTLSStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/tlsfrag.go b/x/configurl/tlsfrag.go index 687fbfc2..1e2ef347 100644 --- a/x/configurl/tlsfrag.go +++ b/x/configurl/tlsfrag.go @@ -23,8 +23,8 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport/tlsfrag" ) -func registerTLSFragStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerTLSFragStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/configurl/websocket.go b/x/configurl/websocket.go index 99e64130..31ef95e4 100644 --- a/x/configurl/websocket.go +++ b/x/configurl/websocket.go @@ -71,8 +71,8 @@ func (c *wsToStreamConn) CloseWrite() error { return c.Close() } -func registerWebsocketStreamDialer(r StreamDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterStreamDialerType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { +func registerWebsocketStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err @@ -105,8 +105,8 @@ func registerWebsocketStreamDialer(r StreamDialerRegistry, typeID string, newSD }) } -func registerWebsocketPacketDialer(r PacketDialerRegistry, typeID string, newSD NewStreamDialerFunc) { - r.RegisterPacketDialerType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { +func registerWebsocketPacketDialer(r TypeRegistry[transport.PacketDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { + r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.PacketDialer, error) { sd, err := newSD(ctx, config.BaseConfig) if err != nil { return nil, err diff --git a/x/examples/fetch-speed/main.go b/x/examples/fetch-speed/main.go index 7e8db297..e896f637 100644 --- a/x/examples/fetch-speed/main.go +++ b/x/examples/fetch-speed/main.go @@ -58,7 +58,7 @@ func main() { os.Exit(1) } - dialer, err := configurl.NewDefaultConfigToDialer().NewStreamDialer(*transportFlag) + dialer, err := configurl.NewDefaultConfigModule().NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create dialer: %v\n", err) } diff --git a/x/examples/fetch/main.go b/x/examples/fetch/main.go index 634385a5..db81f9c4 100644 --- a/x/examples/fetch/main.go +++ b/x/examples/fetch/main.go @@ -84,7 +84,7 @@ func main() { os.Exit(1) } - dialer, err := configurl.NewDefaultConfigToDialer().NewStreamDialer(*transportFlag) + dialer, err := configurl.NewDefaultConfigModule().NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create dialer: %v\n", err) } diff --git a/x/examples/http2transport/main.go b/x/examples/http2transport/main.go index 72fb3dcf..66559d61 100644 --- a/x/examples/http2transport/main.go +++ b/x/examples/http2transport/main.go @@ -34,7 +34,7 @@ func main() { urlProxyPrefixFlag := flag.String("urlProxyPrefix", "/proxy", "Path where to run the URL proxy. Set to empty (\"\") to disable it.") flag.Parse() - dialer, err := configurl.NewDefaultConfigToDialer().NewStreamDialer(*transportFlag) + dialer, err := configurl.NewDefaultConfigModule().NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create dialer: %v", err) diff --git a/x/examples/outline-cli/outline_device.go b/x/examples/outline-cli/outline_device.go index 858e2bfa..a7cadb2a 100644 --- a/x/examples/outline-cli/outline_device.go +++ b/x/examples/outline-cli/outline_device.go @@ -15,6 +15,7 @@ package main import ( + "context" "errors" "fmt" "net" @@ -39,7 +40,7 @@ type OutlineDevice struct { svrIP net.IP } -var configToDialer = configurl.NewDefaultConfigToDialer() +var configModule = configurl.NewDefaultConfigModule() func NewOutlineDevice(transportConfig string) (od *OutlineDevice, err error) { ip, err := resolveShadowsocksServerIPFromConfig(transportConfig) @@ -50,7 +51,7 @@ func NewOutlineDevice(transportConfig string) (od *OutlineDevice, err error) { svrIP: ip, } - if od.sd, err = configToDialer.NewStreamDialer(transportConfig); err != nil { + if od.sd, err = configModule.NewStreamDialer(context.TODO(), transportConfig); err != nil { return nil, fmt.Errorf("failed to create TCP dialer: %w", err) } if od.pp, err = newOutlinePacketProxy(transportConfig); err != nil { diff --git a/x/examples/outline-cli/outline_packet_proxy.go b/x/examples/outline-cli/outline_packet_proxy.go index eb522f86..c98b5a53 100644 --- a/x/examples/outline-cli/outline_packet_proxy.go +++ b/x/examples/outline-cli/outline_packet_proxy.go @@ -36,7 +36,7 @@ type outlinePacketProxy struct { func newOutlinePacketProxy(transportConfig string) (opp *outlinePacketProxy, err error) { opp = &outlinePacketProxy{} - if opp.remotePl, err = configurl.NewDefaultConfigToDialer().NewPacketListener(transportConfig); err != nil { + if opp.remotePl, err = configurl.NewDefaultConfigModule().NewPacketListener(context.TODO(), transportConfig); err != nil { return nil, fmt.Errorf("failed to create UDP packet listener: %w", err) } if opp.remote, err = network.NewPacketProxyFromPacketListener(opp.remotePl); err != nil { diff --git a/x/examples/resolve/main.go b/x/examples/resolve/main.go index 2055b620..d8a24976 100644 --- a/x/examples/resolve/main.go +++ b/x/examples/resolve/main.go @@ -66,15 +66,15 @@ func main() { resolverAddr := *resolverFlag var resolver dns.Resolver - configToDialer := configurl.NewDefaultConfigToDialer() + configModule := configurl.NewDefaultConfigModule() if *tcpFlag { - streamDialer, err := configToDialer.NewStreamDialer(*transportFlag) + streamDialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } resolver = dns.NewTCPResolver(streamDialer, resolverAddr) } else { - packetDialer, err := configToDialer.NewPacketDialer(*transportFlag) + packetDialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create packet dialer: %v", err) } diff --git a/x/examples/smart-proxy/main.go b/x/examples/smart-proxy/main.go index 15490355..b32782a7 100644 --- a/x/examples/smart-proxy/main.go +++ b/x/examples/smart-proxy/main.go @@ -86,12 +86,12 @@ func main() { log.Fatalf("Could not read config: %v", err) } - configToDialer := configurl.NewDefaultConfigToDialer() - packetDialer, err := configToDialer.NewPacketDialer(*transportFlag) + configModule := configurl.NewDefaultConfigModule() + packetDialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create packet dialer: %v", err) } - streamDialer, err := configToDialer.NewStreamDialer(*transportFlag) + streamDialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } diff --git a/x/examples/test-connectivity/main.go b/x/examples/test-connectivity/main.go index 1378ee1b..11a981ae 100644 --- a/x/examples/test-connectivity/main.go +++ b/x/examples/test-connectivity/main.go @@ -240,7 +240,7 @@ func main() { var mu sync.Mutex dnsReports := make([]dnsReport, 0) tcpReports := make([]tcpReport, 0) - configToDialer := configurl.NewDefaultConfigToDialer() + configModule := configurl.NewDefaultConfigModule() onDNS := func(ctx context.Context, domain string) func(di httptrace.DNSDoneInfo) { dnsStart := time.Now() return func(di httptrace.DNSDoneInfo) { @@ -260,7 +260,7 @@ func main() { mu.Unlock() } } - configToDialer.BaseStreamDialer = transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { + configModule.StreamDialers.BaseInstance = transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { hostname, _, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -284,13 +284,13 @@ func main() { } return newTCPTraceDialer(onDNS, onDial).DialStream(ctx, addr) }) - configToDialer.BasePacketDialer = transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { + configModule.PacketDialers.BaseInstance = transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { return newUDPTraceDialer(onDNS).DialPacket(ctx, addr) }) switch proto { case "tcp": - streamDialer, err := configToDialer.NewStreamDialer(*transportFlag) + streamDialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) if err != nil { slog.Error("Failed to create StreamDialer", "error", err) os.Exit(1) @@ -298,7 +298,7 @@ func main() { resolver = dns.NewTCPResolver(streamDialer, resolverAddress) case "udp": - packetDialer, err := configToDialer.NewPacketDialer(*transportFlag) + packetDialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) if err != nil { slog.Error("Failed to create PacketDialer", "error", err) os.Exit(1) diff --git a/x/examples/ws2endpoint/main.go b/x/examples/ws2endpoint/main.go index 5fac6aec..107deff2 100644 --- a/x/examples/ws2endpoint/main.go +++ b/x/examples/ws2endpoint/main.go @@ -60,10 +60,10 @@ func main() { defer listener.Close() log.Printf("Proxy listening on %v\n", listener.Addr().String()) - config2Dialer := configurl.NewDefaultConfigToDialer() + configModule := configurl.NewDefaultConfigModule() mux := http.NewServeMux() if *tcpPathFlag != "" { - dialer, err := config2Dialer.NewStreamDialer(*transportFlag) + dialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } @@ -90,7 +90,7 @@ func main() { mux.Handle(*tcpPathFlag, http.StripPrefix(*tcpPathFlag, handler)) } if *udpPathFlag != "" { - dialer, err := config2Dialer.NewPacketDialer(*transportFlag) + dialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } diff --git a/x/httpproxy/connect_handler.go b/x/httpproxy/connect_handler.go index 69512a7e..e062647c 100644 --- a/x/httpproxy/connect_handler.go +++ b/x/httpproxy/connect_handler.go @@ -52,7 +52,7 @@ func (d *sanitizeErrorDialer) DialStream(ctx context.Context, addr string) (tran type connectHandler struct { dialer *sanitizeErrorDialer - dialerConfig *configurl.ConfigToDialer + configModule *configurl.ConfigModule } var _ http.Handler = (*connectHandler)(nil) @@ -77,7 +77,7 @@ func (h *connectHandler) ServeHTTP(proxyResp http.ResponseWriter, proxyReq *http // Dial the target. transportConfig := proxyReq.Header.Get("Transport") - dialer, err := h.dialerConfig.NewStreamDialer(transportConfig) + dialer, err := h.configModule.NewStreamDialer(proxyReq.Context(), transportConfig) if err != nil { // Because we sanitize the base dialer error, it's safe to return error details here. http.Error(proxyResp, fmt.Sprintf("Invalid config in Transport header: %v", err), http.StatusBadRequest) @@ -149,7 +149,7 @@ func NewConnectHandler(dialer transport.StreamDialer) http.Handler { // of the base dialer (e.g. access key credentials) to the user. sd := &sanitizeErrorDialer{dialer} // TODO(fortuna): Inject the config parser - dialerConfig := configurl.NewDefaultConfigToDialer() - dialerConfig.BaseStreamDialer = sd - return &connectHandler{sd, dialerConfig} + configModule := configurl.NewDefaultConfigModule() + configModule.StreamDialers.BaseInstance = sd + return &connectHandler{sd, configModule} } diff --git a/x/mobileproxy/mobileproxy.go b/x/mobileproxy/mobileproxy.go index 19ea7563..875fab41 100644 --- a/x/mobileproxy/mobileproxy.go +++ b/x/mobileproxy/mobileproxy.go @@ -152,12 +152,12 @@ type StreamDialer struct { transport.StreamDialer } -var configToDialer = configurl.NewDefaultConfigToDialer() +var configModule = configurl.NewDefaultConfigModule() // NewStreamDialerFromConfig creates a [StreamDialer] based on the given config. // The config format is specified in https://pkg.go.dev/github.com/Jigsaw-Code/outline-sdk/x/config#hdr-Config_Format. func NewStreamDialerFromConfig(transportConfig string) (*StreamDialer, error) { - dialer, err := configToDialer.NewStreamDialer(transportConfig) + dialer, err := configModule.NewStreamDialer(context.Background(), transportConfig) if err != nil { return nil, err } diff --git a/x/smart/stream_dialer.go b/x/smart/stream_dialer.go index 092aac1c..1fbb8717 100644 --- a/x/smart/stream_dialer.go +++ b/x/smart/stream_dialer.go @@ -231,8 +231,8 @@ func (f *StrategyFinder) findTLS(ctx context.Context, testDomains []string, base if len(tlsConfig) == 0 { return nil, errors.New("config for TLS is empty. Please specify at least one transport") } - var configToDialer = configurl.NewDefaultConfigToDialer() - configToDialer.BaseStreamDialer = baseDialer + var configModule = configurl.NewDefaultConfigModule() + configModule.StreamDialers.BaseInstance = baseDialer ctx, searchDone := context.WithCancel(ctx) defer searchDone() @@ -242,7 +242,7 @@ func (f *StrategyFinder) findTLS(ctx context.Context, testDomains []string, base Config string } result, err := raceTests(ctx, 250*time.Millisecond, tlsConfig, func(transportCfg string) (*SearchResult, error) { - tlsDialer, err := configToDialer.NewStreamDialer(transportCfg) + tlsDialer, err := configModule.NewStreamDialer(ctx, transportCfg) if err != nil { return nil, fmt.Errorf("WrapStreamDialer failed: %w", err) } From f1941506326439df346e4a18fd1738eae1e075dc Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 29 Oct 2024 16:20:27 -0400 Subject: [PATCH 17/35] Add comments --- x/configurl/config.go | 54 +++++++++++++++++++++++-------------------- x/configurl/doc.go | 14 +++++++---- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index d0854924..9cdcd020 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -22,12 +22,36 @@ import ( "strings" ) +// Config is a pre-parsed generic config created from pipe-separated URLs. +type Config struct { + URL url.URL + BaseConfig *Config +} + +// Provider creates an instance from a config. +type Provider[ObjectType any] interface { + NewInstance(ctx context.Context, config *Config) (ObjectType, error) +} + +// BuildFunc is a convenience type for functions that create a instances of ObjectType given a [Config]. +type BuildFunc[ObjectType any] func(ctx context.Context, config *Config) (ObjectType, error) + +// TypeRegistry registers config types. +type TypeRegistry[ObjectType any] interface { + RegisterType(subtype string, newInstance BuildFunc[ObjectType]) error +} + +// ExtensibleProvider is [Provider] implementation that can be extended via its [TypeRegistry] interface. type ExtensibleProvider[ObjectType comparable] struct { + // Instance to return when config is nil. BaseInstance ObjectType builders map[string]BuildFunc[ObjectType] } -type BuildFunc[ObjectType any] func(ctx context.Context, config *Config) (ObjectType, error) +var ( + _ Provider[any] = (*ExtensibleProvider[any])(nil) + _ TypeRegistry[any] = (*ExtensibleProvider[any])(nil) +) func (p *ExtensibleProvider[ObjectType]) buildersMap() map[string]BuildFunc[ObjectType] { if p.builders == nil { @@ -37,16 +61,16 @@ func (p *ExtensibleProvider[ObjectType]) buildersMap() map[string]BuildFunc[Obje } // RegisterType will register a factory for the given subtype. -func (p *ExtensibleProvider[ObjectType]) RegisterType(subtype string, newDialer BuildFunc[ObjectType]) error { +func (p *ExtensibleProvider[ObjectType]) RegisterType(subtype string, newInstance BuildFunc[ObjectType]) error { builders := p.buildersMap() if _, found := builders[subtype]; found { return fmt.Errorf("type %v registered twice", subtype) } - builders[subtype] = newDialer + builders[subtype] = newInstance return nil } -// NewInstance creates a new instance according to the config. +// NewInstance creates a new instance of ObjectType according to the config. func (p *ExtensibleProvider[ObjectType]) NewInstance(ctx context.Context, config *Config) (ObjectType, error) { var zero ObjectType if config == nil { @@ -63,27 +87,7 @@ func (p *ExtensibleProvider[ObjectType]) NewInstance(ctx context.Context, config return newDialer(ctx, config) } -var ( - _ Provider[any] = (*ExtensibleProvider[any])(nil) - _ TypeRegistry[any] = (*ExtensibleProvider[any])(nil) -) - -// Provider creates an instance from a config. -type Provider[ObjectType any] interface { - NewInstance(ctx context.Context, config *Config) (ObjectType, error) -} - -// TypeRegistry registers config types. -type TypeRegistry[ObjectType any] interface { - RegisterType(subtype string, newInstance BuildFunc[ObjectType]) error -} - -// Transport config. -type Config struct { - URL url.URL - BaseConfig *Config -} - +// ParseConfig will parse a config given as a string and return the structured [Config]. func ParseConfig(configText string) (*Config, error) { parts := strings.Split(strings.TrimSpace(configText), "|") if len(parts) == 1 && parts[0] == "" { diff --git a/x/configurl/doc.go b/x/configurl/doc.go index ad63376a..fb3a9a6d 100644 --- a/x/configurl/doc.go +++ b/x/configurl/doc.go @@ -136,13 +136,17 @@ Defining custom strategies - You can define your custom strategy by implementing // or p := NewDefaultConfigModule() // Register your custom dialer. - p.StreamDialers.RegisterType("custom", newStreamDialerWithCustom) - p.PacketDialers.RegisterType("custom", newPacketDialerWithCustom) - // then use it + p.StreamDialers.RegisterType("custom", func(ctx context.Context, config *Config) (transport.StreamDialer, error) { + // Build logic + // ... + }) + p.PacketDialers.RegisterType("custom", func(ctx context.Context, config *Config) (transport.PacketDialer, error) { + // Build logic + // ... + }) + // Then use it dialer, err := p.NewStreamDialer(context.Background(), "custom://config") -where newStreamDialerWithCustom and newPacketDialerWithCustom implement [BuildFunc[transport.StreamDialer]] and [BuildFunc[transport.PacketDialer]]. - [Onion Routing]: https://en.wikipedia.org/wiki/Onion_routing */ package configurl From 76bcf64e2025280747b6c8491bddb77ad2a11e95 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 29 Oct 2024 16:47:31 -0400 Subject: [PATCH 18/35] Add type assertion --- x/configurl/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/configurl/config.go b/x/configurl/config.go index 9cdcd020..026811d0 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -50,6 +50,7 @@ type ExtensibleProvider[ObjectType comparable] struct { var ( _ Provider[any] = (*ExtensibleProvider[any])(nil) + _ BuildFunc[any] = (*ExtensibleProvider[any])(nil).NewInstance _ TypeRegistry[any] = (*ExtensibleProvider[any])(nil) ) From 866f9c1f1ce52fbc5a6ff91b183c5e057a94bfcd Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 29 Oct 2024 16:49:44 -0400 Subject: [PATCH 19/35] Remove unused Provider --- x/configurl/config.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 026811d0..adb1bf3e 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -28,12 +28,7 @@ type Config struct { BaseConfig *Config } -// Provider creates an instance from a config. -type Provider[ObjectType any] interface { - NewInstance(ctx context.Context, config *Config) (ObjectType, error) -} - -// BuildFunc is a convenience type for functions that create a instances of ObjectType given a [Config]. +// BuildFunc is a function that creates an instance of ObjectType given a [Config]. type BuildFunc[ObjectType any] func(ctx context.Context, config *Config) (ObjectType, error) // TypeRegistry registers config types. @@ -41,7 +36,7 @@ type TypeRegistry[ObjectType any] interface { RegisterType(subtype string, newInstance BuildFunc[ObjectType]) error } -// ExtensibleProvider is [Provider] implementation that can be extended via its [TypeRegistry] interface. +// ExtensibleProvider creates instances of ObjectType in a way that can be extended via its [TypeRegistry] interface. type ExtensibleProvider[ObjectType comparable] struct { // Instance to return when config is nil. BaseInstance ObjectType @@ -49,7 +44,6 @@ type ExtensibleProvider[ObjectType comparable] struct { } var ( - _ Provider[any] = (*ExtensibleProvider[any])(nil) _ BuildFunc[any] = (*ExtensibleProvider[any])(nil).NewInstance _ TypeRegistry[any] = (*ExtensibleProvider[any])(nil) ) From eb793dc29451491b9b7f493a35313e1b90afcc34 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 15:50:55 -0400 Subject: [PATCH 20/35] Allow registration override --- x/configurl/config.go | 11 +++-------- x/configurl/module.go | 12 ++++++------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index adb1bf3e..201c056c 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -33,7 +33,7 @@ type BuildFunc[ObjectType any] func(ctx context.Context, config *Config) (Object // TypeRegistry registers config types. type TypeRegistry[ObjectType any] interface { - RegisterType(subtype string, newInstance BuildFunc[ObjectType]) error + RegisterType(subtype string, newInstance BuildFunc[ObjectType]) } // ExtensibleProvider creates instances of ObjectType in a way that can be extended via its [TypeRegistry] interface. @@ -56,13 +56,8 @@ func (p *ExtensibleProvider[ObjectType]) buildersMap() map[string]BuildFunc[Obje } // RegisterType will register a factory for the given subtype. -func (p *ExtensibleProvider[ObjectType]) RegisterType(subtype string, newInstance BuildFunc[ObjectType]) error { - builders := p.buildersMap() - if _, found := builders[subtype]; found { - return fmt.Errorf("type %v registered twice", subtype) - } - builders[subtype] = newInstance - return nil +func (p *ExtensibleProvider[ObjectType]) RegisterType(subtype string, newInstance BuildFunc[ObjectType]) { + p.buildersMap()[subtype] = newInstance } // NewInstance creates a new instance of ObjectType according to the config. diff --git a/x/configurl/module.go b/x/configurl/module.go index ea6f6a3f..1bafed03 100644 --- a/x/configurl/module.go +++ b/x/configurl/module.go @@ -66,18 +66,18 @@ func NewDefaultConfigModule() *ConfigModule { } // RegisterStreamDialerType will register a factory for stream dialers under the given subtype. -func (p *ConfigModule) RegisterStreamDialerType(subtype string, newInstance BuildFunc[transport.StreamDialer]) error { - return p.StreamDialers.RegisterType(subtype, newInstance) +func (p *ConfigModule) RegisterStreamDialerType(subtype string, newInstance BuildFunc[transport.StreamDialer]) { + p.StreamDialers.RegisterType(subtype, newInstance) } // RegisterPacketDialerType will register a factory for packet dialers under the given subtype. -func (p *ConfigModule) RegisterPacketDialerType(subtype string, newInstance BuildFunc[transport.PacketDialer]) error { - return p.PacketDialers.RegisterType(subtype, newInstance) +func (p *ConfigModule) RegisterPacketDialerType(subtype string, newInstance BuildFunc[transport.PacketDialer]) { + p.PacketDialers.RegisterType(subtype, newInstance) } // RegisterPacketListenerType will register a factory for packet listeners under the given subtype. -func (p *ConfigModule) RegisterPacketListenerType(subtype string, newInstance BuildFunc[transport.PacketListener]) error { - return p.PacketListeners.RegisterType(subtype, newInstance) +func (p *ConfigModule) RegisterPacketListenerType(subtype string, newInstance BuildFunc[transport.PacketListener]) { + p.PacketListeners.RegisterType(subtype, newInstance) } // NewStreamDialer creates a [transport.StreamDialer] according to the config text. From a848931b2efb0639465312726dd38742971623ea Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 15:52:10 -0400 Subject: [PATCH 21/35] Rename newDialer --- x/configurl/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 201c056c..13de384b 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -70,11 +70,11 @@ func (p *ExtensibleProvider[ObjectType]) NewInstance(ctx context.Context, config return p.BaseInstance, nil } - newDialer, ok := p.buildersMap()[config.URL.Scheme] + newInstance, ok := p.buildersMap()[config.URL.Scheme] if !ok { return zero, fmt.Errorf("config type '%v' is not registered", config.URL.Scheme) } - return newDialer(ctx, config) + return newInstance(ctx, config) } // ParseConfig will parse a config given as a string and return the structured [Config]. From 0e483c7ff29f0aee831f138a545de6979089a48c Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 15:53:04 -0400 Subject: [PATCH 22/35] Update doc --- x/configurl/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/configurl/doc.go b/x/configurl/doc.go index fb3a9a6d..d7b0f564 100644 --- a/x/configurl/doc.go +++ b/x/configurl/doc.go @@ -17,7 +17,7 @@ Package configurl provides convenience functions to create network objects based This is experimental and mostly for illustrative purposes at this point. Configurable strategies simplifies the way you create and manage strategies. -With the configurl package, you can use [NewPacketDialer], [NewStreamDialer] and [NewPacketListener] to create objects using a simple text string. +With the configurl package, you can use [ConfigModule.NewPacketDialer], [ConfigModule.NewStreamDialer] and [ConfigModule.NewPacketListener] to create objects using a simple text string. Key Benefits: From b3fbf2bded71729317cb3a7a0136c07081344a60 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 15:54:40 -0400 Subject: [PATCH 23/35] Remove register methods --- x/configurl/module.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/x/configurl/module.go b/x/configurl/module.go index 1bafed03..03381f64 100644 --- a/x/configurl/module.go +++ b/x/configurl/module.go @@ -65,21 +65,6 @@ func NewDefaultConfigModule() *ConfigModule { return p } -// RegisterStreamDialerType will register a factory for stream dialers under the given subtype. -func (p *ConfigModule) RegisterStreamDialerType(subtype string, newInstance BuildFunc[transport.StreamDialer]) { - p.StreamDialers.RegisterType(subtype, newInstance) -} - -// RegisterPacketDialerType will register a factory for packet dialers under the given subtype. -func (p *ConfigModule) RegisterPacketDialerType(subtype string, newInstance BuildFunc[transport.PacketDialer]) { - p.PacketDialers.RegisterType(subtype, newInstance) -} - -// RegisterPacketListenerType will register a factory for packet listeners under the given subtype. -func (p *ConfigModule) RegisterPacketListenerType(subtype string, newInstance BuildFunc[transport.PacketListener]) { - p.PacketListeners.RegisterType(subtype, newInstance) -} - // NewStreamDialer creates a [transport.StreamDialer] according to the config text. func (p *ConfigModule) NewStreamDialer(ctx context.Context, configText string) (transport.StreamDialer, error) { config, err := ParseConfig(configText) From 308ff7dd4affbef226c252a0b9566bc1c98052db Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 16:01:11 -0400 Subject: [PATCH 24/35] Add comment to SanitizeConfig --- x/configurl/module.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/configurl/module.go b/x/configurl/module.go index 03381f64..c3be6183 100644 --- a/x/configurl/module.go +++ b/x/configurl/module.go @@ -92,6 +92,7 @@ func (p *ConfigModule) NewPacketListener(ctx context.Context, configText string) return p.PacketListeners.NewInstance(ctx, config) } +// SanitizeConfig removes sensitive information from the given config so it can be safely be used in logging and debugging. func SanitizeConfig(configStr string) (string, error) { config, err := ParseConfig(configStr) if err != nil { From ffbdf49771aedb3fc2e5dad3ea2a9bcaa3876b16 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 16:02:41 -0400 Subject: [PATCH 25/35] Rename config --- x/configurl/tls_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/configurl/tls_test.go b/x/configurl/tls_test.go index 773fd97f..0aa59dc6 100644 --- a/x/configurl/tls_test.go +++ b/x/configurl/tls_test.go @@ -22,9 +22,9 @@ import ( ) func TestTLS_SNI(t *testing.T) { - tlsURL, err := ParseConfig("tls:sni=www.google.com") + config, err := ParseConfig("tls:sni=www.google.com") require.NoError(t, err) - options, err := parseOptions(tlsURL.URL) + options, err := parseOptions(config.URL) require.NoError(t, err) cfg := tls.ClientConfig{ServerName: "host", CertificateName: "host"} for _, option := range options { From fadf31497421d6c71e5d2218f787cb2a7077b33d Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 16:03:02 -0400 Subject: [PATCH 26/35] Update Copyright line --- x/configurl/tlsfrag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/configurl/tlsfrag.go b/x/configurl/tlsfrag.go index 1e2ef347..4e2e0fe9 100644 --- a/x/configurl/tlsfrag.go +++ b/x/configurl/tlsfrag.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Outline Authors +// 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. From 47c9ad63a8bdf4f28b2294dc7582510931e744f7 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 17:57:22 -0400 Subject: [PATCH 27/35] Use Providers --- x/configurl/doc.go | 2 +- x/configurl/module.go | 66 +++++++++++-------- x/examples/fetch-speed/main.go | 2 +- x/examples/fetch/main.go | 2 +- x/examples/http2transport/main.go | 2 +- x/examples/outline-cli/outline_device.go | 2 +- .../outline-cli/outline_packet_proxy.go | 2 +- x/examples/resolve/main.go | 6 +- x/examples/smart-proxy/main.go | 6 +- x/examples/test-connectivity/main.go | 10 +-- x/examples/ws2endpoint/main.go | 6 +- x/httpproxy/connect_handler.go | 12 ++-- x/mobileproxy/mobileproxy.go | 2 +- x/smart/stream_dialer.go | 2 +- 14 files changed, 65 insertions(+), 57 deletions(-) diff --git a/x/configurl/doc.go b/x/configurl/doc.go index d7b0f564..38243465 100644 --- a/x/configurl/doc.go +++ b/x/configurl/doc.go @@ -17,7 +17,7 @@ Package configurl provides convenience functions to create network objects based This is experimental and mostly for illustrative purposes at this point. Configurable strategies simplifies the way you create and manage strategies. -With the configurl package, you can use [ConfigModule.NewPacketDialer], [ConfigModule.NewStreamDialer] and [ConfigModule.NewPacketListener] to create objects using a simple text string. +With the configurl package, you can use [ProviderContainer.NewPacketDialer], [ProviderContainer.NewStreamDialer] and [ProviderContainer.NewPacketListener] to create objects using a simple text string. Key Benefits: diff --git a/x/configurl/module.go b/x/configurl/module.go index c3be6183..00ee7f63 100644 --- a/x/configurl/module.go +++ b/x/configurl/module.go @@ -22,51 +22,59 @@ import ( "github.com/Jigsaw-Code/outline-sdk/transport" ) -// ConfigModule enables the creation of network objects based on a config. The config is -// extensible by registering providers for config subtypes. -type ConfigModule struct { +// 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[transport.StreamDialer] PacketDialers ExtensibleProvider[transport.PacketDialer] PacketListeners ExtensibleProvider[transport.PacketListener] } -// NewDefaultConfigModule creates a [ConfigModule] with a set of default wrappers already registered. -func NewDefaultConfigModule() *ConfigModule { - p := new(ConfigModule) - - p.StreamDialers.BaseInstance = &transport.TCPDialer{} - p.PacketDialers.BaseInstance = &transport.UDPDialer{} - p.PacketListeners.BaseInstance = &transport.UDPListener{} +// NewProviderContainer creates a [ProviderContainer] with the base instances properly initialized. +func NewProviderContainer() *ProviderContainer { + return &ProviderContainer{ + StreamDialers: ExtensibleProvider[transport.StreamDialer]{BaseInstance: &transport.TCPDialer{}}, + PacketDialers: ExtensibleProvider[transport.PacketDialer]{BaseInstance: &transport.UDPDialer{}}, + PacketListeners: ExtensibleProvider[transport.PacketListener]{BaseInstance: &transport.UDPListener{}}, + } +} +// RegisterDefaultProviders registers a set of default providers with the providers in [ProviderContainer]. +func RegisterDefaultProviders(c *ProviderContainer) *ProviderContainer { // Please keep the list in alphabetical order. - registerDO53StreamDialer(&p.StreamDialers, "do53", p.StreamDialers.NewInstance, p.PacketDialers.NewInstance) - registerDOHStreamDialer(&p.StreamDialers, "doh", p.StreamDialers.NewInstance) + registerDO53StreamDialer(&c.StreamDialers, "do53", c.StreamDialers.NewInstance, c.PacketDialers.NewInstance) + registerDOHStreamDialer(&c.StreamDialers, "doh", c.StreamDialers.NewInstance) - registerOverrideStreamDialer(&p.StreamDialers, "override", p.StreamDialers.NewInstance) - registerOverridePacketDialer(&p.PacketDialers, "override", p.PacketDialers.NewInstance) + registerOverrideStreamDialer(&c.StreamDialers, "override", c.StreamDialers.NewInstance) + registerOverridePacketDialer(&c.PacketDialers, "override", c.PacketDialers.NewInstance) - registerSOCKS5StreamDialer(&p.StreamDialers, "socks5", p.StreamDialers.NewInstance) - registerSOCKS5PacketDialer(&p.PacketDialers, "socks5", p.StreamDialers.NewInstance, p.PacketDialers.NewInstance) - registerSOCKS5PacketListener(&p.PacketListeners, "socks5", p.StreamDialers.NewInstance, p.PacketDialers.NewInstance) + registerSOCKS5StreamDialer(&c.StreamDialers, "socks5", c.StreamDialers.NewInstance) + registerSOCKS5PacketDialer(&c.PacketDialers, "socks5", c.StreamDialers.NewInstance, c.PacketDialers.NewInstance) + registerSOCKS5PacketListener(&c.PacketListeners, "socks5", c.StreamDialers.NewInstance, c.PacketDialers.NewInstance) - registerSplitStreamDialer(&p.StreamDialers, "split", p.StreamDialers.NewInstance) + registerSplitStreamDialer(&c.StreamDialers, "split", c.StreamDialers.NewInstance) - registerShadowsocksStreamDialer(&p.StreamDialers, "ss", p.StreamDialers.NewInstance) - registerShadowsocksPacketDialer(&p.PacketDialers, "ss", p.PacketDialers.NewInstance) - registerShadowsocksPacketListener(&p.PacketListeners, "ss", p.PacketDialers.NewInstance) + registerShadowsocksStreamDialer(&c.StreamDialers, "ss", c.StreamDialers.NewInstance) + registerShadowsocksPacketDialer(&c.PacketDialers, "ss", c.PacketDialers.NewInstance) + registerShadowsocksPacketListener(&c.PacketListeners, "ss", c.PacketDialers.NewInstance) - registerTLSStreamDialer(&p.StreamDialers, "tls", p.StreamDialers.NewInstance) + registerTLSStreamDialer(&c.StreamDialers, "tls", c.StreamDialers.NewInstance) - registerTLSFragStreamDialer(&p.StreamDialers, "tlsfrag", p.StreamDialers.NewInstance) + registerTLSFragStreamDialer(&c.StreamDialers, "tlsfrag", c.StreamDialers.NewInstance) - registerWebsocketStreamDialer(&p.StreamDialers, "ws", p.StreamDialers.NewInstance) - registerWebsocketPacketDialer(&p.PacketDialers, "ws", p.StreamDialers.NewInstance) + registerWebsocketStreamDialer(&c.StreamDialers, "ws", c.StreamDialers.NewInstance) + registerWebsocketPacketDialer(&c.PacketDialers, "ws", c.StreamDialers.NewInstance) + + return c +} - return p +// NewDefaultProviders creates a [ProviderContainer] with a set of default providers already registered. +func NewDefaultProviders() *ProviderContainer { + return RegisterDefaultProviders(NewProviderContainer()) } // NewStreamDialer creates a [transport.StreamDialer] according to the config text. -func (p *ConfigModule) NewStreamDialer(ctx context.Context, configText string) (transport.StreamDialer, error) { +func (p *ProviderContainer) NewStreamDialer(ctx context.Context, configText string) (transport.StreamDialer, error) { config, err := ParseConfig(configText) if err != nil { return nil, err @@ -75,7 +83,7 @@ func (p *ConfigModule) NewStreamDialer(ctx context.Context, configText string) ( } // NewPacketDialer creates a [transport.PacketDialer] according to the config text. -func (p *ConfigModule) NewPacketDialer(ctx context.Context, configText string) (transport.PacketDialer, error) { +func (p *ProviderContainer) NewPacketDialer(ctx context.Context, configText string) (transport.PacketDialer, error) { config, err := ParseConfig(configText) if err != nil { return nil, err @@ -84,7 +92,7 @@ func (p *ConfigModule) NewPacketDialer(ctx context.Context, configText string) ( } // NewPacketListner creates a [transport.PacketListener] according to the config text. -func (p *ConfigModule) NewPacketListener(ctx context.Context, configText string) (transport.PacketListener, error) { +func (p *ProviderContainer) NewPacketListener(ctx context.Context, configText string) (transport.PacketListener, error) { config, err := ParseConfig(configText) if err != nil { return nil, err diff --git a/x/examples/fetch-speed/main.go b/x/examples/fetch-speed/main.go index e896f637..f6b131a7 100644 --- a/x/examples/fetch-speed/main.go +++ b/x/examples/fetch-speed/main.go @@ -58,7 +58,7 @@ func main() { os.Exit(1) } - dialer, err := configurl.NewDefaultConfigModule().NewStreamDialer(context.Background(), *transportFlag) + dialer, err := configurl.NewDefaultProviders().NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create dialer: %v\n", err) } diff --git a/x/examples/fetch/main.go b/x/examples/fetch/main.go index db81f9c4..3b7d2d02 100644 --- a/x/examples/fetch/main.go +++ b/x/examples/fetch/main.go @@ -84,7 +84,7 @@ func main() { os.Exit(1) } - dialer, err := configurl.NewDefaultConfigModule().NewStreamDialer(context.Background(), *transportFlag) + dialer, err := configurl.NewDefaultProviders().NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create dialer: %v\n", err) } diff --git a/x/examples/http2transport/main.go b/x/examples/http2transport/main.go index 66559d61..a0489fae 100644 --- a/x/examples/http2transport/main.go +++ b/x/examples/http2transport/main.go @@ -34,7 +34,7 @@ func main() { urlProxyPrefixFlag := flag.String("urlProxyPrefix", "/proxy", "Path where to run the URL proxy. Set to empty (\"\") to disable it.") flag.Parse() - dialer, err := configurl.NewDefaultConfigModule().NewStreamDialer(context.Background(), *transportFlag) + dialer, err := configurl.NewDefaultProviders().NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create dialer: %v", err) diff --git a/x/examples/outline-cli/outline_device.go b/x/examples/outline-cli/outline_device.go index a7cadb2a..61333bf8 100644 --- a/x/examples/outline-cli/outline_device.go +++ b/x/examples/outline-cli/outline_device.go @@ -40,7 +40,7 @@ type OutlineDevice struct { svrIP net.IP } -var configModule = configurl.NewDefaultConfigModule() +var configModule = configurl.NewDefaultProviders() func NewOutlineDevice(transportConfig string) (od *OutlineDevice, err error) { ip, err := resolveShadowsocksServerIPFromConfig(transportConfig) diff --git a/x/examples/outline-cli/outline_packet_proxy.go b/x/examples/outline-cli/outline_packet_proxy.go index c98b5a53..7c0b314b 100644 --- a/x/examples/outline-cli/outline_packet_proxy.go +++ b/x/examples/outline-cli/outline_packet_proxy.go @@ -36,7 +36,7 @@ type outlinePacketProxy struct { func newOutlinePacketProxy(transportConfig string) (opp *outlinePacketProxy, err error) { opp = &outlinePacketProxy{} - if opp.remotePl, err = configurl.NewDefaultConfigModule().NewPacketListener(context.TODO(), transportConfig); err != nil { + if opp.remotePl, err = configurl.NewDefaultProviders().NewPacketListener(context.TODO(), transportConfig); err != nil { return nil, fmt.Errorf("failed to create UDP packet listener: %w", err) } if opp.remote, err = network.NewPacketProxyFromPacketListener(opp.remotePl); err != nil { diff --git a/x/examples/resolve/main.go b/x/examples/resolve/main.go index d8a24976..e55dd790 100644 --- a/x/examples/resolve/main.go +++ b/x/examples/resolve/main.go @@ -66,15 +66,15 @@ func main() { resolverAddr := *resolverFlag var resolver dns.Resolver - configModule := configurl.NewDefaultConfigModule() + providers := configurl.NewDefaultProviders() if *tcpFlag { - streamDialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) + streamDialer, err := providers.NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } resolver = dns.NewTCPResolver(streamDialer, resolverAddr) } else { - packetDialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) + packetDialer, err := providers.NewPacketDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create packet dialer: %v", err) } diff --git a/x/examples/smart-proxy/main.go b/x/examples/smart-proxy/main.go index b32782a7..59d40b28 100644 --- a/x/examples/smart-proxy/main.go +++ b/x/examples/smart-proxy/main.go @@ -86,12 +86,12 @@ func main() { log.Fatalf("Could not read config: %v", err) } - configModule := configurl.NewDefaultConfigModule() - packetDialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) + providers := configurl.NewDefaultProviders() + packetDialer, err := providers.NewPacketDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create packet dialer: %v", err) } - streamDialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) + streamDialer, err := providers.NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } diff --git a/x/examples/test-connectivity/main.go b/x/examples/test-connectivity/main.go index 11a981ae..7197b6f7 100644 --- a/x/examples/test-connectivity/main.go +++ b/x/examples/test-connectivity/main.go @@ -240,7 +240,7 @@ func main() { var mu sync.Mutex dnsReports := make([]dnsReport, 0) tcpReports := make([]tcpReport, 0) - configModule := configurl.NewDefaultConfigModule() + providers := configurl.NewDefaultProviders() onDNS := func(ctx context.Context, domain string) func(di httptrace.DNSDoneInfo) { dnsStart := time.Now() return func(di httptrace.DNSDoneInfo) { @@ -260,7 +260,7 @@ func main() { mu.Unlock() } } - configModule.StreamDialers.BaseInstance = transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { + providers.StreamDialers.BaseInstance = transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { hostname, _, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -284,13 +284,13 @@ func main() { } return newTCPTraceDialer(onDNS, onDial).DialStream(ctx, addr) }) - configModule.PacketDialers.BaseInstance = transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { + providers.PacketDialers.BaseInstance = transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { return newUDPTraceDialer(onDNS).DialPacket(ctx, addr) }) switch proto { case "tcp": - streamDialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) + streamDialer, err := providers.NewStreamDialer(context.Background(), *transportFlag) if err != nil { slog.Error("Failed to create StreamDialer", "error", err) os.Exit(1) @@ -298,7 +298,7 @@ func main() { resolver = dns.NewTCPResolver(streamDialer, resolverAddress) case "udp": - packetDialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) + packetDialer, err := providers.NewPacketDialer(context.Background(), *transportFlag) if err != nil { slog.Error("Failed to create PacketDialer", "error", err) os.Exit(1) diff --git a/x/examples/ws2endpoint/main.go b/x/examples/ws2endpoint/main.go index 107deff2..e09865e7 100644 --- a/x/examples/ws2endpoint/main.go +++ b/x/examples/ws2endpoint/main.go @@ -60,10 +60,10 @@ func main() { defer listener.Close() log.Printf("Proxy listening on %v\n", listener.Addr().String()) - configModule := configurl.NewDefaultConfigModule() + providers := configurl.NewDefaultProviders() mux := http.NewServeMux() if *tcpPathFlag != "" { - dialer, err := configModule.NewStreamDialer(context.Background(), *transportFlag) + dialer, err := providers.NewStreamDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } @@ -90,7 +90,7 @@ func main() { mux.Handle(*tcpPathFlag, http.StripPrefix(*tcpPathFlag, handler)) } if *udpPathFlag != "" { - dialer, err := configModule.NewPacketDialer(context.Background(), *transportFlag) + dialer, err := providers.NewPacketDialer(context.Background(), *transportFlag) if err != nil { log.Fatalf("Could not create stream dialer: %v", err) } diff --git a/x/httpproxy/connect_handler.go b/x/httpproxy/connect_handler.go index e062647c..0922cce2 100644 --- a/x/httpproxy/connect_handler.go +++ b/x/httpproxy/connect_handler.go @@ -51,8 +51,8 @@ func (d *sanitizeErrorDialer) DialStream(ctx context.Context, addr string) (tran } type connectHandler struct { - dialer *sanitizeErrorDialer - configModule *configurl.ConfigModule + dialer *sanitizeErrorDialer + providers *configurl.ProviderContainer } var _ http.Handler = (*connectHandler)(nil) @@ -77,7 +77,7 @@ func (h *connectHandler) ServeHTTP(proxyResp http.ResponseWriter, proxyReq *http // Dial the target. transportConfig := proxyReq.Header.Get("Transport") - dialer, err := h.configModule.NewStreamDialer(proxyReq.Context(), transportConfig) + dialer, err := h.providers.NewStreamDialer(proxyReq.Context(), transportConfig) if err != nil { // Because we sanitize the base dialer error, it's safe to return error details here. http.Error(proxyResp, fmt.Sprintf("Invalid config in Transport header: %v", err), http.StatusBadRequest) @@ -149,7 +149,7 @@ func NewConnectHandler(dialer transport.StreamDialer) http.Handler { // of the base dialer (e.g. access key credentials) to the user. sd := &sanitizeErrorDialer{dialer} // TODO(fortuna): Inject the config parser - configModule := configurl.NewDefaultConfigModule() - configModule.StreamDialers.BaseInstance = sd - return &connectHandler{sd, configModule} + providers := configurl.NewDefaultProviders() + providers.StreamDialers.BaseInstance = sd + return &connectHandler{sd, providers} } diff --git a/x/mobileproxy/mobileproxy.go b/x/mobileproxy/mobileproxy.go index 875fab41..5b832b7b 100644 --- a/x/mobileproxy/mobileproxy.go +++ b/x/mobileproxy/mobileproxy.go @@ -152,7 +152,7 @@ type StreamDialer struct { transport.StreamDialer } -var configModule = configurl.NewDefaultConfigModule() +var configModule = configurl.NewDefaultProviders() // NewStreamDialerFromConfig creates a [StreamDialer] based on the given config. // The config format is specified in https://pkg.go.dev/github.com/Jigsaw-Code/outline-sdk/x/config#hdr-Config_Format. diff --git a/x/smart/stream_dialer.go b/x/smart/stream_dialer.go index 1fbb8717..54c089ef 100644 --- a/x/smart/stream_dialer.go +++ b/x/smart/stream_dialer.go @@ -231,7 +231,7 @@ func (f *StrategyFinder) findTLS(ctx context.Context, testDomains []string, base if len(tlsConfig) == 0 { return nil, errors.New("config for TLS is empty. Please specify at least one transport") } - var configModule = configurl.NewDefaultConfigModule() + var configModule = configurl.NewDefaultProviders() configModule.StreamDialers.BaseInstance = baseDialer ctx, searchDone := context.WithCancel(ctx) From a716c1f241dd22eaa2aa573c39a9cc79408f5947 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 18:36:29 -0400 Subject: [PATCH 28/35] Cleanup --- x/configurl/config.go | 14 +++++++++++--- x/configurl/module.go | 6 +++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/x/configurl/config.go b/x/configurl/config.go index 13de384b..e338c950 100644 --- a/x/configurl/config.go +++ b/x/configurl/config.go @@ -48,7 +48,15 @@ var ( _ TypeRegistry[any] = (*ExtensibleProvider[any])(nil) ) -func (p *ExtensibleProvider[ObjectType]) buildersMap() map[string]BuildFunc[ObjectType] { +// NewExtensibleProvider creates an [ExtensibleProvider] with the given base instance. +func NewExtensibleProvider[ObjectType comparable](baseInstance ObjectType) ExtensibleProvider[ObjectType] { + return ExtensibleProvider[ObjectType]{ + BaseInstance: baseInstance, + builders: make(map[string]BuildFunc[ObjectType]), + } +} + +func (p *ExtensibleProvider[ObjectType]) ensureBuildersMap() map[string]BuildFunc[ObjectType] { if p.builders == nil { p.builders = make(map[string]BuildFunc[ObjectType]) } @@ -57,7 +65,7 @@ func (p *ExtensibleProvider[ObjectType]) buildersMap() map[string]BuildFunc[Obje // RegisterType will register a factory for the given subtype. func (p *ExtensibleProvider[ObjectType]) RegisterType(subtype string, newInstance BuildFunc[ObjectType]) { - p.buildersMap()[subtype] = newInstance + p.ensureBuildersMap()[subtype] = newInstance } // NewInstance creates a new instance of ObjectType according to the config. @@ -70,7 +78,7 @@ func (p *ExtensibleProvider[ObjectType]) NewInstance(ctx context.Context, config return p.BaseInstance, nil } - newInstance, ok := p.buildersMap()[config.URL.Scheme] + newInstance, ok := p.ensureBuildersMap()[config.URL.Scheme] if !ok { return zero, fmt.Errorf("config type '%v' is not registered", config.URL.Scheme) } diff --git a/x/configurl/module.go b/x/configurl/module.go index 00ee7f63..83e14b89 100644 --- a/x/configurl/module.go +++ b/x/configurl/module.go @@ -33,9 +33,9 @@ type ProviderContainer struct { // NewProviderContainer creates a [ProviderContainer] with the base instances properly initialized. func NewProviderContainer() *ProviderContainer { return &ProviderContainer{ - StreamDialers: ExtensibleProvider[transport.StreamDialer]{BaseInstance: &transport.TCPDialer{}}, - PacketDialers: ExtensibleProvider[transport.PacketDialer]{BaseInstance: &transport.UDPDialer{}}, - PacketListeners: ExtensibleProvider[transport.PacketListener]{BaseInstance: &transport.UDPListener{}}, + StreamDialers: NewExtensibleProvider[transport.StreamDialer](&transport.TCPDialer{}), + PacketDialers: NewExtensibleProvider[transport.PacketDialer](&transport.UDPDialer{}), + PacketListeners: NewExtensibleProvider[transport.PacketListener](&transport.UDPListener{}), } } From b3fec8ace815ebe2af05abb1769c772704c6bb89 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 18:37:09 -0400 Subject: [PATCH 29/35] Remove Close --- x/configurl/dns.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/x/configurl/dns.go b/x/configurl/dns.go index 2314dbd1..2e41ff59 100644 --- a/x/configurl/dns.go +++ b/x/configurl/dns.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "io" "net" "net/url" "strings" @@ -37,16 +36,10 @@ func registerDO53StreamDialer(r TypeRegistry[transport.StreamDialer], typeID str if err != nil { return nil, err } - if closer, ok := sd.(io.Closer); ok { - defer closer.Close() - } pd, err := newPD(ctx, config.BaseConfig) if err != nil { return nil, err } - if closer, ok := pd.(io.Closer); ok { - defer closer.Close() - } resolver, err := newDO53Resolver(config.URL, sd, pd) if err != nil { return nil, err From f9b97cd59f5c9ff8298fcebfbc86401e367fa8d6 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 30 Oct 2024 18:44:34 -0400 Subject: [PATCH 30/35] Update Go mod --- x/go.mod | 2 +- x/go.sum | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/x/go.mod b/x/go.mod index a1dfe72a..7ad5e856 100644 --- a/x/go.mod +++ b/x/go.mod @@ -3,7 +3,7 @@ module github.com/Jigsaw-Code/outline-sdk/x go 1.21 require ( - github.com/Jigsaw-Code/outline-sdk v0.0.17-0.20240726212635-470a9290ec57 + github.com/Jigsaw-Code/outline-sdk v0.0.17 // Use github.com/Psiphon-Labs/psiphon-tunnel-core@staging-client as per // https://github.com/Psiphon-Labs/psiphon-tunnel-core/?tab=readme-ov-file#using-psiphon-with-go-modules github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20240619172145-03cade11f647 diff --git a/x/go.sum b/x/go.sum index 033308fb..67b2fc27 100644 --- a/x/go.sum +++ b/x/go.sum @@ -6,8 +6,8 @@ github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/Jigsaw-Code/outline-sdk v0.0.17-0.20240726212635-470a9290ec57 h1:XNSV0dGW48J8DmdmCnk/txGHf9glAPqa6Xme/rFWn7c= -github.com/Jigsaw-Code/outline-sdk v0.0.17-0.20240726212635-470a9290ec57/go.mod h1:e1oQZbSdLJBBuHgfeQsgEkvkuyIePPwstUeZRGq0KO8= +github.com/Jigsaw-Code/outline-sdk v0.0.17 h1:xkGsp3fs+5EbIZEeCEPI1rl0oyCrOLuO7YSK0gB75HE= +github.com/Jigsaw-Code/outline-sdk v0.0.17/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= @@ -72,8 +72,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439 h1:T6zlOdzrYuHf6HUKujm9bzkzbZ5Iv/xf6rs8BHZDpoI= github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -99,9 +99,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= From 6783ebf60dac91c39397fd18b7064c947bcc1916 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 1 Nov 2024 16:01:55 -0400 Subject: [PATCH 31/35] Use slog --- x/examples/fetch/main.go | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/x/examples/fetch/main.go b/x/examples/fetch/main.go index b5ee2c71..0b0ae843 100644 --- a/x/examples/fetch/main.go +++ b/x/examples/fetch/main.go @@ -21,7 +21,6 @@ import ( "flag" "fmt" "io" - "log" "log/slog" "net" "net/http" @@ -104,7 +103,7 @@ func main() { url := flag.Arg(0) if url == "" { - log.Println("Need to pass the URL to fetch in the command-line") + slog.Error("Need to pass the URL to fetch in the command-line") flag.Usage() os.Exit(1) } @@ -120,7 +119,8 @@ func main() { if *protoFlag == "h1" || *protoFlag == "h2" { dialer, err := configModule.NewStreamDialer(*transportFlag) if err != nil { - log.Fatalf("Could not create dialer: %v\n", err) + slog.Error("Could not create dialer", "error", err) + os.Exit(1) } dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { addressToDial, err := overrideAddress(addr, overrideHost, overridePort) @@ -147,11 +147,13 @@ func main() { } else if *protoFlag == "h3" { listener, err := configModule.NewPacketListener(*transportFlag) if err != nil { - log.Fatalf("Could not create listener: %v\n", err) + slog.Error("Could not create listener", "error", err) + os.Exit(1) } conn, err := listener.ListenPacket(context.Background()) if err != nil { - log.Fatalf("Could not create PacketConn: %v\n", err) + slog.Error("Could not create PacketConn", "error", err) + os.Exit(1) } tr := &quic.Transport{ Conn: conn, @@ -169,17 +171,20 @@ func main() { Logger: slog.Default(), } } else { - log.Fatalln("Invalid HTTP protocol: ", *protoFlag) + slog.Error("Invalid HTTP protocol", "proto", *protoFlag) + os.Exit(1) } req, err := http.NewRequest(*methodFlag, url, nil) if err != nil { - log.Fatalln("Failed to create request:", err) + slog.Error("Failed to create request", "error", err) + os.Exit(1) } headerText := strings.Join(headersFlag, "\r\n") + "\r\n\r\n" h, err := textproto.NewReader(bufio.NewReader(strings.NewReader(headerText))).ReadMIMEHeader() if err != nil { - log.Fatalf("invalid header line: %v", err) + slog.Error("Invalid header line", "error", err) + os.Exit(1) } for name, values := range h { for _, value := range values { @@ -188,7 +193,8 @@ func main() { } resp, err := httpClient.Do(req) if err != nil { - log.Fatalf("HTTP request failed: %v\n", err) + slog.Error("HTTP request failed", "error", err) + os.Exit(1) } defer resp.Body.Close() @@ -203,6 +209,7 @@ func main() { _, err = io.Copy(os.Stdout, resp.Body) fmt.Println() if err != nil { - log.Fatalf("Read of page body failed: %v\n", err) + slog.Error("Read of page body failed", "error", err) + os.Exit(1) } } From 34682f1885cb287919b5fab119260e993fddf663 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 1 Nov 2024 16:23:14 -0400 Subject: [PATCH 32/35] Add key log --- x/examples/fetch/main.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/x/examples/fetch/main.go b/x/examples/fetch/main.go index 0b0ae843..225e23d2 100644 --- a/x/examples/fetch/main.go +++ b/x/examples/fetch/main.go @@ -71,6 +71,7 @@ func overrideAddress(original string, newHost string, newPort string) (string, e func main() { verboseFlag := flag.Bool("v", false, "Enable debug output") + tlsKeyLogFlag := flag.String("tls-key-log", "", "Filename to write the TLS key log to allow for decryption on Wireshark.") protoFlag := flag.String("proto", "h1", "HTTP version to use (h1, h2, h3)") transportFlag := flag.String("transport", "", "Transport config") addressFlag := flag.String("address", "", "Address to connect to. If empty, use the URL authority") @@ -115,6 +116,16 @@ func main() { }, } + var tlsConfig tls.Config + if *tlsKeyLogFlag != "" { + f, err := os.Create(*tlsKeyLogFlag) + if err != nil { + slog.Error("Failed to creare TLS key log file", "error", err) + os.Exit(1) + } + defer f.Close() + tlsConfig.KeyLogWriter = f + } configModule := configurl.NewDefaultConfigToDialer() if *protoFlag == "h1" || *protoFlag == "h2" { dialer, err := configModule.NewStreamDialer(*transportFlag) @@ -133,14 +144,16 @@ func main() { return dialer.DialStream(ctx, addressToDial) } if *protoFlag == "h1" { + tlsConfig.NextProtos = []string{"http/1.1"} httpClient.Transport = &http.Transport{ DialContext: dialContext, - TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1"}}, + TLSClientConfig: &tlsConfig, } } else if *protoFlag == "h2" { + tlsConfig.NextProtos = []string{"h2"} httpClient.Transport = &http.Transport{ DialContext: dialContext, - TLSClientConfig: &tls.Config{NextProtos: []string{"h2"}}, + TLSClientConfig: &tlsConfig, ForceAttemptHTTP2: true, } } @@ -160,6 +173,7 @@ func main() { } defer tr.Close() httpClient.Transport = &http3.Transport{ + TLSClientConfig: &tlsConfig, EnableDatagrams: true, Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) { a, err := net.ResolveUDPAddr("udp", addr) From 3fae6e022f934847473d968a5f7ae7e1cbd8685b Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 1 Nov 2024 16:37:24 -0400 Subject: [PATCH 33/35] Cleanup --- x/examples/fetch/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/examples/fetch/main.go b/x/examples/fetch/main.go index 225e23d2..67d4e9af 100644 --- a/x/examples/fetch/main.go +++ b/x/examples/fetch/main.go @@ -174,7 +174,6 @@ func main() { defer tr.Close() httpClient.Transport = &http3.Transport{ TLSClientConfig: &tlsConfig, - EnableDatagrams: true, Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) { a, err := net.ResolveUDPAddr("udp", addr) if err != nil { From 3e1442ca191a08e5039e3de68e6a834721d1c217 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 1 Nov 2024 17:25:14 -0400 Subject: [PATCH 34/35] update mod --- x/go.mod | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x/go.mod b/x/go.mod index 9473981a..ef66a9d5 100644 --- a/x/go.mod +++ b/x/go.mod @@ -2,14 +2,13 @@ module github.com/Jigsaw-Code/outline-sdk/x go 1.22 -toolchain go1.22.1 - require ( github.com/Jigsaw-Code/outline-sdk v0.0.17 // Use github.com/Psiphon-Labs/psiphon-tunnel-core@staging-client as per // https://github.com/Psiphon-Labs/psiphon-tunnel-core/?tab=readme-ov-file#using-psiphon-with-go-modules github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20240619172145-03cade11f647 github.com/lmittmann/tint v1.0.5 + github.com/quic-go/quic-go v0.48.1 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/stretchr/testify v1.9.0 github.com/vishvananda/netlink v1.1.0 @@ -19,8 +18,6 @@ require ( golang.org/x/term v0.23.0 ) -require github.com/quic-go/quic-go v0.48.1 - require ( filippo.io/bigmod v0.0.1 // indirect filippo.io/keygen v0.0.0-20230306160926-5201437acf8e // indirect From e245a9a17f102581a8332b0d42bd324ed31f6a47 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 1 Nov 2024 16:38:23 -0600 Subject: [PATCH 35/35] Update x/examples/fetch/main.go Co-authored-by: J. Yi <93548144+jyyi1@users.noreply.github.com> --- x/examples/fetch/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/examples/fetch/main.go b/x/examples/fetch/main.go index 932764bd..cc945779 100644 --- a/x/examples/fetch/main.go +++ b/x/examples/fetch/main.go @@ -71,7 +71,7 @@ func overrideAddress(original string, newHost string, newPort string) (string, e func main() { verboseFlag := flag.Bool("v", false, "Enable debug output") - tlsKeyLogFlag := flag.String("tls-key-log", "", "Filename to write the TLS key log to allow for decryption on Wireshark.") + tlsKeyLogFlag := flag.String("tls-key-log", "", "Filename to write the TLS key log to allow for decryption on Wireshark") protoFlag := flag.String("proto", "h1", "HTTP version to use (h1, h2, h3)") transportFlag := flag.String("transport", "", "Transport config") addressFlag := flag.String("address", "", "Address to connect to. If empty, use the URL authority")