Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(x): add x/internal/outline package for some shared app logic #39

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/eycorsican/go-tun2socks v1.16.11 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
Expand Down
5 changes: 5 additions & 0 deletions x/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eycorsican/go-tun2socks v1.16.11 h1:+hJDNgisrYaGEqoSxhdikMgMJ4Ilfwm/IZDrWRrbaH8=
github.com/eycorsican/go-tun2socks v1.16.11/go.mod h1:wgB2BFT8ZaPKyKOQ/5dljMG/YIow+AIXyq4KBwJ5sGQ=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
Expand All @@ -15,6 +18,7 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand All @@ -29,6 +33,7 @@ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
Expand Down
3 changes: 2 additions & 1 deletion x/outline-connectivity/main.go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's revert outline-connectivity.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks"
"github.com/Jigsaw-Code/outline-sdk/x/connectivity"
"github.com/Jigsaw-Code/outline-sdk/x/outline"
)

var debugLog log.Logger = *log.New(io.Discard, "", 0)
Expand Down Expand Up @@ -121,7 +122,7 @@ func main() {
// - Server IPv4 dial support
// - Server IPv6 dial support

config, err := parseAccessKey(*accessKeyFlag)
config, err := outline.ParseAccessKey(*accessKeyFlag)
if err != nil {
log.Fatal(err.Error())
}
Expand Down
19 changes: 11 additions & 8 deletions x/outline-connectivity/access_key.go → x/outline/access_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package main
package outline

import (
"encoding/base64"
Expand All @@ -25,15 +25,15 @@ import (
"github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks"
)

type sessionConfig struct {
type Prefix []byte

type SessionConfig struct {
jyyi1 marked this conversation as resolved.
Show resolved Hide resolved
Hostname string
Port int
CryptoKey *shadowsocks.EncryptionKey
Prefix Prefix
}

type Prefix []byte

func (p Prefix) String() string {
runes := make([]rune, len(p))
for i, b := range p {
Expand All @@ -42,13 +42,14 @@ func (p Prefix) String() string {
return string(runes)
}

// TODO(fortuna): provide this as a reusable library. Perhaps as x/shadowsocks or x/outline.
func parseAccessKey(accessKey string) (*sessionConfig, error) {
var config sessionConfig
func ParseAccessKey(accessKey string) (*SessionConfig, error) {
var config SessionConfig

accessKeyURL, err := url.Parse(accessKey)
if err != nil {
return nil, fmt.Errorf("failed to parse access key: %w", err)
}

var portString string
// Host is a <host>:<port> string
config.Hostname, portString, err = net.SplitHostPort(accessKeyURL.Host)
Expand All @@ -59,9 +60,10 @@ func parseAccessKey(accessKey string) (*sessionConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse port number: %w", err)
}

cipherInfoBytes, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(accessKeyURL.User.String())
if err != nil {
return nil, fmt.Errorf("failed to decode cipher info [%v]: %v", accessKeyURL.User.String(), err)
return nil, fmt.Errorf("failed to decode cipher info [%v]: %w", accessKeyURL.User.String(), err)
}
cipherName, secret, found := strings.Cut(string(cipherInfoBytes), ":")
if !found {
Expand All @@ -71,6 +73,7 @@ func parseAccessKey(accessKey string) (*sessionConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to create cipher: %w", err)
}

prefixStr := accessKeyURL.Query().Get("prefix")
if len(prefixStr) > 0 {
config.Prefix, err = ParseStringPrefix(prefixStr)
Expand Down
140 changes: 140 additions & 0 deletions x/outline/outline_device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2023 Jigsaw Operations LLC
//
// 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 outline

import (
"context"
"errors"
"fmt"
"io"
"net"
"strconv"
"sync"

"github.com/Jigsaw-Code/outline-sdk/network"
"github.com/Jigsaw-Code/outline-sdk/network/dnstruncate"
"github.com/Jigsaw-Code/outline-sdk/network/lwip2transport"
"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks"
"github.com/Jigsaw-Code/outline-sdk/x/connectivity"
)

const (
connectivityTestDomain = "www.google.com"
connectivityTestResolver = "1.1.1.1:53"
)

type OutlineConfig struct {
Hostname string
Port uint16
Password string
Cipher string
}

type OutlineDevice struct {
t2s network.IPDevice
pktProxy network.DelegatePacketProxy
fallbackPktProxy network.PacketProxy
ssStreamDialer transport.StreamDialer
ssPktListener transport.PacketListener
ssPktProxy network.PacketProxy
}

func NewOutlineDevice(accessKey string) (*OutlineDevice, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflates too many things to be in the SDK. It's code that belongs in outline-client instead.

For the SDK, I'd like to see a device others can reuse that is detached from our application logic. And users should be able to provide their own relay logic. We can provide the Outline and relay functionality via composition/layering.

One functionality that any VPN provider will need is the UDP refresh.
It would be helpful to see an IPdevice that provides the UDP update, taking the transport objects.

The relay should be a separate function. So is the Outline/Shadowsocks logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed this and replaced with three new functions: NewOutlineStreamDialer, NewOutlinePacketProxy and NewOutlinePacketListener.

var d OutlineDevice

config, err := ParseAccessKey(accessKey)
if err != nil {
return nil, fmt.Errorf("invalid outline access key: %w", err)
}

ssAddress := net.JoinHostPort(config.Hostname, strconv.Itoa(config.Port))

// Create Shadowsocks TCP StreamDialer
d.ssStreamDialer, err = shadowsocks.NewStreamDialer(&transport.TCPEndpoint{Address: ssAddress}, config.CryptoKey)
if err != nil {
return nil, fmt.Errorf("failed to create TCP dialer: %w", err)
}

// Create DNS Truncated PacketProxy
d.fallbackPktProxy, err = dnstruncate.NewPacketProxy()
if err != nil {
return nil, fmt.Errorf("failed to create DNS truncate proxy: %w", err)
}

// Create Shadowsocks UDP PacketProxy
d.ssPktListener, err = shadowsocks.NewPacketListener(&transport.UDPEndpoint{Address: ssAddress}, config.CryptoKey)
if err != nil {
return nil, fmt.Errorf("failed to create UDP listener: %w", err)
}

d.ssPktProxy, err = network.NewPacketProxyFromPacketListener(d.ssPktListener)
if err != nil {
return nil, fmt.Errorf("failed to create UDP proxy: %w", err)
}

// Create DelegatePacketProxy
d.pktProxy, err = network.NewDelegatePacketProxy(d.fallbackPktProxy)
if err != nil {
return nil, fmt.Errorf("failed to create delegate UDP proxy: %w", err)
}

// Configure lwIP Device
d.t2s, err = lwip2transport.ConfigureDevice(d.ssStreamDialer, d.pktProxy)
if err != nil {
return nil, fmt.Errorf("failed to configure lwIP: %w", err)
}

return &d, nil
}

func (d *OutlineDevice) Close() error {
return d.t2s.Close()
}

func (d *OutlineDevice) Refresh() error {
streamResolver := &transport.StreamDialerEndpoint{Dialer: d.ssStreamDialer, Address: connectivityTestResolver}
_, err := connectivity.TestResolverStreamConnectivity(context.Background(), streamResolver, connectivityTestDomain)
if err != nil {
return fmt.Errorf("failed to connect to the remote Shadowsocks server: %w", err)
}

dialer := transport.PacketListenerDialer{Listener: d.ssPktListener}
packetResolver := &transport.PacketDialerEndpoint{Dialer: dialer, Address: connectivityTestResolver}
_, err = connectivity.TestResolverPacketConnectivity(context.Background(), packetResolver, connectivityTestDomain)

if err != nil {
return d.pktProxy.SetProxy(d.fallbackPktProxy)
} else {
return d.pktProxy.SetProxy(d.ssPktProxy)
}
}

func (d *OutlineDevice) RelayTraffic(netDev io.ReadWriter) error {
var err1, err2 error

wg := &sync.WaitGroup{}
wg.Add(1)

go func() {
defer wg.Done()
_, err2 = io.Copy(d.t2s, netDev)
}()

_, err1 = io.Copy(netDev, d.t2s)

wg.Wait()
jyyi1 marked this conversation as resolved.
Show resolved Hide resolved
return errors.Join(err1, err2)
}