Skip to content

Commit

Permalink
add OutlineDevice to handle proxy traffic
Browse files Browse the repository at this point in the history
  • Loading branch information
jyyi1 committed Aug 4, 2023
1 parent 2ff1650 commit 466cc9d
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 92 deletions.
4 changes: 2 additions & 2 deletions transport/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func (e *PacketDialerEndpoint) Connect(ctx context.Context) (net.Conn, error) {

// PacketDialer provides a way to dial a destination and establish datagram connections.
type PacketDialer interface {
// Dial connects to `raddr`.
// `raddr` has the form `host:port`, where `host` can be a domain name or IP address.
// Dial connects to `addr`.
// `addr` has the form `host:port`, where `host` can be a domain name or IP address.
Dial(ctx context.Context, addr string) (net.Conn, error)
}

Expand Down
116 changes: 26 additions & 90 deletions x/outline-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@ package main

import (
"fmt"
"io"
"net"
"os"
"os/signal"
"strconv"
"time"
"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/songgao/water"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
Expand All @@ -48,7 +42,7 @@ const OUTLINE_ROUTING_TABLE = 233
// <svt-port> : the outline server port (e.g. 21532)
// <svr-pass> : the outline server password
func main() {
fmt.Println("OutlineVPN CLI (experimental-07211507)")
fmt.Println("OutlineVPN CLI (experimental-08031526)")

svrIp := os.Args[1]
svrIpCidr := svrIp + "/32"
Expand All @@ -63,8 +57,8 @@ func main() {
return
}

// wait for go routine to terminate
defer time.Sleep(1 * time.Second)
bgWait := &sync.WaitGroup{}
defer bgWait.Wait()

tun, err := setupTunDevice()
if err != nil {
Expand All @@ -82,6 +76,28 @@ func main() {
return
}

ss, err := NewOutlineDevice(&OutlineConfig{
Hostname: svrIp,
Port: uint16(svrPort),
Password: svrPass,
Cipher: "chacha20-ietf-poly1305",
})
if err != nil {
fmt.Printf("fatal error: %v", err)
return
}
defer ss.Close()

ss.Refresh()

bgWait.Add(1)
go func() {
defer bgWait.Done()
if err := ss.RelayTraffic(tun); err != nil {
fmt.Printf("Traffic bridge destroyed: %v\n", err)
}
}()

err = setupRouting()
if err != nil {
return
Expand All @@ -102,30 +118,6 @@ func main() {
return
}

t2s, err := startTun2Socks(tun, svrIp, svrPass, svrPort)
if err != nil {
return
}
defer stopTun2Socks(t2s)

go func() {
fmt.Printf("debug: start receiving data from tun %v\n", tun.Name())
if _, err := io.Copy(t2s, tun); err != nil {
fmt.Printf("warning: failed to write data to network stack: %v\n", err)
} else {
fmt.Printf("debug: %v -> t2s eof\n", tun.Name())
}
}()

go func() {
fmt.Printf("debug: start forwarding t2s data to tun %v\n", tun.Name())
if _, err := io.Copy(tun, t2s); err != nil {
fmt.Printf("warning: failed to forward t2s data to tun: %v\n", err)
} else {
fmt.Printf("debug: t2s -> %v eof\n", tun.Name())
}
}()

sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, unix.SIGTERM, unix.SIGHUP)
s := <-sigc
Expand Down Expand Up @@ -332,59 +324,3 @@ func cleanUpRule(rule *netlink.Rule) error {
fmt.Println("ip rule of routing table deleted")
return nil
}

func startTun2Socks(tun *water.Interface, ip, pass string, port int) (network.IPDevice, error) {
fmt.Println("starting outline-go-tun2socks...")

cipher, err := shadowsocks.NewEncryptionKey("chacha20-ietf-poly1305", pass)
if err != nil {
fmt.Printf("fatal error: failed to create Shadowsocks cipher, %v\n", err)
return nil, err
}

proxyIP, err := net.ResolveIPAddr("ip", ip)
if err != nil {
fmt.Printf("fatal error: failed to resolve proxy address, %v\n", err)
return nil, err
}
proxyAddress := net.JoinHostPort(proxyIP.String(), fmt.Sprint(port))

sd, err := shadowsocks.NewStreamDialer(&transport.TCPEndpoint{Address: proxyAddress}, cipher)
if err != nil {
fmt.Printf("fatal error: failed to create StreamDialer, %v\n", err)
return nil, err
}

// pl, err := shadowsocks.NewPacketListener(&transport.UDPEndpoint{Address: proxyAddress}, cipher)
// pl, err := dnsovertcp.NewPacketListener(sd)
// pl, err := dnstruncate.NewPacketListener()
if err != nil {
fmt.Printf("fatal error: failed to create PacketListener, %v\n", err)
return nil, err
}
// ph, err := network.NewPacketProxyFromPacketListener(pl)
ph, err := dnstruncate.NewPacketProxy()
if err != nil {
fmt.Printf("fatal error: failed to create PacketProxy, %v\n", err)
return nil, err
}

t2s, err := lwip2transport.ConfigureDevice(sd, ph)
if err != nil {
fmt.Printf("fatal error: failed to create Tun2Socks device, %v\n", err)
return nil, err
}

fmt.Println("lwIP tun2socks created")
return t2s, nil
}

func stopTun2Socks(t2s network.IPDevice) error {
fmt.Println("stopping outline-go-tun2socks...")
err := t2s.Close()
if err != nil {
fmt.Printf("fatal error: %v\n", err)
}
fmt.Println("outline-go-tun2socks stopped")
return err
}
157 changes: 157 additions & 0 deletions x/outline-cli/outline_device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// 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"
"errors"
"fmt"
"io"
"net"
"strconv"
"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"
)

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(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)
if err != nil {
return nil, fmt.Errorf("failed to create TCP dialer: %w", 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)
}

// 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)
}

// Create DelegatePacketProxy
od.pktProxy, err = network.NewDelegatePacketProxy(od.fallbackPktProxy)
if 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 {
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 {
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)

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) RelayTraffic(netDev io.ReadWriter) error {
var err1, err2 error

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

go func() {
defer wg.Done()

fmt.Println("debug: OutlineDevice start receiving data from tun")
if _, err2 = io.Copy(d.t2s, netDev); err2 != nil {
fmt.Printf("warning: failed to write data to OutlineDevice: %v\n", err2)
} else {
fmt.Println("debug: tun -> OutlineDevice eof")
}
}()

fmt.Println("debug: start forwarding OutlineDevice data to tun")
if _, err1 = io.Copy(netDev, d.t2s); err1 != nil {
fmt.Printf("warning: failed to forward OutlineDevice data to tun: %v\n", err1)
} else {
fmt.Println("debug: OutlineDevice -> tun eof")
}

wg.Wait()

return errors.Join(err1, err2)
}

0 comments on commit 466cc9d

Please sign in to comment.