Skip to content

Commit

Permalink
update to use the latest outline-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
jyyi1 committed Oct 16, 2023
1 parent 01bef07 commit 3fe4380
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 136 deletions.
19 changes: 19 additions & 0 deletions x/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,22 @@ func newPacketDialerFromPart(innerDialer transport.PacketDialer, oneDialerConfig
return nil, fmt.Errorf("config scheme '%v' is not supported", url.Scheme)
}
}

// NewShadowsocksPacketListenerFromPart creates a new [transport.PacketListener] according to the given config,
// the config must contain only one "ss://" segment.
func NewShadowsocksPacketListenerFromPart(ssConfig string) (transport.PacketListener, error) {
ssConfig = strings.TrimSpace(ssConfig)
if ssConfig == "" {
return nil, errors.New("empty config part")
}

url, err := url.Parse(ssConfig)
if err != nil {
return nil, fmt.Errorf("failed to parse config part: %w", err)
}

if url.Scheme != "ss" {
return nil, errors.New("config scheme must be 'ss' for a PacketListener")
}
return newShadowsocksPacketListenerFromURL(url)
}
9 changes: 9 additions & 0 deletions x/config/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ func newShadowsocksPacketDialerFromURL(innerDialer transport.PacketDialer, confi
return dialer, nil
}

func newShadowsocksPacketListenerFromURL(configURL *url.URL) (transport.PacketListener, error) {
config, err := parseShadowsocksURL(configURL)
if err != nil {
return nil, err
}
ep := &transport.UDPEndpoint{Address: config.serverAddress}
return shadowsocks.NewPacketListener(ep, config.cryptoKey)
}

type shadowsocksConfig struct {
serverAddress string
cryptoKey *shadowsocks.EncryptionKey
Expand Down
67 changes: 7 additions & 60 deletions x/examples/outline-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
package main

import (
"flag"
"fmt"
"net"
"os"
"os/signal"
"strconv"
"sync"

"github.com/vishvananda/netlink"
Expand All @@ -31,32 +31,17 @@ import (
const OUTLINE_TUN_NAME = "outline233"
const OUTLINE_TUN_IP = "10.233.233.1"
const OUTLINE_TUN_MTU = 1500 // todo: we can read this from netlink
// const OUTLINE_TUN_SUBNET = "10.233.233.1/32"
const OUTLINE_GW_SUBNET = "10.233.233.2/32"
const OUTLINE_GW_IP = "10.233.233.2"
const OUTLINE_ROUTING_PRIORITY = 23333
const OUTLINE_ROUTING_TABLE = 233

// ./app
//
// <svr-ip> : the outline server IP (e.g. 111.111.111.111)
// <svt-port> : the outline server port (e.g. 21532)
// <svr-pass> : the outline server password
// ./app -transport "ss://..."
func main() {
fmt.Println("OutlineVPN CLI (experimental-08031526)")
fmt.Println("OutlineVPN CLI (experimental-10161603)")

svrIp := os.Args[1]
svrIpCidr := svrIp + "/32"
svrPass := os.Args[3]
svrPort, err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Printf("fatal error: %v\n", err)
return
}
if svrPort < 1000 || svrPort > 65535 {
fmt.Printf("fatal error: server port out of range\n")
return
}
transportFlag := flag.String("transport", "", "Transport config")
flag.Parse()

bgWait := &sync.WaitGroup{}
defer bgWait.Wait()
Expand All @@ -68,12 +53,7 @@ func main() {
}
defer tun.Close()

ss, err := NewOutlineDevice(&OutlineConfig{
Hostname: svrIp,
Port: uint16(svrPort),
Password: svrPass,
Cipher: "chacha20-ietf-poly1305",
})
ss, err := NewOutlineDevice(*transportFlag)
if err != nil {
fmt.Printf("fatal error: %v", err)
return
Expand All @@ -96,40 +76,19 @@ func main() {
}
defer cleanUpRouting()

if err := showRouting(); err != nil {
return
}

svrIpCidr := ss.GetServerIP().String() + "/32"
r, err := setupIpRule(svrIpCidr)
if err != nil {
return
}
defer cleanUpRule(r)

if err := showAllRules(); err != nil {
return
}

sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, unix.SIGTERM, unix.SIGHUP)
s := <-sigc
fmt.Printf("\nReceived %v, cleaning up resources...\n", s)
}

func showRouting() error {
filter := netlink.Route{Table: OUTLINE_ROUTING_TABLE}
routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, &filter, netlink.RT_FILTER_TABLE)
if err != nil {
fmt.Printf("fatal error: %v\n", err)
return err
}
fmt.Printf("\tRoutes (@%v): %v\n", OUTLINE_ROUTING_TABLE, len(routes))
for _, route := range routes {
fmt.Printf("\t\t%v\n", route)
}
return nil
}

func setupRouting() error {
fmt.Println("configuring outline routing table...")
tun, err := netlink.LinkByName(OUTLINE_TUN_NAME)
Expand Down Expand Up @@ -194,18 +153,6 @@ func cleanUpRouting() error {
return lastErr
}

func showAllRules() error {
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
if err != nil {
fmt.Printf("fatal error: %v\n", err)
return err
}
for _, r := range rules {
fmt.Printf("\t%v\n", r)
}
return nil
}

func setupIpRule(svrIp string) (*netlink.Rule, error) {
fmt.Println("adding ip rule for outline routing table...")
dst, err := netlink.ParseIPNet(svrIp)
Expand Down
126 changes: 51 additions & 75 deletions x/examples/outline-cli/outline_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,85 +15,48 @@
package main

import (
"context"
"errors"
"fmt"
"io"
"net"
"strconv"
"net/url"
"strings"
"sync"

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

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
t2s network.IPDevice
sd transport.StreamDialer
pp *outlinePacketProxy
svrIP net.IP
}

func NewOutlineDevice(config *OutlineConfig) (od *OutlineDevice, err error) {
od = &OutlineDevice{}

cipher, err := shadowsocks.NewEncryptionKey(config.Cipher, config.Password)
if err != nil {
return nil, fmt.Errorf("failed to create cipher `%v`: %w", config.Cipher, err)
}

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

// Create Shadowsocks TCP StreamDialer
od.ssStreamDialer, err = shadowsocks.NewStreamDialer(&transport.TCPEndpoint{Address: ssAddress}, cipher)
func NewOutlineDevice(transportConfig string) (od *OutlineDevice, err error) {
ip, err := resolveShadowsocksServerIPFromConfig(transportConfig)
if err != nil {
return nil, fmt.Errorf("failed to create TCP dialer: %w", err)
return nil, err
}

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

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

od.ssPktProxy, err = network.NewPacketProxyFromPacketListener(od.ssPktListener)
if err != nil {
return nil, fmt.Errorf("failed to create UDP proxy: %w", err)
if od.sd, err = config.NewStreamDialer(transportConfig); err != nil {
return nil, fmt.Errorf("failed to create TCP dialer: %w", err)
}

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

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

Expand All @@ -105,26 +68,11 @@ func (d *OutlineDevice) Close() error {
}

func (d *OutlineDevice) Refresh() error {
fmt.Println("debug: testing TCP connectivity...")
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)
}

fmt.Println("debug: testing UDP connectivity...")
dialer := transport.PacketListenerDialer{Listener: d.ssPktListener}
packetResolver := &transport.PacketDialerEndpoint{Dialer: dialer, Address: connectivityTestResolver}
_, err = connectivity.TestResolverPacketConnectivity(context.Background(), packetResolver, connectivityTestDomain)
fmt.Printf("debug: UDP connectivity test result: %v\n", err)
return d.pp.testConnectivityAndRefresh(connectivityTestResolver, connectivityTestDomain)
}

if err != nil {
fmt.Println("info: remote Shadowsocks server doesn't support UDP, switching to local DNS truncation handler")
return d.pktProxy.SetProxy(d.fallbackPktProxy)
} else {
fmt.Println("info: remote Shadowsocks server supports UDP traffic")
return d.pktProxy.SetProxy(d.ssPktProxy)
}
func (d *OutlineDevice) GetServerIP() net.IP {
return d.svrIP
}

func (d *OutlineDevice) RelayTraffic(netDev io.ReadWriter) error {
Expand Down Expand Up @@ -155,3 +103,31 @@ func (d *OutlineDevice) RelayTraffic(netDev io.ReadWriter) error {

return errors.Join(err1, err2)
}

func resolveShadowsocksServerIPFromConfig(transportConfig string) (net.IP, error) {
if strings.Contains(transportConfig, "|") {
return nil, errors.New("multi-part config is not supported")
}
if transportConfig = strings.TrimSpace(transportConfig); transportConfig == "" {
return nil, errors.New("config is required")
}
url, err := url.Parse(transportConfig)
if err != nil {
return nil, fmt.Errorf("failed to parse config: %w", err)
}
if url.Scheme != "ss" {
return nil, errors.New("config must start with 'ss://'")
}
ipList, err := net.LookupIP(url.Hostname())
if err != nil {
return nil, fmt.Errorf("invalid server hostname: %w", err)
}

// todo: we only tested IPv4 routing table, need to test IPv6 in the future
for _, ip := range ipList {
if ip = ip.To4(); ip != nil {
return ip, nil
}
}
return nil, errors.New("IPv6 only Shadowsocks server is not supported yet")
}
67 changes: 67 additions & 0 deletions x/examples/outline-cli/outline_packet_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 main

import (
"context"
"fmt"
"log"

"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/config"
"github.com/Jigsaw-Code/outline-sdk/x/connectivity"
)

type outlinePacketProxy struct {
network.DelegatePacketProxy

remote, fallback network.PacketProxy
remotePl transport.PacketListener
}

func newOutlinePacketProxy(transportConfig string) (opp *outlinePacketProxy, err error) {
opp = &outlinePacketProxy{}

if opp.remotePl, err = config.NewShadowsocksPacketListenerFromPart(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 {
return nil, fmt.Errorf("failed to create UDP packet proxy: %w", err)
}
if opp.fallback, err = dnstruncate.NewPacketProxy(); err != nil {
return nil, fmt.Errorf("failed to create DNS truncate packet proxy: %w", err)
}
if opp.DelegatePacketProxy, err = network.NewDelegatePacketProxy(opp.fallback); err != nil {
return nil, fmt.Errorf("failed to create delegate UDP proxy: %w", err)
}

return
}

func (proxy *outlinePacketProxy) testConnectivityAndRefresh(resolver, domain string) error {
dialer := transport.PacketListenerDialer{Listener: proxy.remotePl}
dnsResolver := &transport.PacketDialerEndpoint{Dialer: dialer, Address: resolver}
_, err := connectivity.TestResolverPacketConnectivity(context.Background(), dnsResolver, domain)

if err != nil {
log.Println("[info] remote server cannot handle UDP traffic, switch to DNS truncate mode")
return proxy.SetProxy(proxy.fallback)
} else {
log.Println("[info] remote server supports UDP, we will delegate all UDP packets to it")
return proxy.SetProxy(proxy.remote)
}
}
Loading

0 comments on commit 3fe4380

Please sign in to comment.