From 173c3ce10cdc2e42c99df43cce556bdaabde9198 Mon Sep 17 00:00:00 2001 From: Daniel LaCosse <3759828+daniellacosse@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:55:21 -0400 Subject: [PATCH] do it --- outline/device/config.go | 98 ---------------------- outline/device/config_test.go | 133 ------------------------------ outline/device/device.go | 67 --------------- outline/device/device_test.go | 40 --------- outline/device/packet_listener.go | 25 ------ outline/device/packet_proxy.go | 67 --------------- outline/device/stream_dialer.go | 32 ------- 7 files changed, 462 deletions(-) delete mode 100644 outline/device/config.go delete mode 100644 outline/device/config_test.go delete mode 100644 outline/device/device.go delete mode 100644 outline/device/device_test.go delete mode 100644 outline/device/packet_listener.go delete mode 100644 outline/device/packet_proxy.go delete mode 100644 outline/device/stream_dialer.go diff --git a/outline/device/config.go b/outline/device/config.go deleted file mode 100644 index 356dd778bc..0000000000 --- a/outline/device/config.go +++ /dev/null @@ -1,98 +0,0 @@ -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package device - -import ( - "encoding/json" - "fmt" - "net" - "strconv" - - "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" -) - -// An internal configuration data structure to be used by Outline transports. -type transportConfig struct { - RemoteAddress string // the remote server address of "host:port" - CryptoKey *shadowsocks.EncryptionKey - Prefix []byte -} - -// The configuration interface between the Outline backend and Outline apps. -// Must match the ShadowsocksSessionConfig interface defined in Outline Client. -type configJSON struct { - Host string `json:"host"` - Port uint16 `json:"port"` - Password string `json:"password"` - Method string `json:"method"` - Prefix string `json:"prefix"` -} - -// parseConfigFromJSON parses a transport configuration string in JSON format, and returns a corresponding -// TransportConfig. The JSON string `in` must match the ShadowsocksSessionConfig interface defined in Outline Client. -func parseConfigFromJSON(in string) (config *transportConfig, err error) { - var confJson configJSON - if err = json.Unmarshal([]byte(in), &confJson); err != nil { - return nil, err - } - if err = validateConfig(&confJson); err != nil { - return nil, fmt.Errorf("invalid configuration: %w", err) - } - - config = &transportConfig{ - RemoteAddress: net.JoinHostPort(confJson.Host, strconv.Itoa(int(confJson.Port))), - } - if config.CryptoKey, err = shadowsocks.NewEncryptionKey(confJson.Method, confJson.Password); err != nil { - return nil, fmt.Errorf("invalid cipher: %w", err) - } - if len(confJson.Prefix) > 0 { - if config.Prefix, err = parseStringPrefix(confJson.Prefix); err != nil { - return nil, fmt.Errorf("invalid configuration prefix: %w", err) - } - } - - return config, nil -} - -// validateConfig validates whether an Outline transport configuration is valid (it won't do any connectivity tests). -// -// Returns nil if it is valid; or an error if not. -func validateConfig(config *configJSON) error { - if len(config.Host) == 0 { - return fmt.Errorf("must provide a hostname or IP address") - } - if config.Port <= 0 || config.Port > 65535 { - return fmt.Errorf("port must be within range [1..65535]") - } - if len(config.Method) == 0 { - return fmt.Errorf("must provide an encryption cipher method") - } - if len(config.Password) == 0 { - return fmt.Errorf("must provide an encryption cipher password") - } - return nil -} - -func parseStringPrefix(utf8Str string) ([]byte, error) { - runes := []rune(utf8Str) - rawBytes := make([]byte, len(runes)) - for i, r := range runes { - if (r & 0xFF) != r { - return nil, fmt.Errorf("character out of range: %d", r) - } - rawBytes[i] = byte(r) - } - return rawBytes, nil -} diff --git a/outline/device/config_test.go b/outline/device/config_test.go deleted file mode 100644 index 0ef0996d15..0000000000 --- a/outline/device/config_test.go +++ /dev/null @@ -1,133 +0,0 @@ -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package device - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func Test_ParseConfigFromJSON(t *testing.T) { - tests := []struct { - name string - input string - expectErr bool - expectAddress string - expectPrefix []byte - }{ - { - name: "normal config", - input: `{"host":"192.0.2.1","port":12345,"method":"chacha20-ietf-poly1305","password":"abcd1234"}`, - expectAddress: "192.0.2.1:12345", - }, - { - name: "normal config with prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"aes-128-gcm","password":"abcd1234","prefix":"abc 123"}`, - expectAddress: "192.0.2.1:12345", - expectPrefix: []byte("abc 123"), - }, - { - name: "normal config with extra fields", - input: `{"extra_field":"ignored","host":"192.0.2.1","port":12345,"method":"aes-192-gcm","password":"abcd1234"}`, - expectAddress: "192.0.2.1:12345", - }, - { - name: "unprintable prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"AES-256-gcm","password":"abcd1234","prefix":"abc 123","prefix":"\u0000\u0080\u00ff"}`, - expectAddress: "192.0.2.1:12345", - expectPrefix: []byte{0x00, 0x80, 0xff}, - }, - { - name: "multi-byte utf-8 prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"chacha20-ietf-poly1305","password":"abcd1234","prefix":"abc 123","prefix":"` + "\xc2\x80\xc2\x81\xc3\xbd\xc3\xbf" + `"}`, - expectAddress: "192.0.2.1:12345", - expectPrefix: []byte{0x80, 0x81, 0xfd, 0xff}, - }, - { - name: "missing host", - input: `{"port":12345,"method":"AES-128-GCM","password":"abcd1234"}`, - expectErr: true, - }, - { - name: "missing port", - input: `{"host":"192.0.2.1","method":"aes-192-gcm","password":"abcd1234"}`, - expectErr: true, - }, - { - name: "missing method", - input: `{"host":"192.0.2.1","port":12345,"password":"abcd1234"}`, - expectErr: true, - }, - { - name: "missing password", - input: `{"host":"192.0.2.1","port":12345,"method":"chacha20-ietf-poly1305"}`, - expectErr: true, - }, - { - name: "empty host", - input: `{"host":"","port":12345,"method":"chacha20-ietf-poly1305","password":"abcd1234"}`, - expectErr: true, - }, - { - name: "zero port", - input: `{"host":"192.0.2.1","port":0,"method":"chacha20-ietf-poly1305","password":"abcd1234"}`, - expectErr: true, - }, - { - name: "empty method", - input: `{"host":"192.0.2.1","port":12345,"method":"","password":"abcd1234"}`, - expectErr: true, - }, - { - name: "empty password", - input: `{"host":"192.0.2.1","port":12345,"method":"chacha20-ietf-poly1305","password":""}`, - expectErr: true, - }, - { - name: "empty prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":"abcd1234","prefix":""}`, - expectErr: true, - }, - { - name: "port -1", - input: `{"host":"192.0.2.1","port":-1,"method":"aes-128-gcm","password":"abcd1234"}`, - expectErr: true, - }, - { - name: "port 65536", - input: `{"host":"192.0.2.1","port":65536,"method":"aes-128-gcm","password":"abcd1234"}`, - expectErr: true, - }, - { - name: "prefix out-of-range", - input: `{"host":"192.0.2.1","port":8080,"method":"aes-128-gcm","password":"abcd1234","prefix":"\x1234"}`, - expectErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := parseConfigFromJSON(tt.input) - if tt.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.Equal(t, tt.expectAddress, got.RemoteAddress) - require.NotNil(t, got.CryptoKey) - require.Equal(t, tt.expectPrefix, got.Prefix) - } - }) - } -} diff --git a/outline/device/device.go b/outline/device/device.go deleted file mode 100644 index 1a9ce70456..0000000000 --- a/outline/device/device.go +++ /dev/null @@ -1,67 +0,0 @@ -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package device - -import ( - "fmt" - - "github.com/Jigsaw-Code/outline-sdk/network" - "github.com/Jigsaw-Code/outline-sdk/network/lwip2transport" - "github.com/Jigsaw-Code/outline-sdk/transport" -) - -const ( - connectivityTestDNSResolver = "1.1.1.1:53" - connectivityTestTargetDomain = "www.google.com" -) - -// OutlineDevice delegates the TCP and UDP traffic from local machine to the remote Outline server. -type OutlineDevice struct { - t2s network.IPDevice - pp *outlinePacketProxy - sd transport.StreamDialer -} - -// NewOutlineDevice creates a new [OutlineDevice] that can relay traffic to a remote Outline server. -func NewOutlineDevice(configJSON string) (d *OutlineDevice, err error) { - config, err := parseConfigFromJSON(configJSON) - if err != nil { - return nil, err - } - - d = &OutlineDevice{} - - if d.sd, err = newOutlineStreamDialer(config); err != nil { - return nil, fmt.Errorf("failed to create TCP dialer: %w", err) - } - - if d.pp, err = newOutlinePacketProxy(config); err != nil { - return nil, fmt.Errorf("failed to create UDP proxy: %w", err) - } - - if d.t2s, err = lwip2transport.ConfigureDevice(d.sd, d.pp); err != nil { - return nil, fmt.Errorf("failed to configure lwIP: %w", err) - } - - return -} - -func (d *OutlineDevice) Close() error { - return d.t2s.Close() -} - -func (d *OutlineDevice) Refresh() error { - return d.pp.testConnectivityAndRefresh(connectivityTestDNSResolver, connectivityTestTargetDomain) -} diff --git a/outline/device/device_test.go b/outline/device/device_test.go deleted file mode 100644 index 33c3fdec09..0000000000 --- a/outline/device/device_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package device - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func Test_NewOutlineDevice(t *testing.T) { - tests := []struct { - name string - input string - }{ - { - name: "normal configuration", - input: `{"host":"192.0.2.1","port":12345,"method":"chacha20-ietf-poly1305","password":"abcd1234"}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d, err := NewOutlineDevice(tt.input) - require.NoError(t, err) - require.NotNil(t, d) - }) - } -} diff --git a/outline/device/packet_listener.go b/outline/device/packet_listener.go deleted file mode 100644 index 277cc12b4b..0000000000 --- a/outline/device/packet_listener.go +++ /dev/null @@ -1,25 +0,0 @@ -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package device - -import ( - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" -) - -// newOutlinePacketListener creates a [transport.PacketListener] that connects to the remote proxy using `config`. -func newOutlinePacketListener(config *transportConfig) (transport.PacketListener, error) { - return shadowsocks.NewPacketListener(&transport.UDPEndpoint{Address: config.RemoteAddress}, config.CryptoKey) -} diff --git a/outline/device/packet_proxy.go b/outline/device/packet_proxy.go deleted file mode 100644 index 53a2f32a14..0000000000 --- a/outline/device/packet_proxy.go +++ /dev/null @@ -1,67 +0,0 @@ -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package device - -import ( - "context" - "fmt" - - "github.com/Jigsaw-Code/outline-sdk/network" - "github.com/Jigsaw-Code/outline-sdk/network/dnstruncate" - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/x/connectivity" -) - -type outlinePacketProxy struct { - network.DelegatePacketProxy - remote, fallback network.PacketProxy - remotePktListener transport.PacketListener // this will be used in connectivity test -} - -func newOutlinePacketProxy(config *transportConfig) (proxy *outlinePacketProxy, err error) { - proxy = &outlinePacketProxy{} - - if proxy.remotePktListener, err = newOutlinePacketListener(config); err != nil { - return nil, fmt.Errorf("failed to create packet listener: %w", err) - } - - if proxy.remote, err = network.NewPacketProxyFromPacketListener(proxy.remotePktListener); err != nil { - return nil, fmt.Errorf("failed to create packet proxy: %w", err) - } - - if proxy.fallback, err = dnstruncate.NewPacketProxy(); err != nil { - return nil, fmt.Errorf("failed to create DNS fallback packet proxy: %w", err) - } - - if proxy.DelegatePacketProxy, err = network.NewDelegatePacketProxy(proxy.fallback); err != nil { - return nil, fmt.Errorf("failed to create mutable packet proxy: %w", err) - } - - return -} - -// testConnectivityAndRefresh tests whether the remote server can handle packet traffic and sets the underlying proxy -// to be either remote or fallback according to the result. -func (proxy *outlinePacketProxy) testConnectivityAndRefresh(resolver, domain string) error { - dialer := transport.PacketListenerDialer{Listener: proxy.remotePktListener} - dnsResolver := &transport.PacketDialerEndpoint{Dialer: dialer, Address: resolver} - _, err := connectivity.TestResolverPacketConnectivity(context.Background(), dnsResolver, domain) - - if err != nil { - return proxy.SetProxy(proxy.fallback) - } else { - return proxy.SetProxy(proxy.remote) - } -} diff --git a/outline/device/stream_dialer.go b/outline/device/stream_dialer.go deleted file mode 100644 index b837bbc443..0000000000 --- a/outline/device/stream_dialer.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package device - -import ( - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" -) - -// newOutlineStreamDialer creates a [transport.StreamDialer] that connects to the remote proxy using `config`. -func newOutlineStreamDialer(config *transportConfig) (transport.StreamDialer, error) { - dialer, err := shadowsocks.NewStreamDialer(&transport.TCPEndpoint{Address: config.RemoteAddress}, config.CryptoKey) - if err != nil { - return nil, err - } - if len(config.Prefix) > 0 { - dialer.SaltGenerator = shadowsocks.NewPrefixSaltGenerator(config.Prefix) - } - return dialer, nil -}