From c38861eea0f39c34bededf901249462a30267684 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Mon, 15 May 2023 14:44:51 -0400 Subject: [PATCH 01/21] feat: add the PoC outline CLI app for Linux --- x/outline-cli/.gitignore | 15 ++ x/outline-cli/LICENSE | 21 +++ x/outline-cli/README.md | 2 + x/outline-cli/go.mod | 18 ++ x/outline-cli/go.sum | 35 ++++ x/outline-cli/main.go | 371 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 462 insertions(+) create mode 100644 x/outline-cli/.gitignore create mode 100644 x/outline-cli/LICENSE create mode 100644 x/outline-cli/README.md create mode 100644 x/outline-cli/go.mod create mode 100644 x/outline-cli/go.sum create mode 100644 x/outline-cli/main.go diff --git a/x/outline-cli/.gitignore b/x/outline-cli/.gitignore new file mode 100644 index 00000000..66fd13c9 --- /dev/null +++ b/x/outline-cli/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/x/outline-cli/LICENSE b/x/outline-cli/LICENSE new file mode 100644 index 00000000..872bfc75 --- /dev/null +++ b/x/outline-cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 J. Yi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/x/outline-cli/README.md b/x/outline-cli/README.md new file mode 100644 index 00000000..f1de8938 --- /dev/null +++ b/x/outline-cli/README.md @@ -0,0 +1,2 @@ +# outline-vpn-cli-poc +The CLI interface of Outline VPN client for Linux (PoC). diff --git a/x/outline-cli/go.mod b/x/outline-cli/go.mod new file mode 100644 index 00000000..4b700e91 --- /dev/null +++ b/x/outline-cli/go.mod @@ -0,0 +1,18 @@ +module github.com/jyyi1/outline-vpn-cli-poc + +go 1.20 + +require ( + github.com/Jigsaw-Code/outline-go-tun2socks v0.0.0-20230119221141-ab0408a6d301 + github.com/eycorsican/go-tun2socks v1.16.11 + github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 + github.com/vishvananda/netlink v1.1.0 + golang.org/x/sys v0.4.0 +) + +require ( + github.com/Jigsaw-Code/outline-ss-server v1.4.0 // indirect + github.com/shadowsocks/go-shadowsocks2 v0.1.4-0.20201002022019-75d43273f5a5 // indirect + github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect + golang.org/x/crypto v0.1.0 // indirect +) diff --git a/x/outline-cli/go.sum b/x/outline-cli/go.sum new file mode 100644 index 00000000..1e623f08 --- /dev/null +++ b/x/outline-cli/go.sum @@ -0,0 +1,35 @@ +github.com/Jigsaw-Code/outline-go-tun2socks v0.0.0-20230119221141-ab0408a6d301 h1:1og4I2PdmtvLm22VupA8aXeA21u+O3v6cAykjAboeis= +github.com/Jigsaw-Code/outline-go-tun2socks v0.0.0-20230119221141-ab0408a6d301/go.mod h1:9eVKhiW3maxpTZtb2fF1nU1kPgA/FdjroWpegnj8ejM= +github.com/Jigsaw-Code/outline-ss-server v1.4.0 h1:TBmbPknt+6IIA1GfOky0i+JkZOsW4nuljOTff3INTxo= +github.com/Jigsaw-Code/outline-ss-server v1.4.0/go.mod h1:ZpUdvhJPncn4RILa3YVM6hwSr1/5E52Vo0NRJ3KsfT4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +github.com/shadowsocks/go-shadowsocks2 v0.1.4-0.20201002022019-75d43273f5a5 h1:PH+QJxWqlFTux7T1inBImjualkfKum8UKgAsRqDMmbM= +github.com/shadowsocks/go-shadowsocks2 v0.1.4-0.20201002022019-75d43273f5a5/go.mod h1:/jk7XQoEyq98sd0ckJtBhaaFqfnzWm7CX/OzUAIy/Kk= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/x/outline-cli/main.go b/x/outline-cli/main.go new file mode 100644 index 00000000..477007df --- /dev/null +++ b/x/outline-cli/main.go @@ -0,0 +1,371 @@ +package main + +import ( + "fmt" + "io" + "net" + "os" + "os/signal" + "strconv" + "time" + + "github.com/Jigsaw-Code/outline-internal-sdk/network" + "github.com/Jigsaw-Code/outline-internal-sdk/transport" + "github.com/Jigsaw-Code/outline-internal-sdk/transport/shadowsocks" + "github.com/Jigsaw-Code/outline-internal-sdk/tun2socks/lwip" + "github.com/songgao/water" + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" +) + +// Compile with `go build -ldflags="-extldflags=-static"` on Linux +// We only support Linux for now + +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 +// +// : the outline server IP (e.g. 111.111.111.111) +// : the outline server port (e.g. 21532) +// : the outline server password +func main() { + fmt.Println("OutlineVPN CLI (experimental-05092000)") + + 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 + } + + // wait for go routine to terminate + defer time.Sleep(1 * time.Second) + + tun, err := setupTunDevice() + if err != nil { + return + } + defer cleanUpTunDevice(tun) + + if err := showTunDevice(); err != nil { + return + } + if err := configureTunDevice(); err != nil { + return + } + if err := showTunDevice(); err != nil { + return + } + + err = setupRouting() + if err != nil { + return + } + defer cleanUpRouting() + + if err := showRouting(); err != nil { + return + } + + r, err := setupIpRule(svrIpCidr) + if err != nil { + return + } + defer cleanUpRule(r) + + if err := showAllRules(); err != nil { + 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, os.Kill, unix.SIGTERM, unix.SIGHUP) + s := <-sigc + fmt.Printf("\nReceived %v, cleaning up resources...\n", s) +} + +func showTunDevice() error { + l, err := netlink.LinkByName(OUTLINE_TUN_NAME) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + if tun, ok := l.(*netlink.Tuntap); ok { + mode := "unknown" + if tun.Mode == netlink.TUNTAP_MODE_TUN { + mode = "tun" + } else if tun.Mode == netlink.TUNTAP_MODE_TAP { + mode = "tap" + } + persist := "persist" + if tun.NonPersist { + persist = "non-persist" + } + fmt.Printf("\t%v %v %v mtu=%v attr=%v stat=%v\n", tun.Name, mode, persist, tun.MTU, tun.Attrs(), tun.Statistics) + return nil + } else { + fmt.Printf("fatal error: %v is not a tun device\n", OUTLINE_TUN_NAME) + return fmt.Errorf("tun device not found") + } +} + +func setupTunDevice() (*water.Interface, error) { + fmt.Println("setting up tun device...") + conf := water.Config{ + DeviceType: water.TUN, + PlatformSpecificParams: water.PlatformSpecificParams{ + Name: OUTLINE_TUN_NAME, + Persist: false, + }, + } + r, err := water.New(conf) + if err == nil { + fmt.Println("tun device created") + } else { + fmt.Printf("fatal error: %v\n", err) + } + return r, err +} + +func configureTunDevice() error { + fmt.Println("configuring tun device ip...") + tun, err := netlink.LinkByName(OUTLINE_TUN_NAME) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + addr, err := netlink.ParseAddr(OUTLINE_TUN_SUBNET) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + if err := netlink.AddrAdd(tun, addr); err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + if err := netlink.LinkSetUp(tun); err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + return nil +} + +func cleanUpTunDevice(tun *water.Interface) error { + fmt.Println("cleaning up tun device...") + err := tun.Close() + if err == nil { + fmt.Println("tun device deleted") + } else { + fmt.Printf("clean up error: %v\n", err) + } + return err +} + +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) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + + dst, err := netlink.ParseIPNet(OUTLINE_GW_SUBNET) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + r := netlink.Route{ + LinkIndex: tun.Attrs().Index, + Table: OUTLINE_ROUTING_TABLE, + Dst: dst, + Src: net.ParseIP(OUTLINE_TUN_IP), + Scope: netlink.SCOPE_LINK, + } + fmt.Printf("\trouting only from %v to %v through nic %v...\n", r.Src, r.Dst, r.LinkIndex) + err = netlink.RouteAdd(&r) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + + r = netlink.Route{ + LinkIndex: tun.Attrs().Index, + Table: OUTLINE_ROUTING_TABLE, + Gw: net.ParseIP(OUTLINE_GW_IP), + } + fmt.Printf("\tdefault routing entry via gw %v through nic %v...\n", r.Gw, r.LinkIndex) + err = netlink.RouteAdd(&r) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + + fmt.Println("routing table has been successfully configured") + return nil +} + +func cleanUpRouting() error { + fmt.Println("cleaning up outline routing table...") + 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 + } + var lastErr error = nil + for _, route := range routes { + if err := netlink.RouteDel(&route); err != nil { + fmt.Printf("fatal error: %v\n", err) + lastErr = err + } + } + if lastErr == nil { + fmt.Println("routing table has been reset") + } + 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) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return nil, err + } + rule := netlink.NewRule() + rule.Priority = OUTLINE_ROUTING_PRIORITY + rule.Family = netlink.FAMILY_V4 + rule.Table = OUTLINE_ROUTING_TABLE + rule.Dst = dst + rule.Invert = true + fmt.Printf("+from all not to %v via table %v...\n", rule.Dst, rule.Table) + err = netlink.RuleAdd(rule) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return nil, err + } + fmt.Println("ip rule for outline routing table created") + return rule, nil +} + +func cleanUpRule(rule *netlink.Rule) error { + fmt.Println("cleaning up ip rule of routing table...") + err := netlink.RuleDel(rule) + if err != nil { + fmt.Printf("fatal error: %v\n", err) + return err + } + 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.NewCipher("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 + } + proxyTCPEndpoint := transport.TCPEndpoint{RemoteAddr: net.TCPAddr{IP: proxyIP.IP, Port: port}} + proxyUDPEndpoint := transport.UDPEndpoint{RemoteAddr: net.UDPAddr{IP: proxyIP.IP, Port: port}} + + sd, err := shadowsocks.NewStreamDialer(proxyTCPEndpoint, cipher) + if err != nil { + fmt.Printf("fatal error: failed to create StreamDialer, %v\n", err) + return nil, err + } + + pl, err := shadowsocks.NewPacketListener(proxyUDPEndpoint, cipher) + if err != nil { + fmt.Printf("fatal error: failed to create PacketListener, %v\n", err) + return nil, err + } + + t2s, err := lwip.NewTun2SocksDevice(sd, pl) + 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 +} From 2ca21fef1f8c502dfc4ae0ad62bf888257fdf0e8 Mon Sep 17 00:00:00 2001 From: Junyi Yi Date: Tue, 25 Jul 2023 13:30:33 -0400 Subject: [PATCH 02/21] upgrade to use the latest sdk --- transport/dnsovertcp/doc.go | 19 +++++++++++++++++++ x/outline-cli/LICENSE | 21 --------------------- x/outline-cli/main.go | 26 +++++++++++++++++--------- 3 files changed, 36 insertions(+), 30 deletions(-) create mode 100644 transport/dnsovertcp/doc.go delete mode 100644 x/outline-cli/LICENSE diff --git a/transport/dnsovertcp/doc.go b/transport/dnsovertcp/doc.go new file mode 100644 index 00000000..fbe78caf --- /dev/null +++ b/transport/dnsovertcp/doc.go @@ -0,0 +1,19 @@ +// 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. + +/* + +[RFC 1035 TCP usage]: https://datatracker.ietf.org/doc/html/rfc1035#section-4.2.2 +*/ +package dnsovertcp diff --git a/x/outline-cli/LICENSE b/x/outline-cli/LICENSE deleted file mode 100644 index 872bfc75..00000000 --- a/x/outline-cli/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 J. Yi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/x/outline-cli/main.go b/x/outline-cli/main.go index 477007df..e141ec61 100644 --- a/x/outline-cli/main.go +++ b/x/outline-cli/main.go @@ -10,9 +10,10 @@ import ( "time" "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/tun2socks/lwip" "github.com/songgao/water" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" @@ -36,7 +37,7 @@ const OUTLINE_ROUTING_TABLE = 233 // : the outline server port (e.g. 21532) // : the outline server password func main() { - fmt.Println("OutlineVPN CLI (experimental-05092000)") + fmt.Println("OutlineVPN CLI (experimental-07211507)") svrIp := os.Args[1] svrIpCidr := svrIp + "/32" @@ -107,7 +108,7 @@ func main() { go func() { fmt.Printf("debug: start forwarding t2s data to tun %v\n", tun.Name()) - if _, err = io.Copy(tun, t2s); err != nil { + 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()) @@ -324,7 +325,7 @@ func cleanUpRule(rule *netlink.Rule) error { func startTun2Socks(tun *water.Interface, ip, pass string, port int) (network.IPDevice, error) { fmt.Println("starting outline-go-tun2socks...") - cipher, err := shadowsocks.NewCipher("chacha20-ietf-poly1305", pass) + 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 @@ -335,22 +336,29 @@ func startTun2Socks(tun *water.Interface, ip, pass string, port int) (network.IP fmt.Printf("fatal error: failed to resolve proxy address, %v\n", err) return nil, err } - proxyTCPEndpoint := transport.TCPEndpoint{RemoteAddr: net.TCPAddr{IP: proxyIP.IP, Port: port}} - proxyUDPEndpoint := transport.UDPEndpoint{RemoteAddr: net.UDPAddr{IP: proxyIP.IP, Port: port}} + proxyAddress := net.JoinHostPort(proxyIP.String(), fmt.Sprint(port)) - sd, err := shadowsocks.NewStreamDialer(proxyTCPEndpoint, cipher) + 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(proxyUDPEndpoint, cipher) + // 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 := lwip.NewTun2SocksDevice(sd, pl) + t2s, err := lwip2transport.ConfigureDevice(sd, ph) if err != nil { fmt.Printf("fatal error: failed to create Tun2Socks device, %v\n", err) return nil, err From b5579fedcf9cd96f1dc16dda06b58a8ff5f97d4b Mon Sep 17 00:00:00 2001 From: Junyi Yi Date: Tue, 25 Jul 2023 13:31:24 -0400 Subject: [PATCH 03/21] remove wrong doc file --- transport/dnsovertcp/doc.go | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 transport/dnsovertcp/doc.go diff --git a/transport/dnsovertcp/doc.go b/transport/dnsovertcp/doc.go deleted file mode 100644 index fbe78caf..00000000 --- a/transport/dnsovertcp/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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. - -/* - -[RFC 1035 TCP usage]: https://datatracker.ietf.org/doc/html/rfc1035#section-4.2.2 -*/ -package dnsovertcp From 2ff16506f432728a19cc0989272deab9c89665be Mon Sep 17 00:00:00 2001 From: Junyi Yi Date: Wed, 26 Jul 2023 14:48:07 -0400 Subject: [PATCH 04/21] update folder structure and README --- x/go.mod | 6 +++++- x/go.sum | 17 +++++++++++++---- x/outline-cli/.gitignore | 15 --------------- x/outline-cli/README.md | 34 ++++++++++++++++++++++++++++++++-- x/outline-cli/go.mod | 18 ------------------ x/outline-cli/go.sum | 35 ----------------------------------- x/outline-cli/main.go | 19 +++++++++++++++---- 7 files changed, 65 insertions(+), 79 deletions(-) delete mode 100644 x/outline-cli/.gitignore delete mode 100644 x/outline-cli/go.mod delete mode 100644 x/outline-cli/go.sum diff --git a/x/go.mod b/x/go.mod index c87bd29e..b2d65e70 100644 --- a/x/go.mod +++ b/x/go.mod @@ -3,17 +3,21 @@ module github.com/Jigsaw-Code/outline-internal-sdk/x go 1.20 require ( - github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230522161920-4652a0839de2 + github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230724153316-35b8cde31f67 github.com/miekg/dns v1.1.54 + github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b github.com/stretchr/testify v1.8.2 + github.com/vishvananda/netlink v1.1.0 golang.org/x/sys v0.8.0 ) 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 + github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect diff --git a/x/go.sum b/x/go.sum index 01939776..e21b6c42 100644 --- a/x/go.sum +++ b/x/go.sum @@ -1,11 +1,12 @@ -github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230516135045-d8f077429783 h1:eQLfyDy1HS4RnoSGfYWzwIyjdbo0kEPicl13G43KDro= -github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230516135045-d8f077429783/go.mod h1:vxtE3esaFy5UG6TnipLyWx0esUQBy9LBXHLQx+SoER8= -github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230522161920-4652a0839de2 h1:Yo0b9lNvwQqr17RRB4YmeasNt9N4O+qJwZsDIx7pN0M= -github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230522161920-4652a0839de2/go.mod h1:vxtE3esaFy5UG6TnipLyWx0esUQBy9LBXHLQx+SoER8= +github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230724153316-35b8cde31f67 h1:kAMiobWgwFMfCJMfziN0+94W5Haoya4f0Uxk1hGAWDI= +github.com/Jigsaw-Code/outline-internal-sdk v0.0.0-20230724153316-35b8cde31f67/go.mod h1:zErD49ISn85l4AMp/zzBj5rHz/C35tn/ssp1wuE1Tz8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -17,6 +18,8 @@ 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 h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= +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= @@ -24,6 +27,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= @@ -31,10 +38,12 @@ 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= 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= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/x/outline-cli/.gitignore b/x/outline-cli/.gitignore deleted file mode 100644 index 66fd13c9..00000000 --- a/x/outline-cli/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ diff --git a/x/outline-cli/README.md b/x/outline-cli/README.md index f1de8938..dbd91433 100644 --- a/x/outline-cli/README.md +++ b/x/outline-cli/README.md @@ -1,2 +1,32 @@ -# outline-vpn-cli-poc -The CLI interface of Outline VPN client for Linux (PoC). +# Outline VPN CLI (PoC) + +The CLI interface of Outline VPN client for Linux. + +## Usage + +#### Standard + +``` +./OutlineCLI "" "" "" +``` + +#### Advanced (with Golang) + +``` +go run github.com/Jigsaw-Code/outline-internal-sdk/x/outline-cli@latest "" "" "" +``` + +### CLI Arguments + +- `svr-ip` : the outline server IP (for example, `111.111.111.111`) +- `svr-port` : the outline server port (for example, `21532`) +- `svr-pass` : the outline server password + +## Build (for Developers) + +We recommend to setup a [go workspace](https://go.dev/blog/get-familiar-with-workspaces) to build the code. Then use the following command to build the CLI (only support Linux): + +``` +cd outline-internal-sdk/x +go build -o OutlineCLI -ldflags="-extldflags=-static" ./outline-cli +``` diff --git a/x/outline-cli/go.mod b/x/outline-cli/go.mod deleted file mode 100644 index 4b700e91..00000000 --- a/x/outline-cli/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/jyyi1/outline-vpn-cli-poc - -go 1.20 - -require ( - github.com/Jigsaw-Code/outline-go-tun2socks v0.0.0-20230119221141-ab0408a6d301 - github.com/eycorsican/go-tun2socks v1.16.11 - github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 - github.com/vishvananda/netlink v1.1.0 - golang.org/x/sys v0.4.0 -) - -require ( - github.com/Jigsaw-Code/outline-ss-server v1.4.0 // indirect - github.com/shadowsocks/go-shadowsocks2 v0.1.4-0.20201002022019-75d43273f5a5 // indirect - github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect - golang.org/x/crypto v0.1.0 // indirect -) diff --git a/x/outline-cli/go.sum b/x/outline-cli/go.sum deleted file mode 100644 index 1e623f08..00000000 --- a/x/outline-cli/go.sum +++ /dev/null @@ -1,35 +0,0 @@ -github.com/Jigsaw-Code/outline-go-tun2socks v0.0.0-20230119221141-ab0408a6d301 h1:1og4I2PdmtvLm22VupA8aXeA21u+O3v6cAykjAboeis= -github.com/Jigsaw-Code/outline-go-tun2socks v0.0.0-20230119221141-ab0408a6d301/go.mod h1:9eVKhiW3maxpTZtb2fF1nU1kPgA/FdjroWpegnj8ejM= -github.com/Jigsaw-Code/outline-ss-server v1.4.0 h1:TBmbPknt+6IIA1GfOky0i+JkZOsW4nuljOTff3INTxo= -github.com/Jigsaw-Code/outline-ss-server v1.4.0/go.mod h1:ZpUdvhJPncn4RILa3YVM6hwSr1/5E52Vo0NRJ3KsfT4= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= -github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= -github.com/shadowsocks/go-shadowsocks2 v0.1.4-0.20201002022019-75d43273f5a5 h1:PH+QJxWqlFTux7T1inBImjualkfKum8UKgAsRqDMmbM= -github.com/shadowsocks/go-shadowsocks2 v0.1.4-0.20201002022019-75d43273f5a5/go.mod h1:/jk7XQoEyq98sd0ckJtBhaaFqfnzWm7CX/OzUAIy/Kk= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= -github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= -github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/x/outline-cli/main.go b/x/outline-cli/main.go index e141ec61..72f1feee 100644 --- a/x/outline-cli/main.go +++ b/x/outline-cli/main.go @@ -1,3 +1,17 @@ +// 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 ( @@ -19,9 +33,6 @@ import ( "golang.org/x/sys/unix" ) -// Compile with `go build -ldflags="-extldflags=-static"` on Linux -// We only support Linux for now - 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 @@ -116,7 +127,7 @@ func main() { }() sigc := make(chan os.Signal, 1) - signal.Notify(sigc, os.Interrupt, os.Kill, unix.SIGTERM, unix.SIGHUP) + signal.Notify(sigc, os.Interrupt, unix.SIGTERM, unix.SIGHUP) s := <-sigc fmt.Printf("\nReceived %v, cleaning up resources...\n", s) } From 466cc9dd7e6d0698ac63b4d70abc41e8494ad33c Mon Sep 17 00:00:00 2001 From: Junyi Yi Date: Fri, 4 Aug 2023 17:12:25 -0400 Subject: [PATCH 05/21] add OutlineDevice to handle proxy traffic --- transport/packet.go | 4 +- x/outline-cli/main.go | 116 ++++++----------------- x/outline-cli/outline_device.go | 157 ++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 92 deletions(-) create mode 100644 x/outline-cli/outline_device.go diff --git a/transport/packet.go b/transport/packet.go index d612f970..bddb609d 100644 --- a/transport/packet.go +++ b/transport/packet.go @@ -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) } diff --git a/x/outline-cli/main.go b/x/outline-cli/main.go index 72f1feee..27efd91b 100644 --- a/x/outline-cli/main.go +++ b/x/outline-cli/main.go @@ -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" @@ -48,7 +42,7 @@ const OUTLINE_ROUTING_TABLE = 233 // : the outline server port (e.g. 21532) // : 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" @@ -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 { @@ -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 @@ -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 @@ -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 -} diff --git a/x/outline-cli/outline_device.go b/x/outline-cli/outline_device.go new file mode 100644 index 00000000..454b0282 --- /dev/null +++ b/x/outline-cli/outline_device.go @@ -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) +} From 4e70bb799de393c4e70f9bc0f75479ab53af9ee0 Mon Sep 17 00:00:00 2001 From: Junyi Yi Date: Wed, 9 Aug 2023 18:33:16 -0400 Subject: [PATCH 06/21] add posix tun device --- x/outline-cli/main.go | 97 ++----------------------------- x/outline-cli/tun_device.go | 23 ++++++++ x/outline-cli/tun_device_posix.go | 89 ++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 91 deletions(-) create mode 100644 x/outline-cli/tun_device.go create mode 100644 x/outline-cli/tun_device_posix.go diff --git a/x/outline-cli/main.go b/x/outline-cli/main.go index 27efd91b..0d83b546 100644 --- a/x/outline-cli/main.go +++ b/x/outline-cli/main.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build linux + package main import ( @@ -22,7 +24,6 @@ import ( "strconv" "sync" - "github.com/songgao/water" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" ) @@ -30,7 +31,7 @@ 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_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 @@ -60,21 +61,12 @@ func main() { bgWait := &sync.WaitGroup{} defer bgWait.Wait() - tun, err := setupTunDevice() + tun, err := NewTunDevice(OUTLINE_TUN_NAME, OUTLINE_TUN_IP) if err != nil { + fmt.Printf("fatal error: %v\n", err) return } - defer cleanUpTunDevice(tun) - - if err := showTunDevice(); err != nil { - return - } - if err := configureTunDevice(); err != nil { - return - } - if err := showTunDevice(); err != nil { - return - } + defer tun.Close() ss, err := NewOutlineDevice(&OutlineConfig{ Hostname: svrIp, @@ -124,83 +116,6 @@ func main() { fmt.Printf("\nReceived %v, cleaning up resources...\n", s) } -func showTunDevice() error { - l, err := netlink.LinkByName(OUTLINE_TUN_NAME) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - if tun, ok := l.(*netlink.Tuntap); ok { - mode := "unknown" - if tun.Mode == netlink.TUNTAP_MODE_TUN { - mode = "tun" - } else if tun.Mode == netlink.TUNTAP_MODE_TAP { - mode = "tap" - } - persist := "persist" - if tun.NonPersist { - persist = "non-persist" - } - fmt.Printf("\t%v %v %v mtu=%v attr=%v stat=%v\n", tun.Name, mode, persist, tun.MTU, tun.Attrs(), tun.Statistics) - return nil - } else { - fmt.Printf("fatal error: %v is not a tun device\n", OUTLINE_TUN_NAME) - return fmt.Errorf("tun device not found") - } -} - -func setupTunDevice() (*water.Interface, error) { - fmt.Println("setting up tun device...") - conf := water.Config{ - DeviceType: water.TUN, - PlatformSpecificParams: water.PlatformSpecificParams{ - Name: OUTLINE_TUN_NAME, - Persist: false, - }, - } - r, err := water.New(conf) - if err == nil { - fmt.Println("tun device created") - } else { - fmt.Printf("fatal error: %v\n", err) - } - return r, err -} - -func configureTunDevice() error { - fmt.Println("configuring tun device ip...") - tun, err := netlink.LinkByName(OUTLINE_TUN_NAME) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - addr, err := netlink.ParseAddr(OUTLINE_TUN_SUBNET) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - if err := netlink.AddrAdd(tun, addr); err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - if err := netlink.LinkSetUp(tun); err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - return nil -} - -func cleanUpTunDevice(tun *water.Interface) error { - fmt.Println("cleaning up tun device...") - err := tun.Close() - if err == nil { - fmt.Println("tun device deleted") - } else { - fmt.Printf("clean up error: %v\n", err) - } - return err -} - func showRouting() error { filter := netlink.Route{Table: OUTLINE_ROUTING_TABLE} routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, &filter, netlink.RT_FILTER_TABLE) diff --git a/x/outline-cli/tun_device.go b/x/outline-cli/tun_device.go new file mode 100644 index 00000000..d04bc399 --- /dev/null +++ b/x/outline-cli/tun_device.go @@ -0,0 +1,23 @@ +// 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 ( + "github.com/Jigsaw-Code/outline-internal-sdk/network" +) + +type TunDevice interface { + network.IPDevice +} diff --git a/x/outline-cli/tun_device_posix.go b/x/outline-cli/tun_device_posix.go new file mode 100644 index 00000000..0b2ac8e7 --- /dev/null +++ b/x/outline-cli/tun_device_posix.go @@ -0,0 +1,89 @@ +// 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. + +//go:build linux + +package main + +import ( + "errors" + "fmt" + + "github.com/songgao/water" + "github.com/vishvananda/netlink" +) + +type tunDevice struct { + *water.Interface +} + +var _ TunDevice = (*tunDevice)(nil) + +func NewTunDevice(name, ip string) (d TunDevice, err error) { + if len(name) == 0 { + return nil, errors.New("name is required for TUN/TAP device") + } + if len(ip) == 0 { + return nil, errors.New("ip is required for TUN/TAP device") + } + + tun, err := water.New(water.Config{ + DeviceType: water.TUN, + PlatformSpecificParams: water.PlatformSpecificParams{ + Name: name, + Persist: false, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to create TUN/TAP device: %w", err) + } + + defer func() { + if err != nil { + tun.Close() + } + }() + + tunDev := &tunDevice{tun} + if err := tunDev.configureSubnetAndBringUp(ip); err != nil { + return nil, fmt.Errorf("failed to configure TUN/TAP device: %w", err) + } + return tunDev, nil +} + +func (d *tunDevice) MTU() int { + // d.Interface. + // netlink.NewLinkAttrs().MTU + return 0 +} + +func (d *tunDevice) configureSubnetAndBringUp(ip string) error { + tunName := d.Interface.Name() + tunLink, err := netlink.LinkByName(tunName) + if err != nil { + return fmt.Errorf("TUN/TAP device '%s' not found: %w", tunName, err) + } + subnet := ip + "/32" + addr, err := netlink.ParseAddr(subnet) + if err != nil { + return fmt.Errorf("subnet address '%s' is not valid: %w", subnet, err) + } + if err := netlink.AddrAdd(tunLink, addr); err != nil { + return fmt.Errorf("failed to add subnet to TUN/TAP device '': %w", tunName, err) + } + if err := netlink.LinkSetUp(tunLink); err != nil { + return fmt.Errorf("failed to bring TUN/TAP device '%s' up: %w", tunName, err) + } + return nil +} From 01bef07d5dd24b6c8833599cf3eb5dcb441c996c Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 10 Oct 2023 17:14:46 -0400 Subject: [PATCH 07/21] move CLI code to x/examples --- x/{ => examples}/outline-cli/README.md | 0 x/{ => examples}/outline-cli/main.go | 0 x/{ => examples}/outline-cli/outline_device.go | 0 x/{ => examples}/outline-cli/tun_device.go | 0 x/{ => examples}/outline-cli/tun_device_posix.go | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename x/{ => examples}/outline-cli/README.md (100%) rename x/{ => examples}/outline-cli/main.go (100%) rename x/{ => examples}/outline-cli/outline_device.go (100%) rename x/{ => examples}/outline-cli/tun_device.go (100%) rename x/{ => examples}/outline-cli/tun_device_posix.go (100%) diff --git a/x/outline-cli/README.md b/x/examples/outline-cli/README.md similarity index 100% rename from x/outline-cli/README.md rename to x/examples/outline-cli/README.md diff --git a/x/outline-cli/main.go b/x/examples/outline-cli/main.go similarity index 100% rename from x/outline-cli/main.go rename to x/examples/outline-cli/main.go diff --git a/x/outline-cli/outline_device.go b/x/examples/outline-cli/outline_device.go similarity index 100% rename from x/outline-cli/outline_device.go rename to x/examples/outline-cli/outline_device.go diff --git a/x/outline-cli/tun_device.go b/x/examples/outline-cli/tun_device.go similarity index 100% rename from x/outline-cli/tun_device.go rename to x/examples/outline-cli/tun_device.go diff --git a/x/outline-cli/tun_device_posix.go b/x/examples/outline-cli/tun_device_posix.go similarity index 100% rename from x/outline-cli/tun_device_posix.go rename to x/examples/outline-cli/tun_device_posix.go From 3fe4380d37fb6f5992a37fcdeeec7540386969de Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Mon, 16 Oct 2023 16:57:04 -0400 Subject: [PATCH 08/21] update to use the latest outline-sdk --- x/config/config.go | 19 +++ x/config/shadowsocks.go | 9 ++ x/examples/outline-cli/main.go | 67 +--------- x/examples/outline-cli/outline_device.go | 126 +++++++----------- .../outline-cli/outline_packet_proxy.go | 67 ++++++++++ x/examples/outline-cli/tun_device.go | 2 +- 6 files changed, 154 insertions(+), 136 deletions(-) create mode 100644 x/examples/outline-cli/outline_packet_proxy.go diff --git a/x/config/config.go b/x/config/config.go index a2dca887..a93b8517 100644 --- a/x/config/config.go +++ b/x/config/config.go @@ -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) +} diff --git a/x/config/shadowsocks.go b/x/config/shadowsocks.go index 5d56cd68..c1c74818 100644 --- a/x/config/shadowsocks.go +++ b/x/config/shadowsocks.go @@ -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 diff --git a/x/examples/outline-cli/main.go b/x/examples/outline-cli/main.go index 0d83b546..cfded5f8 100644 --- a/x/examples/outline-cli/main.go +++ b/x/examples/outline-cli/main.go @@ -17,11 +17,11 @@ package main import ( + "flag" "fmt" "net" "os" "os/signal" - "strconv" "sync" "github.com/vishvananda/netlink" @@ -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 -// -// : the outline server IP (e.g. 111.111.111.111) -// : the outline server port (e.g. 21532) -// : 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() @@ -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 @@ -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) @@ -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) diff --git a/x/examples/outline-cli/outline_device.go b/x/examples/outline-cli/outline_device.go index 454b0282..1d338a72 100644 --- a/x/examples/outline-cli/outline_device.go +++ b/x/examples/outline-cli/outline_device.go @@ -15,20 +15,18 @@ 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 ( @@ -36,64 +34,29 @@ const ( 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) } @@ -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 { @@ -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") +} diff --git a/x/examples/outline-cli/outline_packet_proxy.go b/x/examples/outline-cli/outline_packet_proxy.go new file mode 100644 index 00000000..386ef627 --- /dev/null +++ b/x/examples/outline-cli/outline_packet_proxy.go @@ -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) + } +} diff --git a/x/examples/outline-cli/tun_device.go b/x/examples/outline-cli/tun_device.go index d04bc399..74f7d183 100644 --- a/x/examples/outline-cli/tun_device.go +++ b/x/examples/outline-cli/tun_device.go @@ -15,7 +15,7 @@ package main import ( - "github.com/Jigsaw-Code/outline-internal-sdk/network" + "github.com/Jigsaw-Code/outline-sdk/network" ) type TunDevice interface { From a22215d131e6acebd54e2d980e0b6a645ca03e43 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 17 Oct 2023 18:06:35 -0400 Subject: [PATCH 09/21] add dns setting and ipv6 setting support --- x/examples/outline-cli/dns_posix.go | 67 +++++++++ x/examples/outline-cli/ipv6_posix.go | 51 +++++++ x/examples/outline-cli/main.go | 155 +++++---------------- x/examples/outline-cli/outline_device.go | 37 +---- x/examples/outline-cli/routing_posix.go | 132 ++++++++++++++++++ x/examples/outline-cli/tun_device_posix.go | 6 +- 6 files changed, 293 insertions(+), 155 deletions(-) create mode 100644 x/examples/outline-cli/dns_posix.go create mode 100644 x/examples/outline-cli/ipv6_posix.go create mode 100644 x/examples/outline-cli/routing_posix.go diff --git a/x/examples/outline-cli/dns_posix.go b/x/examples/outline-cli/dns_posix.go new file mode 100644 index 00000000..8f162dd3 --- /dev/null +++ b/x/examples/outline-cli/dns_posix.go @@ -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. + +//go:build linux + +package main + +import ( + "fmt" + "os" +) + +const ( + resolvConfFile = "/etc/resolv.conf" + resolvConfHeadFile = "/etc/resolv.conf.head" + resolvConfBackupFile = "/etc/resolv.outlinecli.backup" + resolvConfHeadBackupFile = "/etc/resolv.head.outlinecli.backup" +) + +func setSystemDNSServer(serverHost string) error { + setting := []byte(`# Outline CLI DNS Setting +# The original file has been renamed as resolv[.head].outlinecli.backup +nameserver ` + serverHost + "\n") + + if err := backupAndWriteFile(resolvConfFile, resolvConfBackupFile, setting); err != nil { + return err + } + return backupAndWriteFile(resolvConfHeadFile, resolvConfHeadBackupFile, setting) +} + +func restoreSystemDNSServer() { + restoreFileIfExists(resolvConfBackupFile, resolvConfFile) + restoreFileIfExists(resolvConfHeadBackupFile, resolvConfHeadFile) +} + +func backupAndWriteFile(original, backup string, data []byte) error { + if err := os.Rename(original, backup); err != nil { + return fmt.Errorf("failed to backup DNS config file '%s' to '%s': %w", original, backup, err) + } + if err := os.WriteFile(original, data, 0644); err != nil { + return fmt.Errorf("failed to write DNS config file '%s': %w", original, err) + } + return nil +} + +func restoreFileIfExists(backup, original string) { + if _, err := os.Stat(backup); err != nil { + fmt.Printf("[warn] failed to read DNS config backup '%s': %v\n", backup, err) + return + } + if err := os.Rename(backup, original); err != nil { + fmt.Printf("[error] failed to restore DNS config from backup '%s' to '%s': %v\n", backup, original, err) + return + } + fmt.Printf("[info] DNS config restored from '%s' to '%s'\n", backup, original) +} diff --git a/x/examples/outline-cli/ipv6_posix.go b/x/examples/outline-cli/ipv6_posix.go new file mode 100644 index 00000000..225a3996 --- /dev/null +++ b/x/examples/outline-cli/ipv6_posix.go @@ -0,0 +1,51 @@ +// 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. + +//go:build linux + +package main + +import ( + "fmt" + "os" +) + +const disableIPv6ProcFile = "/proc/sys/net/ipv6/conf/all/disable_ipv6" + +// enableIPv6 enables or disables the IPv6 support for the Linux system. +// It returns the previous setting value so the caller can restore it. +// Non-nil error means we cannot find the IPv6 setting. +func enableIPv6(enabled bool) (bool, error) { + disabledStr, err := os.ReadFile(disableIPv6ProcFile) + if err != nil { + return false, fmt.Errorf("failed to read IPv6 config: %w", err) + } + if disabledStr[0] != '0' && disabledStr[0] != '1' { + return false, fmt.Errorf("invalid IPv6 config value: %v", disabledStr) + } + + prevEnabled := disabledStr[0] == '0' + + if enabled { + disabledStr[0] = '0' + } else { + disabledStr[0] = '1' + } + if err := os.WriteFile(disableIPv6ProcFile, disabledStr, 0644); err != nil { + return prevEnabled, fmt.Errorf("failed to write IPv6 config: %w", err) + } + + fmt.Printf("[info] global IPv6 enabled: %v\n", enabled) + return prevEnabled, nil +} diff --git a/x/examples/outline-cli/main.go b/x/examples/outline-cli/main.go index cfded5f8..3ebca36f 100644 --- a/x/examples/outline-cli/main.go +++ b/x/examples/outline-cli/main.go @@ -19,12 +19,11 @@ package main import ( "flag" "fmt" - "net" + "io" "os" "os/signal" "sync" - "github.com/vishvananda/netlink" "golang.org/x/sys/unix" ) @@ -35,154 +34,76 @@ 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 +const OUTLINE_DNS_SERVER = "9.9.9.9" // ./app -transport "ss://..." func main() { - fmt.Println("OutlineVPN CLI (experimental-10161603)") + fmt.Println("OutlineVPN CLI (experimental)") transportFlag := flag.String("transport", "", "Transport config") flag.Parse() - bgWait := &sync.WaitGroup{} - defer bgWait.Wait() + // this WaitGroup must Wait() after tun is closed + trafficCopyWg := &sync.WaitGroup{} + defer trafficCopyWg.Wait() tun, err := NewTunDevice(OUTLINE_TUN_NAME, OUTLINE_TUN_IP) if err != nil { - fmt.Printf("fatal error: %v\n", err) + fmt.Printf("[error] failed to create tun device: %v\n", err) return } defer tun.Close() + // disable IPv6 before resolving Shadowsocks server IP + prevIPv6, err := enableIPv6(false) + if err != nil { + fmt.Printf("[error] failed to disable IPv6: %v\n", err) + return + } + defer enableIPv6(prevIPv6) + ss, err := NewOutlineDevice(*transportFlag) if err != nil { - fmt.Printf("fatal error: %v", err) + fmt.Printf("[error] failed to create Outline device: %v", err) return } defer ss.Close() ss.Refresh() - bgWait.Add(1) + // Copy the traffic from tun device to OutlineDevice bidirectionally + trafficCopyWg.Add(2) + go func() { + defer trafficCopyWg.Done() + written, err := io.Copy(ss, tun) + fmt.Printf("[info] tun -> OutlineDevice stopped: %v %v\n", written, err) + }() go func() { - defer bgWait.Done() - if err := ss.RelayTraffic(tun); err != nil { - fmt.Printf("Traffic bridge destroyed: %v\n", err) - } + defer trafficCopyWg.Done() + written, err := io.Copy(tun, ss) + fmt.Printf("[info] OutlineDevice -> tun stopped: %v %v\n", written, err) }() - err = setupRouting() - if err != nil { + if err := setSystemDNSServer(OUTLINE_DNS_SERVER); err != nil { + fmt.Printf("[error] failed to configure system DNS: %v", err) return } - defer cleanUpRouting() + defer restoreSystemDNSServer() - svrIpCidr := ss.GetServerIP().String() + "/32" - r, err := setupIpRule(svrIpCidr) - if err != nil { + if err := startRouting(ss.GetServerIP().String(), + OUTLINE_TUN_NAME, + OUTLINE_GW_SUBNET, + OUTLINE_TUN_IP, + OUTLINE_GW_IP, + OUTLINE_ROUTING_TABLE, + OUTLINE_ROUTING_PRIORITY); err != nil { + fmt.Printf("[error] failed to configure routing: %v", err) return } - defer cleanUpRule(r) + defer stopRouting(OUTLINE_ROUTING_TABLE) 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 setupRouting() error { - fmt.Println("configuring outline routing table...") - tun, err := netlink.LinkByName(OUTLINE_TUN_NAME) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - - dst, err := netlink.ParseIPNet(OUTLINE_GW_SUBNET) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - r := netlink.Route{ - LinkIndex: tun.Attrs().Index, - Table: OUTLINE_ROUTING_TABLE, - Dst: dst, - Src: net.ParseIP(OUTLINE_TUN_IP), - Scope: netlink.SCOPE_LINK, - } - fmt.Printf("\trouting only from %v to %v through nic %v...\n", r.Src, r.Dst, r.LinkIndex) - err = netlink.RouteAdd(&r) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - - r = netlink.Route{ - LinkIndex: tun.Attrs().Index, - Table: OUTLINE_ROUTING_TABLE, - Gw: net.ParseIP(OUTLINE_GW_IP), - } - fmt.Printf("\tdefault routing entry via gw %v through nic %v...\n", r.Gw, r.LinkIndex) - err = netlink.RouteAdd(&r) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - - fmt.Println("routing table has been successfully configured") - return nil -} - -func cleanUpRouting() error { - fmt.Println("cleaning up outline routing table...") - 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 - } - var lastErr error = nil - for _, route := range routes { - if err := netlink.RouteDel(&route); err != nil { - fmt.Printf("fatal error: %v\n", err) - lastErr = err - } - } - if lastErr == nil { - fmt.Println("routing table has been reset") - } - return lastErr -} - -func setupIpRule(svrIp string) (*netlink.Rule, error) { - fmt.Println("adding ip rule for outline routing table...") - dst, err := netlink.ParseIPNet(svrIp) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return nil, err - } - rule := netlink.NewRule() - rule.Priority = OUTLINE_ROUTING_PRIORITY - rule.Family = netlink.FAMILY_V4 - rule.Table = OUTLINE_ROUTING_TABLE - rule.Dst = dst - rule.Invert = true - fmt.Printf("+from all not to %v via table %v...\n", rule.Dst, rule.Table) - err = netlink.RuleAdd(rule) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return nil, err - } - fmt.Println("ip rule for outline routing table created") - return rule, nil -} - -func cleanUpRule(rule *netlink.Rule) error { - fmt.Println("cleaning up ip rule of routing table...") - err := netlink.RuleDel(rule) - if err != nil { - fmt.Printf("fatal error: %v\n", err) - return err - } - fmt.Println("ip rule of routing table deleted") - return nil -} diff --git a/x/examples/outline-cli/outline_device.go b/x/examples/outline-cli/outline_device.go index 1d338a72..0a17a28b 100644 --- a/x/examples/outline-cli/outline_device.go +++ b/x/examples/outline-cli/outline_device.go @@ -17,11 +17,9 @@ package main import ( "errors" "fmt" - "io" "net" "net/url" "strings" - "sync" "github.com/Jigsaw-Code/outline-sdk/network" "github.com/Jigsaw-Code/outline-sdk/network/lwip2transport" @@ -35,7 +33,7 @@ const ( ) type OutlineDevice struct { - t2s network.IPDevice + network.IPDevice sd transport.StreamDialer pp *outlinePacketProxy svrIP net.IP @@ -56,7 +54,7 @@ func NewOutlineDevice(transportConfig string) (od *OutlineDevice, err error) { if od.pp, err = newOutlinePacketProxy(transportConfig); err != nil { return nil, fmt.Errorf("failed to create delegate UDP proxy: %w", err) } - if od.t2s, err = lwip2transport.ConfigureDevice(od.sd, od.pp); err != nil { + if od.IPDevice, err = lwip2transport.ConfigureDevice(od.sd, od.pp); err != nil { return nil, fmt.Errorf("failed to configure lwIP: %w", err) } @@ -64,7 +62,7 @@ func NewOutlineDevice(transportConfig string) (od *OutlineDevice, err error) { } func (d *OutlineDevice) Close() error { - return d.t2s.Close() + return d.IPDevice.Close() } func (d *OutlineDevice) Refresh() error { @@ -75,35 +73,6 @@ func (d *OutlineDevice) GetServerIP() net.IP { return d.svrIP } -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) -} - func resolveShadowsocksServerIPFromConfig(transportConfig string) (net.IP, error) { if strings.Contains(transportConfig, "|") { return nil, errors.New("multi-part config is not supported") diff --git a/x/examples/outline-cli/routing_posix.go b/x/examples/outline-cli/routing_posix.go new file mode 100644 index 00000000..0f1f3e20 --- /dev/null +++ b/x/examples/outline-cli/routing_posix.go @@ -0,0 +1,132 @@ +// 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. + +//go:build linux + +package main + +import ( + "errors" + "fmt" + "net" + + "github.com/vishvananda/netlink" +) + +var ipRule *netlink.Rule = nil + +func startRouting(svrIP string, tunName, gwSubnet string, tunIP, gwIP string, routingTable, routingPriority int) error { + if err := setupRoutingTable(routingTable, tunName, gwSubnet, tunIP, gwIP); err != nil { + return err + } + return setupIpRule(svrIP+"/32", routingTable, routingPriority) +} + +func stopRouting(routingTable int) { + if err := cleanUpRoutingTable(routingTable); err != nil { + fmt.Printf("[error] failed to clean up routing table '%v': %v\n", routingTable, err) + } + if err := cleanUpRule(); err != nil { + fmt.Printf("[error] failed to clean up IP rule: %v\n", err) + } +} + +func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP, gwIP string) error { + tun, err := netlink.LinkByName(tunName) + if err != nil { + return fmt.Errorf("failed to find tun device '%s': %w", tunName, err) + } + + dst, err := netlink.ParseIPNet(gwSubnet) + if err != nil { + return fmt.Errorf("failed to parse gateway '%s': %w", gwSubnet, err) + } + + r := netlink.Route{ + LinkIndex: tun.Attrs().Index, + Table: routingTable, + Dst: dst, + Src: net.ParseIP(tunIP), + Scope: netlink.SCOPE_LINK, + } + + if err = netlink.RouteAdd(&r); err != nil { + return fmt.Errorf("failed to add routing entry '%v' -> '%v': %w", r.Src, r.Dst, err) + } + fmt.Printf("[info] routing traffic from %v to %v through nic %v\n", r.Src, r.Dst, r.LinkIndex) + + r = netlink.Route{ + LinkIndex: tun.Attrs().Index, + Table: routingTable, + Gw: net.ParseIP(gwIP), + } + + if err := netlink.RouteAdd(&r); err != nil { + return fmt.Errorf("failed to add gateway routing entry '%v': %w", r.Gw, err) + } + fmt.Printf("[info] routing traffic via gw %v through nic %v...\n", r.Gw, r.LinkIndex) + + return nil +} + +func cleanUpRoutingTable(routingTable int) error { + filter := netlink.Route{Table: routingTable} + routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, &filter, netlink.RT_FILTER_TABLE) + if err != nil { + return fmt.Errorf("failed to list entries in routing table '%v': %w", routingTable, err) + } + + var rtDelErr error = nil + for _, route := range routes { + if err := netlink.RouteDel(&route); err != nil { + rtDelErr = errors.Join(rtDelErr, fmt.Errorf("failed to remove routing entry: %w", err)) + } + } + if rtDelErr == nil { + fmt.Printf("[info] routing table '%v' has been cleaned up\n", routingTable) + } + return rtDelErr +} + +func setupIpRule(svrIp string, routingTable, routingPriority int) error { + dst, err := netlink.ParseIPNet(svrIp) + if err != nil { + return fmt.Errorf("failed to parse server IP CIDR '%s': %w", svrIp, err) + } + + ipRule = netlink.NewRule() + ipRule.Priority = routingPriority + ipRule.Family = netlink.FAMILY_V4 + ipRule.Table = routingTable + ipRule.Dst = dst + ipRule.Invert = true + + if err := netlink.RuleAdd(ipRule); err != nil { + return fmt.Errorf("failed to add IP rule (table %v, dst %v): %w", ipRule.Table, ipRule.Dst, err) + } + fmt.Printf("[info] ip rule 'from all not to %v via table %v' created\n", ipRule.Dst, ipRule.Table) + return nil +} + +func cleanUpRule() error { + if ipRule == nil { + return nil + } + if err := netlink.RuleDel(ipRule); err != nil { + return fmt.Errorf("failed to delete IP rule of routing table '%v': %w", ipRule.Table, err) + } + fmt.Printf("[info] ip rule of routing table '%v' deleted\n", ipRule.Table) + ipRule = nil + return nil +} diff --git a/x/examples/outline-cli/tun_device_posix.go b/x/examples/outline-cli/tun_device_posix.go index 0b2ac8e7..d3614c44 100644 --- a/x/examples/outline-cli/tun_device_posix.go +++ b/x/examples/outline-cli/tun_device_posix.go @@ -63,9 +63,7 @@ func NewTunDevice(name, ip string) (d TunDevice, err error) { } func (d *tunDevice) MTU() int { - // d.Interface. - // netlink.NewLinkAttrs().MTU - return 0 + return 1500 } func (d *tunDevice) configureSubnetAndBringUp(ip string) error { @@ -80,7 +78,7 @@ func (d *tunDevice) configureSubnetAndBringUp(ip string) error { return fmt.Errorf("subnet address '%s' is not valid: %w", subnet, err) } if err := netlink.AddrAdd(tunLink, addr); err != nil { - return fmt.Errorf("failed to add subnet to TUN/TAP device '': %w", tunName, err) + return fmt.Errorf("failed to add subnet to TUN/TAP device '%s': %w", tunName, err) } if err := netlink.LinkSetUp(tunLink); err != nil { return fmt.Errorf("failed to bring TUN/TAP device '%s' up: %w", tunName, err) From 58c2a634e89daf75e4679b83d8e95b24ace56ab0 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 17 Oct 2023 18:16:46 -0400 Subject: [PATCH 10/21] update instructions in README --- x/examples/outline-cli/README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/x/examples/outline-cli/README.md b/x/examples/outline-cli/README.md index dbd91433..80707150 100644 --- a/x/examples/outline-cli/README.md +++ b/x/examples/outline-cli/README.md @@ -1,32 +1,30 @@ -# Outline VPN CLI (PoC) +# OutlineVPN CLI -The CLI interface of Outline VPN client for Linux. +The CLI interface of OutlineVPN client for Linux. ## Usage #### Standard ``` -./OutlineCLI "" "" "" +./outline-cli -transport "ss://" ``` #### Advanced (with Golang) ``` -go run github.com/Jigsaw-Code/outline-internal-sdk/x/outline-cli@latest "" "" "" +go run github.com/Jigsaw-Code/outline-sdk/x/examples/outline-cli@latest -transport "ss://" ``` -### CLI Arguments +### Arguments -- `svr-ip` : the outline server IP (for example, `111.111.111.111`) -- `svr-port` : the outline server port (for example, `21532`) -- `svr-pass` : the outline server password +- `-transport` : the Outline server access key from the service provider, it should start with "ss://" ## Build (for Developers) We recommend to setup a [go workspace](https://go.dev/blog/get-familiar-with-workspaces) to build the code. Then use the following command to build the CLI (only support Linux): ``` -cd outline-internal-sdk/x -go build -o OutlineCLI -ldflags="-extldflags=-static" ./outline-cli +cd outline-sdk/x/examples/ +go build -o outline-cli -ldflags="-extldflags=-static" ./outline-cli ``` From 4cfacb7c277bb43bf8b687135ad437ee111ea7bf Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 17 Oct 2023 18:23:29 -0400 Subject: [PATCH 11/21] replace fmt.Printf with log.Printf --- x/examples/outline-cli/dns_posix.go | 7 ++++--- x/examples/outline-cli/ipv6_posix.go | 3 ++- x/examples/outline-cli/main.go | 17 +++++++++-------- x/examples/outline-cli/routing_posix.go | 15 ++++++++------- x/go.sum | 1 + 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/x/examples/outline-cli/dns_posix.go b/x/examples/outline-cli/dns_posix.go index 8f162dd3..68ab01ab 100644 --- a/x/examples/outline-cli/dns_posix.go +++ b/x/examples/outline-cli/dns_posix.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "log" "os" ) @@ -56,12 +57,12 @@ func backupAndWriteFile(original, backup string, data []byte) error { func restoreFileIfExists(backup, original string) { if _, err := os.Stat(backup); err != nil { - fmt.Printf("[warn] failed to read DNS config backup '%s': %v\n", backup, err) + log.Printf("[warn] no DNS config backup file '%s' presents: %v\n", backup, err) return } if err := os.Rename(backup, original); err != nil { - fmt.Printf("[error] failed to restore DNS config from backup '%s' to '%s': %v\n", backup, original, err) + log.Printf("[error] failed to restore DNS config from backup '%s' to '%s': %v\n", backup, original, err) return } - fmt.Printf("[info] DNS config restored from '%s' to '%s'\n", backup, original) + log.Printf("[info] DNS config restored from '%s' to '%s'\n", backup, original) } diff --git a/x/examples/outline-cli/ipv6_posix.go b/x/examples/outline-cli/ipv6_posix.go index 225a3996..9022394e 100644 --- a/x/examples/outline-cli/ipv6_posix.go +++ b/x/examples/outline-cli/ipv6_posix.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "log" "os" ) @@ -46,6 +47,6 @@ func enableIPv6(enabled bool) (bool, error) { return prevEnabled, fmt.Errorf("failed to write IPv6 config: %w", err) } - fmt.Printf("[info] global IPv6 enabled: %v\n", enabled) + log.Printf("[info] global IPv6 enabled: %v\n", enabled) return prevEnabled, nil } diff --git a/x/examples/outline-cli/main.go b/x/examples/outline-cli/main.go index 3ebca36f..b28391c8 100644 --- a/x/examples/outline-cli/main.go +++ b/x/examples/outline-cli/main.go @@ -20,6 +20,7 @@ import ( "flag" "fmt" "io" + "log" "os" "os/signal" "sync" @@ -49,7 +50,7 @@ func main() { tun, err := NewTunDevice(OUTLINE_TUN_NAME, OUTLINE_TUN_IP) if err != nil { - fmt.Printf("[error] failed to create tun device: %v\n", err) + log.Printf("[error] failed to create tun device: %v\n", err) return } defer tun.Close() @@ -57,14 +58,14 @@ func main() { // disable IPv6 before resolving Shadowsocks server IP prevIPv6, err := enableIPv6(false) if err != nil { - fmt.Printf("[error] failed to disable IPv6: %v\n", err) + log.Printf("[error] failed to disable IPv6: %v\n", err) return } defer enableIPv6(prevIPv6) ss, err := NewOutlineDevice(*transportFlag) if err != nil { - fmt.Printf("[error] failed to create Outline device: %v", err) + log.Printf("[error] failed to create Outline device: %v", err) return } defer ss.Close() @@ -76,16 +77,16 @@ func main() { go func() { defer trafficCopyWg.Done() written, err := io.Copy(ss, tun) - fmt.Printf("[info] tun -> OutlineDevice stopped: %v %v\n", written, err) + log.Printf("[info] tun -> OutlineDevice stopped: %v %v\n", written, err) }() go func() { defer trafficCopyWg.Done() written, err := io.Copy(tun, ss) - fmt.Printf("[info] OutlineDevice -> tun stopped: %v %v\n", written, err) + log.Printf("[info] OutlineDevice -> tun stopped: %v %v\n", written, err) }() if err := setSystemDNSServer(OUTLINE_DNS_SERVER); err != nil { - fmt.Printf("[error] failed to configure system DNS: %v", err) + log.Printf("[error] failed to configure system DNS: %v", err) return } defer restoreSystemDNSServer() @@ -97,7 +98,7 @@ func main() { OUTLINE_GW_IP, OUTLINE_ROUTING_TABLE, OUTLINE_ROUTING_PRIORITY); err != nil { - fmt.Printf("[error] failed to configure routing: %v", err) + log.Printf("[error] failed to configure routing: %v", err) return } defer stopRouting(OUTLINE_ROUTING_TABLE) @@ -105,5 +106,5 @@ func main() { 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) + log.Printf("\nreceived %v, terminating...\n", s) } diff --git a/x/examples/outline-cli/routing_posix.go b/x/examples/outline-cli/routing_posix.go index 0f1f3e20..03566c6d 100644 --- a/x/examples/outline-cli/routing_posix.go +++ b/x/examples/outline-cli/routing_posix.go @@ -19,6 +19,7 @@ package main import ( "errors" "fmt" + "log" "net" "github.com/vishvananda/netlink" @@ -35,10 +36,10 @@ func startRouting(svrIP string, tunName, gwSubnet string, tunIP, gwIP string, ro func stopRouting(routingTable int) { if err := cleanUpRoutingTable(routingTable); err != nil { - fmt.Printf("[error] failed to clean up routing table '%v': %v\n", routingTable, err) + log.Printf("[error] failed to clean up routing table '%v': %v\n", routingTable, err) } if err := cleanUpRule(); err != nil { - fmt.Printf("[error] failed to clean up IP rule: %v\n", err) + log.Printf("[error] failed to clean up IP rule: %v\n", err) } } @@ -64,7 +65,7 @@ func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP, gwIP s if err = netlink.RouteAdd(&r); err != nil { return fmt.Errorf("failed to add routing entry '%v' -> '%v': %w", r.Src, r.Dst, err) } - fmt.Printf("[info] routing traffic from %v to %v through nic %v\n", r.Src, r.Dst, r.LinkIndex) + log.Printf("[info] routing traffic from %v to %v through nic %v\n", r.Src, r.Dst, r.LinkIndex) r = netlink.Route{ LinkIndex: tun.Attrs().Index, @@ -75,7 +76,7 @@ func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP, gwIP s if err := netlink.RouteAdd(&r); err != nil { return fmt.Errorf("failed to add gateway routing entry '%v': %w", r.Gw, err) } - fmt.Printf("[info] routing traffic via gw %v through nic %v...\n", r.Gw, r.LinkIndex) + log.Printf("[info] routing traffic via gw %v through nic %v...\n", r.Gw, r.LinkIndex) return nil } @@ -94,7 +95,7 @@ func cleanUpRoutingTable(routingTable int) error { } } if rtDelErr == nil { - fmt.Printf("[info] routing table '%v' has been cleaned up\n", routingTable) + log.Printf("[info] routing table '%v' has been cleaned up\n", routingTable) } return rtDelErr } @@ -115,7 +116,7 @@ func setupIpRule(svrIp string, routingTable, routingPriority int) error { if err := netlink.RuleAdd(ipRule); err != nil { return fmt.Errorf("failed to add IP rule (table %v, dst %v): %w", ipRule.Table, ipRule.Dst, err) } - fmt.Printf("[info] ip rule 'from all not to %v via table %v' created\n", ipRule.Dst, ipRule.Table) + log.Printf("[info] ip rule 'from all not to %v via table %v' created\n", ipRule.Dst, ipRule.Table) return nil } @@ -126,7 +127,7 @@ func cleanUpRule() error { if err := netlink.RuleDel(ipRule); err != nil { return fmt.Errorf("failed to delete IP rule of routing table '%v': %w", ipRule.Table, err) } - fmt.Printf("[info] ip rule of routing table '%v' deleted\n", ipRule.Table) + log.Printf("[info] ip rule of routing table '%v' deleted\n", ipRule.Table) ipRule = nil return nil } diff --git a/x/go.sum b/x/go.sum index 064f0de8..d785d452 100644 --- a/x/go.sum +++ b/x/go.sum @@ -40,6 +40,7 @@ golang.org/x/mobile v0.0.0-20230905140555-fbe1c053b6a9/go.mod h1:2jxcxt/JNJik+N+ golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.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.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= From 1ebabd00026baad6a423f52ab1f113266381ad80 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 18 Oct 2023 18:06:13 -0400 Subject: [PATCH 12/21] add RoutingConfig type --- x/examples/outline-cli/app.go | 30 ++++++ x/examples/outline-cli/app_linux.go | 82 ++++++++++++++++ .../{tun_device.go => app_other.go} | 11 ++- .../{dns_posix.go => dns_linux.go} | 2 - .../{ipv6_posix.go => ipv6_linux.go} | 4 +- x/examples/outline-cli/main.go | 94 +++---------------- .../{routing_posix.go => routing_linux.go} | 12 +-- ...un_device_posix.go => tun_device_linux.go} | 15 ++- x/go.sum | 1 + 9 files changed, 146 insertions(+), 105 deletions(-) create mode 100644 x/examples/outline-cli/app.go create mode 100644 x/examples/outline-cli/app_linux.go rename x/examples/outline-cli/{tun_device.go => app_other.go} (82%) rename x/examples/outline-cli/{dns_posix.go => dns_linux.go} (99%) rename x/examples/outline-cli/{ipv6_posix.go => ipv6_linux.go} (95%) rename x/examples/outline-cli/{routing_posix.go => routing_linux.go} (91%) rename x/examples/outline-cli/{tun_device_posix.go => tun_device_linux.go} (85%) diff --git a/x/examples/outline-cli/app.go b/x/examples/outline-cli/app.go new file mode 100644 index 00000000..3147a206 --- /dev/null +++ b/x/examples/outline-cli/app.go @@ -0,0 +1,30 @@ +// 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 + +type App struct { + TransportConfig *string + RoutingConfig *RoutingConfig +} + +type RoutingConfig struct { + TunDeviceName string + TunDeviceIP string + TunDeviceMTU int + TunGatewayCIDR string + RoutingTableID int + RoutingTablePriority int + DNSServerIP string +} diff --git a/x/examples/outline-cli/app_linux.go b/x/examples/outline-cli/app_linux.go new file mode 100644 index 00000000..23610902 --- /dev/null +++ b/x/examples/outline-cli/app_linux.go @@ -0,0 +1,82 @@ +// 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 ( + "fmt" + "io" + "log" + "os" + "os/signal" + "sync" + + "golang.org/x/sys/unix" +) + +func (app App) Run() error { + // this WaitGroup must Wait() after tun is closed + trafficCopyWg := &sync.WaitGroup{} + defer trafficCopyWg.Wait() + + tun, err := newTunDevice(app.RoutingConfig) + if err != nil { + return fmt.Errorf("failed to create tun device: %w", err) + } + defer tun.Close() + + // disable IPv6 before resolving Shadowsocks server IP + prevIPv6, err := enableIPv6(false) + if err != nil { + return fmt.Errorf("failed to disable IPv6: %w", err) + } + defer enableIPv6(prevIPv6) + + ss, err := NewOutlineDevice(*app.TransportConfig) + if err != nil { + return fmt.Errorf("failed to create OutlineDevice: %w", err) + } + defer ss.Close() + + ss.Refresh() + + // Copy the traffic from tun device to OutlineDevice bidirectionally + trafficCopyWg.Add(2) + go func() { + defer trafficCopyWg.Done() + written, err := io.Copy(ss, tun) + log.Printf("[info] tun -> OutlineDevice stopped: %v %v\n", written, err) + }() + go func() { + defer trafficCopyWg.Done() + written, err := io.Copy(tun, ss) + log.Printf("[info] OutlineDevice -> tun stopped: %v %v\n", written, err) + }() + + if err := setSystemDNSServer(app.RoutingConfig.DNSServerIP); err != nil { + return fmt.Errorf("failed to configure system DNS: %w", err) + } + defer restoreSystemDNSServer() + + if err := startRouting(ss.GetServerIP().String(), app.RoutingConfig); err != nil { + return fmt.Errorf("failed to configure routing: %w", err) + } + defer stopRouting(app.RoutingConfig.RoutingTableID) + + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, os.Interrupt, unix.SIGTERM, unix.SIGHUP) + s := <-sigc + log.Printf("\nreceived %v, terminating...\n", s) + return nil +} diff --git a/x/examples/outline-cli/tun_device.go b/x/examples/outline-cli/app_other.go similarity index 82% rename from x/examples/outline-cli/tun_device.go rename to x/examples/outline-cli/app_other.go index 74f7d183..a7a67ba7 100644 --- a/x/examples/outline-cli/tun_device.go +++ b/x/examples/outline-cli/app_other.go @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux + package main -import ( - "github.com/Jigsaw-Code/outline-sdk/network" -) +import "fmt" -type TunDevice interface { - network.IPDevice +func (App) Run() error { + fmt.Println("OutlineVPN does not support this platform") + return nil } diff --git a/x/examples/outline-cli/dns_posix.go b/x/examples/outline-cli/dns_linux.go similarity index 99% rename from x/examples/outline-cli/dns_posix.go rename to x/examples/outline-cli/dns_linux.go index 68ab01ab..01f26f31 100644 --- a/x/examples/outline-cli/dns_posix.go +++ b/x/examples/outline-cli/dns_linux.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build linux - package main import ( diff --git a/x/examples/outline-cli/ipv6_posix.go b/x/examples/outline-cli/ipv6_linux.go similarity index 95% rename from x/examples/outline-cli/ipv6_posix.go rename to x/examples/outline-cli/ipv6_linux.go index 9022394e..4a02ffe9 100644 --- a/x/examples/outline-cli/ipv6_posix.go +++ b/x/examples/outline-cli/ipv6_linux.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build linux - package main import ( @@ -47,6 +45,6 @@ func enableIPv6(enabled bool) (bool, error) { return prevEnabled, fmt.Errorf("failed to write IPv6 config: %w", err) } - log.Printf("[info] global IPv6 enabled: %v\n", enabled) + log.Printf("[info] updated global IPv6 support: %v\n", enabled) return prevEnabled, nil } diff --git a/x/examples/outline-cli/main.go b/x/examples/outline-cli/main.go index b28391c8..65c737d3 100644 --- a/x/examples/outline-cli/main.go +++ b/x/examples/outline-cli/main.go @@ -12,99 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build linux - package main import ( "flag" "fmt" - "io" "log" - "os" - "os/signal" - "sync" - - "golang.org/x/sys/unix" ) -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_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 -const OUTLINE_DNS_SERVER = "9.9.9.9" - // ./app -transport "ss://..." func main() { fmt.Println("OutlineVPN CLI (experimental)") - transportFlag := flag.String("transport", "", "Transport config") - flag.Parse() - - // this WaitGroup must Wait() after tun is closed - trafficCopyWg := &sync.WaitGroup{} - defer trafficCopyWg.Wait() - - tun, err := NewTunDevice(OUTLINE_TUN_NAME, OUTLINE_TUN_IP) - if err != nil { - log.Printf("[error] failed to create tun device: %v\n", err) - return - } - defer tun.Close() - - // disable IPv6 before resolving Shadowsocks server IP - prevIPv6, err := enableIPv6(false) - if err != nil { - log.Printf("[error] failed to disable IPv6: %v\n", err) - return + app := App{ + TransportConfig: flag.String("transport", "", "Transport config"), + RoutingConfig: &RoutingConfig{ + TunDeviceName: "outline233", + TunDeviceIP: "10.233.233.1", + TunDeviceMTU: 1500, // todo: read this from netlink + TunGatewayCIDR: "10.233.233.2/32", + RoutingTableID: 233, + RoutingTablePriority: 23333, + DNSServerIP: "9.9.9.9", + }, } - defer enableIPv6(prevIPv6) - - ss, err := NewOutlineDevice(*transportFlag) - if err != nil { - log.Printf("[error] failed to create Outline device: %v", err) - return - } - defer ss.Close() - - ss.Refresh() - - // Copy the traffic from tun device to OutlineDevice bidirectionally - trafficCopyWg.Add(2) - go func() { - defer trafficCopyWg.Done() - written, err := io.Copy(ss, tun) - log.Printf("[info] tun -> OutlineDevice stopped: %v %v\n", written, err) - }() - go func() { - defer trafficCopyWg.Done() - written, err := io.Copy(tun, ss) - log.Printf("[info] OutlineDevice -> tun stopped: %v %v\n", written, err) - }() - - if err := setSystemDNSServer(OUTLINE_DNS_SERVER); err != nil { - log.Printf("[error] failed to configure system DNS: %v", err) - return - } - defer restoreSystemDNSServer() + flag.Parse() - if err := startRouting(ss.GetServerIP().String(), - OUTLINE_TUN_NAME, - OUTLINE_GW_SUBNET, - OUTLINE_TUN_IP, - OUTLINE_GW_IP, - OUTLINE_ROUTING_TABLE, - OUTLINE_ROUTING_PRIORITY); err != nil { - log.Printf("[error] failed to configure routing: %v", err) - return + if err := app.Run(); err != nil { + log.Printf("[error] %v\n", err) } - defer stopRouting(OUTLINE_ROUTING_TABLE) - - sigc := make(chan os.Signal, 1) - signal.Notify(sigc, os.Interrupt, unix.SIGTERM, unix.SIGHUP) - s := <-sigc - log.Printf("\nreceived %v, terminating...\n", s) } diff --git a/x/examples/outline-cli/routing_posix.go b/x/examples/outline-cli/routing_linux.go similarity index 91% rename from x/examples/outline-cli/routing_posix.go rename to x/examples/outline-cli/routing_linux.go index 03566c6d..e6bc6b09 100644 --- a/x/examples/outline-cli/routing_posix.go +++ b/x/examples/outline-cli/routing_linux.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build linux - package main import ( @@ -27,11 +25,11 @@ import ( var ipRule *netlink.Rule = nil -func startRouting(svrIP string, tunName, gwSubnet string, tunIP, gwIP string, routingTable, routingPriority int) error { - if err := setupRoutingTable(routingTable, tunName, gwSubnet, tunIP, gwIP); err != nil { +func startRouting(proxyIP string, config *RoutingConfig) error { + if err := setupRoutingTable(config.RoutingTableID, config.TunDeviceName, config.TunGatewayCIDR, config.TunDeviceIP); err != nil { return err } - return setupIpRule(svrIP+"/32", routingTable, routingPriority) + return setupIpRule(proxyIP+"/32", config.RoutingTableID, config.RoutingTablePriority) } func stopRouting(routingTable int) { @@ -43,7 +41,7 @@ func stopRouting(routingTable int) { } } -func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP, gwIP string) error { +func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP string) error { tun, err := netlink.LinkByName(tunName) if err != nil { return fmt.Errorf("failed to find tun device '%s': %w", tunName, err) @@ -70,7 +68,7 @@ func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP, gwIP s r = netlink.Route{ LinkIndex: tun.Attrs().Index, Table: routingTable, - Gw: net.ParseIP(gwIP), + Gw: dst.IP, } if err := netlink.RouteAdd(&r); err != nil { diff --git a/x/examples/outline-cli/tun_device_posix.go b/x/examples/outline-cli/tun_device_linux.go similarity index 85% rename from x/examples/outline-cli/tun_device_posix.go rename to x/examples/outline-cli/tun_device_linux.go index d3614c44..fad20aa1 100644 --- a/x/examples/outline-cli/tun_device_posix.go +++ b/x/examples/outline-cli/tun_device_linux.go @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build linux - package main import ( "errors" "fmt" + "github.com/Jigsaw-Code/outline-sdk/network" "github.com/songgao/water" "github.com/vishvananda/netlink" ) @@ -28,20 +27,20 @@ type tunDevice struct { *water.Interface } -var _ TunDevice = (*tunDevice)(nil) +var _ network.IPDevice = (*tunDevice)(nil) -func NewTunDevice(name, ip string) (d TunDevice, err error) { - if len(name) == 0 { +func newTunDevice(config *RoutingConfig) (d network.IPDevice, err error) { + if len(config.TunDeviceName) == 0 { return nil, errors.New("name is required for TUN/TAP device") } - if len(ip) == 0 { + if len(config.TunDeviceIP) == 0 { return nil, errors.New("ip is required for TUN/TAP device") } tun, err := water.New(water.Config{ DeviceType: water.TUN, PlatformSpecificParams: water.PlatformSpecificParams{ - Name: name, + Name: config.TunDeviceName, Persist: false, }, }) @@ -56,7 +55,7 @@ func NewTunDevice(name, ip string) (d TunDevice, err error) { }() tunDev := &tunDevice{tun} - if err := tunDev.configureSubnetAndBringUp(ip); err != nil { + if err := tunDev.configureSubnetAndBringUp(config.TunDeviceIP); err != nil { return nil, fmt.Errorf("failed to configure TUN/TAP device: %w", err) } return tunDev, nil diff --git a/x/go.sum b/x/go.sum index 300f46d2..ec8a53cc 100644 --- a/x/go.sum +++ b/x/go.sum @@ -40,6 +40,7 @@ golang.org/x/mobile v0.0.0-20230905140555-fbe1c053b6a9/go.mod h1:2jxcxt/JNJik+N+ golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= From 7f5613dcb733aa258326bf16b7040b1977f972dd Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 18 Oct 2023 18:26:46 -0400 Subject: [PATCH 13/21] rename to config.NewPacketListener --- x/config/config.go | 18 ++++++++++-------- x/examples/outline-cli/README.md | 19 ++++++------------- .../outline-cli/outline_packet_proxy.go | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/x/config/config.go b/x/config/config.go index a93b8517..0dfe0a44 100644 --- a/x/config/config.go +++ b/x/config/config.go @@ -120,21 +120,23 @@ func newPacketDialerFromPart(innerDialer transport.PacketDialer, oneDialerConfig } } -// NewShadowsocksPacketListenerFromPart creates a new [transport.PacketListener] according to the given config, +// NewpacketListener 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") +func NewpacketListener(transportConfig string) (transport.PacketListener, error) { + if transportConfig = strings.TrimSpace(transportConfig); transportConfig == "" { + return nil, errors.New("config is required") + } + if strings.Contains(transportConfig, "|") { + return nil, errors.New("multi-part config is not supported") } - url, err := url.Parse(ssConfig) + url, err := url.Parse(transportConfig) if err != nil { - return nil, fmt.Errorf("failed to parse config part: %w", err) + return nil, fmt.Errorf("failed to parse config: %w", err) } - if url.Scheme != "ss" { return nil, errors.New("config scheme must be 'ss' for a PacketListener") } + return newShadowsocksPacketListenerFromURL(url) } diff --git a/x/examples/outline-cli/README.md b/x/examples/outline-cli/README.md index 80707150..e0dfb8a1 100644 --- a/x/examples/outline-cli/README.md +++ b/x/examples/outline-cli/README.md @@ -2,29 +2,22 @@ The CLI interface of OutlineVPN client for Linux. -## Usage - -#### Standard - -``` -./outline-cli -transport "ss://" -``` - -#### Advanced (with Golang) +### Usage ``` go run github.com/Jigsaw-Code/outline-sdk/x/examples/outline-cli@latest -transport "ss://" ``` -### Arguments - - `-transport` : the Outline server access key from the service provider, it should start with "ss://" -## Build (for Developers) +### Build + +You can use the following command to build the CLI. -We recommend to setup a [go workspace](https://go.dev/blog/get-familiar-with-workspaces) to build the code. Then use the following command to build the CLI (only support Linux): ``` cd outline-sdk/x/examples/ go build -o outline-cli -ldflags="-extldflags=-static" ./outline-cli ``` + +> 💡 `cgo` will pull in the C runtime. By default, the C runtime is linked as a dynamic library. Sometimes this can cause problems when running the binary on different versions or distributions of Linux. To avoid this, we have added the `-ldflags="-extldflags=-static"` option. But if you only need to run the binary on the same machine, you can omit this option. diff --git a/x/examples/outline-cli/outline_packet_proxy.go b/x/examples/outline-cli/outline_packet_proxy.go index 386ef627..d9b24a9a 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 = config.NewShadowsocksPacketListenerFromPart(transportConfig); err != nil { + if opp.remotePl, err = config.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 c7c973b23d6a0f65ca97c8755e84bb4bcdd6a73d Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 18 Oct 2023 20:02:09 -0400 Subject: [PATCH 14/21] add OutlineCLI entry to root README --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4c27ea3f..9166ee99 100644 --- a/README.md +++ b/README.md @@ -123,13 +123,12 @@ Beta features: - Integration resources - For Mobile apps - - [x] Library to run a local SOCKS5 or HTTP-Connect proxy ([source](./x/mobileproxy/mobileproxy.go), [example Go usage](./x/examples/fetch-proxy/main.go), [example mobile usage](./x/examples/mobileproxy)). (v0.0.6) - - [x] Documentation on how to integrate the SDK into mobile apps (v0.0.6) - - [x] Connectivity Test iOS mobile app using [Capacitor](https://capacitorjs.com/) - - [ ] Connectivity Test Android app using [Capacitor](https://capacitorjs.com/) (coming soon) + - [x] Library to run a local SOCKS5 or HTTP-Connect proxy ([source](./x/mobileproxy/mobileproxy.go), [example Go usage](./x/examples/fetch-proxy/main.go), [example mobile usage](./x/examples/mobileproxy)). + - [x] Documentation on how to integrate the SDK into mobile apps + - [x] Connectivity Test iOS mobile app (iOS and Android) using [Capacitor](https://capacitorjs.com/) - For Go apps - [x] Connectivity Test example [Wails](https://wails.io/) graphical app - - [x] Connectivity Test example command-line app ([source](./x/examples/outline-connectivity/)) (v0.0.6) - - [ ] Outline Client example command-line app (coming soon) - - [x] Page fetch example command-line app ([source](./x/examples/outline-fetch/)) (v0.0.6) - - [x] Local proxy example command-line app ([source](./x/examples/http2transport/)) (v0.0.6) + - [x] Connectivity Test example command-line app ([source](./x/examples/outline-connectivity/)) + - [x] Outline Client example command-line app ([source](./x/examples/outline-cli/)) + - [x] Page fetch example command-line app ([source](./x/examples/outline-fetch/)) + - [x] Local proxy example command-line app ([source](./x/examples/http2transport/)) From 50dddd8e55256e048370a80829491d2b98ed86bc Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 18 Oct 2023 20:03:01 -0400 Subject: [PATCH 15/21] remove redundant iOS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9166ee99..70f1eb1a 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Beta features: - For Mobile apps - [x] Library to run a local SOCKS5 or HTTP-Connect proxy ([source](./x/mobileproxy/mobileproxy.go), [example Go usage](./x/examples/fetch-proxy/main.go), [example mobile usage](./x/examples/mobileproxy)). - [x] Documentation on how to integrate the SDK into mobile apps - - [x] Connectivity Test iOS mobile app (iOS and Android) using [Capacitor](https://capacitorjs.com/) + - [x] Connectivity Test mobile app (iOS and Android) using [Capacitor](https://capacitorjs.com/) - For Go apps - [x] Connectivity Test example [Wails](https://wails.io/) graphical app - [x] Connectivity Test example command-line app ([source](./x/examples/outline-connectivity/)) From a9031a75cac643bd86c018c2b8bb57fbb213a81e Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 18 Oct 2023 20:04:58 -0400 Subject: [PATCH 16/21] remove redundant newline in log --- x/examples/outline-cli/app_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/examples/outline-cli/app_linux.go b/x/examples/outline-cli/app_linux.go index 23610902..858ffcfd 100644 --- a/x/examples/outline-cli/app_linux.go +++ b/x/examples/outline-cli/app_linux.go @@ -77,6 +77,6 @@ func (app App) Run() error { sigc := make(chan os.Signal, 1) signal.Notify(sigc, os.Interrupt, unix.SIGTERM, unix.SIGHUP) s := <-sigc - log.Printf("\nreceived %v, terminating...\n", s) + log.Printf("received %v, terminating...\n", s) return nil } From f57ce3e98b10bca91552c50368c2dcf155cef6f3 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Mon, 23 Oct 2023 19:08:58 -0400 Subject: [PATCH 17/21] add loggers for different levels --- x/config/config.go | 1 + x/config/shadowsocks.go | 1 + x/examples/outline-cli/README.md | 2 +- x/examples/outline-cli/app_linux.go | 9 ++-- x/examples/outline-cli/app_other.go | 5 +-- x/examples/outline-cli/dns_linux.go | 8 ++-- x/examples/outline-cli/ipv6_linux.go | 3 +- x/examples/outline-cli/main.go | 13 +++++- .../outline-cli/outline_packet_proxy.go | 5 +-- x/examples/outline-cli/routing_linux.go | 15 ++++--- x/examples/outline-cli/tun_device_linux.go | 42 +++++++++++-------- 11 files changed, 60 insertions(+), 44 deletions(-) diff --git a/x/config/config.go b/x/config/config.go index 0dfe0a44..c1821b2a 100644 --- a/x/config/config.go +++ b/x/config/config.go @@ -138,5 +138,6 @@ func NewpacketListener(transportConfig string) (transport.PacketListener, error) return nil, errors.New("config scheme must be 'ss' for a PacketListener") } + // todo: support nested dialer, the last part must be "ss://" return newShadowsocksPacketListenerFromURL(url) } diff --git a/x/config/shadowsocks.go b/x/config/shadowsocks.go index c1c74818..6b4a3ddf 100644 --- a/x/config/shadowsocks.go +++ b/x/config/shadowsocks.go @@ -60,6 +60,7 @@ func newShadowsocksPacketListenerFromURL(configURL *url.URL) (transport.PacketLi if err != nil { return nil, err } + // 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) } diff --git a/x/examples/outline-cli/README.md b/x/examples/outline-cli/README.md index e0dfb8a1..b3d2b6ba 100644 --- a/x/examples/outline-cli/README.md +++ b/x/examples/outline-cli/README.md @@ -1,6 +1,6 @@ # OutlineVPN CLI -The CLI interface of OutlineVPN client for Linux. +A CLI interface of Outline VPN client for Linux. ### Usage diff --git a/x/examples/outline-cli/app_linux.go b/x/examples/outline-cli/app_linux.go index 858ffcfd..9b35675e 100644 --- a/x/examples/outline-cli/app_linux.go +++ b/x/examples/outline-cli/app_linux.go @@ -17,7 +17,6 @@ package main import ( "fmt" "io" - "log" "os" "os/signal" "sync" @@ -30,7 +29,7 @@ func (app App) Run() error { trafficCopyWg := &sync.WaitGroup{} defer trafficCopyWg.Wait() - tun, err := newTunDevice(app.RoutingConfig) + tun, err := newTunDevice(app.RoutingConfig.TunDeviceName, app.RoutingConfig.TunDeviceIP) if err != nil { return fmt.Errorf("failed to create tun device: %w", err) } @@ -56,12 +55,12 @@ func (app App) Run() error { go func() { defer trafficCopyWg.Done() written, err := io.Copy(ss, tun) - log.Printf("[info] tun -> OutlineDevice stopped: %v %v\n", written, err) + logging.Info.Printf("tun -> OutlineDevice stopped: %v %v\n", written, err) }() go func() { defer trafficCopyWg.Done() written, err := io.Copy(tun, ss) - log.Printf("[info] OutlineDevice -> tun stopped: %v %v\n", written, err) + logging.Info.Printf("OutlineDevice -> tun stopped: %v %v\n", written, err) }() if err := setSystemDNSServer(app.RoutingConfig.DNSServerIP); err != nil { @@ -77,6 +76,6 @@ func (app App) Run() error { sigc := make(chan os.Signal, 1) signal.Notify(sigc, os.Interrupt, unix.SIGTERM, unix.SIGHUP) s := <-sigc - log.Printf("received %v, terminating...\n", s) + logging.Info.Printf("received %v, terminating...\n", s) return nil } diff --git a/x/examples/outline-cli/app_other.go b/x/examples/outline-cli/app_other.go index a7a67ba7..ca351f30 100644 --- a/x/examples/outline-cli/app_other.go +++ b/x/examples/outline-cli/app_other.go @@ -16,9 +16,8 @@ package main -import "fmt" +import "errors" func (App) Run() error { - fmt.Println("OutlineVPN does not support this platform") - return nil + return errors.New("platform not supported") } diff --git a/x/examples/outline-cli/dns_linux.go b/x/examples/outline-cli/dns_linux.go index 01f26f31..bb34aee8 100644 --- a/x/examples/outline-cli/dns_linux.go +++ b/x/examples/outline-cli/dns_linux.go @@ -16,10 +16,10 @@ package main import ( "fmt" - "log" "os" ) +// todo: find a more portable way of configuring DNS (e.g. resolved) const ( resolvConfFile = "/etc/resolv.conf" resolvConfHeadFile = "/etc/resolv.conf.head" @@ -55,12 +55,12 @@ func backupAndWriteFile(original, backup string, data []byte) error { func restoreFileIfExists(backup, original string) { if _, err := os.Stat(backup); err != nil { - log.Printf("[warn] no DNS config backup file '%s' presents: %v\n", backup, err) + logging.Warn.Printf("no DNS config backup file '%s' presents: %v\n", backup, err) return } if err := os.Rename(backup, original); err != nil { - log.Printf("[error] failed to restore DNS config from backup '%s' to '%s': %v\n", backup, original, err) + logging.Err.Printf("failed to restore DNS config from backup '%s' to '%s': %v\n", backup, original, err) return } - log.Printf("[info] DNS config restored from '%s' to '%s'\n", backup, original) + logging.Info.Printf("DNS config restored from '%s' to '%s'\n", backup, original) } diff --git a/x/examples/outline-cli/ipv6_linux.go b/x/examples/outline-cli/ipv6_linux.go index 4a02ffe9..5dd245e9 100644 --- a/x/examples/outline-cli/ipv6_linux.go +++ b/x/examples/outline-cli/ipv6_linux.go @@ -16,7 +16,6 @@ package main import ( "fmt" - "log" "os" ) @@ -45,6 +44,6 @@ func enableIPv6(enabled bool) (bool, error) { return prevEnabled, fmt.Errorf("failed to write IPv6 config: %w", err) } - log.Printf("[info] updated global IPv6 support: %v\n", enabled) + logging.Info.Printf("updated global IPv6 support: %v\n", enabled) return prevEnabled, nil } diff --git a/x/examples/outline-cli/main.go b/x/examples/outline-cli/main.go index 65c737d3..1be00964 100644 --- a/x/examples/outline-cli/main.go +++ b/x/examples/outline-cli/main.go @@ -17,9 +17,20 @@ package main import ( "flag" "fmt" + "io" "log" + "os" ) +var logging = &struct { + Debug, Info, Warn, Err *log.Logger +}{ + Debug: log.New(io.Discard, "[DEBUG] ", log.LstdFlags), + Info: log.New(os.Stdout, "[INFO] ", log.LstdFlags), + Warn: log.New(os.Stderr, "[WARN] ", log.LstdFlags), + Err: log.New(os.Stderr, "[ERROR] ", log.LstdFlags), +} + // ./app -transport "ss://..." func main() { fmt.Println("OutlineVPN CLI (experimental)") @@ -39,6 +50,6 @@ func main() { flag.Parse() if err := app.Run(); err != nil { - log.Printf("[error] %v\n", err) + logging.Err.Printf("%v\n", err) } } diff --git a/x/examples/outline-cli/outline_packet_proxy.go b/x/examples/outline-cli/outline_packet_proxy.go index d9b24a9a..4ed6be1d 100644 --- a/x/examples/outline-cli/outline_packet_proxy.go +++ b/x/examples/outline-cli/outline_packet_proxy.go @@ -17,7 +17,6 @@ package main import ( "context" "fmt" - "log" "github.com/Jigsaw-Code/outline-sdk/network" "github.com/Jigsaw-Code/outline-sdk/network/dnstruncate" @@ -58,10 +57,10 @@ func (proxy *outlinePacketProxy) testConnectivityAndRefresh(resolver, domain str _, err := connectivity.TestResolverPacketConnectivity(context.Background(), dnsResolver, domain) if err != nil { - log.Println("[info] remote server cannot handle UDP traffic, switch to DNS truncate mode") + logging.Info.Println("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") + logging.Info.Println("remote server supports UDP, we will delegate all UDP packets to it") return proxy.SetProxy(proxy.remote) } } diff --git a/x/examples/outline-cli/routing_linux.go b/x/examples/outline-cli/routing_linux.go index e6bc6b09..e690fd7c 100644 --- a/x/examples/outline-cli/routing_linux.go +++ b/x/examples/outline-cli/routing_linux.go @@ -17,7 +17,6 @@ package main import ( "errors" "fmt" - "log" "net" "github.com/vishvananda/netlink" @@ -34,10 +33,10 @@ func startRouting(proxyIP string, config *RoutingConfig) error { func stopRouting(routingTable int) { if err := cleanUpRoutingTable(routingTable); err != nil { - log.Printf("[error] failed to clean up routing table '%v': %v\n", routingTable, err) + logging.Err.Printf("failed to clean up routing table '%v': %v\n", routingTable, err) } if err := cleanUpRule(); err != nil { - log.Printf("[error] failed to clean up IP rule: %v\n", err) + logging.Err.Printf("failed to clean up IP rule: %v\n", err) } } @@ -63,7 +62,7 @@ func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP string) if err = netlink.RouteAdd(&r); err != nil { return fmt.Errorf("failed to add routing entry '%v' -> '%v': %w", r.Src, r.Dst, err) } - log.Printf("[info] routing traffic from %v to %v through nic %v\n", r.Src, r.Dst, r.LinkIndex) + logging.Info.Printf("routing traffic from %v to %v through nic %v\n", r.Src, r.Dst, r.LinkIndex) r = netlink.Route{ LinkIndex: tun.Attrs().Index, @@ -74,7 +73,7 @@ func setupRoutingTable(routingTable int, tunName, gwSubnet string, tunIP string) if err := netlink.RouteAdd(&r); err != nil { return fmt.Errorf("failed to add gateway routing entry '%v': %w", r.Gw, err) } - log.Printf("[info] routing traffic via gw %v through nic %v...\n", r.Gw, r.LinkIndex) + logging.Info.Printf("routing traffic via gw %v through nic %v...\n", r.Gw, r.LinkIndex) return nil } @@ -93,7 +92,7 @@ func cleanUpRoutingTable(routingTable int) error { } } if rtDelErr == nil { - log.Printf("[info] routing table '%v' has been cleaned up\n", routingTable) + logging.Info.Printf("routing table '%v' has been cleaned up\n", routingTable) } return rtDelErr } @@ -114,7 +113,7 @@ func setupIpRule(svrIp string, routingTable, routingPriority int) error { if err := netlink.RuleAdd(ipRule); err != nil { return fmt.Errorf("failed to add IP rule (table %v, dst %v): %w", ipRule.Table, ipRule.Dst, err) } - log.Printf("[info] ip rule 'from all not to %v via table %v' created\n", ipRule.Dst, ipRule.Table) + logging.Info.Printf("ip rule 'from all not to %v via table %v' created\n", ipRule.Dst, ipRule.Table) return nil } @@ -125,7 +124,7 @@ func cleanUpRule() error { if err := netlink.RuleDel(ipRule); err != nil { return fmt.Errorf("failed to delete IP rule of routing table '%v': %w", ipRule.Table, err) } - log.Printf("[info] ip rule of routing table '%v' deleted\n", ipRule.Table) + logging.Info.Printf("ip rule of routing table '%v' deleted\n", ipRule.Table) ipRule = nil return nil } diff --git a/x/examples/outline-cli/tun_device_linux.go b/x/examples/outline-cli/tun_device_linux.go index fad20aa1..bc2da40f 100644 --- a/x/examples/outline-cli/tun_device_linux.go +++ b/x/examples/outline-cli/tun_device_linux.go @@ -25,22 +25,23 @@ import ( type tunDevice struct { *water.Interface + link netlink.Link } var _ network.IPDevice = (*tunDevice)(nil) -func newTunDevice(config *RoutingConfig) (d network.IPDevice, err error) { - if len(config.TunDeviceName) == 0 { +func newTunDevice(name, ip string) (d network.IPDevice, err error) { + if len(name) == 0 { return nil, errors.New("name is required for TUN/TAP device") } - if len(config.TunDeviceIP) == 0 { + if len(ip) == 0 { return nil, errors.New("ip is required for TUN/TAP device") } tun, err := water.New(water.Config{ DeviceType: water.TUN, PlatformSpecificParams: water.PlatformSpecificParams{ - Name: config.TunDeviceName, + Name: name, Persist: false, }, }) @@ -54,9 +55,17 @@ func newTunDevice(config *RoutingConfig) (d network.IPDevice, err error) { } }() - tunDev := &tunDevice{tun} - if err := tunDev.configureSubnetAndBringUp(config.TunDeviceIP); err != nil { - return nil, fmt.Errorf("failed to configure TUN/TAP device: %w", err) + tunLink, err := netlink.LinkByName(name) + if err != nil { + return nil, fmt.Errorf("newly created TUN/TAP device '%s' not found: %w", name, err) + } + + tunDev := &tunDevice{tun, tunLink} + if err := tunDev.configureSubnet(ip); err != nil { + return nil, fmt.Errorf("failed to configure TUN/TAP device subnet: %w", err) + } + if err := tunDev.bringUp(); err != nil { + return nil, fmt.Errorf("failed to bring up TUN/TAP device: %w", err) } return tunDev, nil } @@ -65,22 +74,21 @@ func (d *tunDevice) MTU() int { return 1500 } -func (d *tunDevice) configureSubnetAndBringUp(ip string) error { - tunName := d.Interface.Name() - tunLink, err := netlink.LinkByName(tunName) - if err != nil { - return fmt.Errorf("TUN/TAP device '%s' not found: %w", tunName, err) - } +func (d *tunDevice) configureSubnet(ip string) error { subnet := ip + "/32" addr, err := netlink.ParseAddr(subnet) if err != nil { return fmt.Errorf("subnet address '%s' is not valid: %w", subnet, err) } - if err := netlink.AddrAdd(tunLink, addr); err != nil { - return fmt.Errorf("failed to add subnet to TUN/TAP device '%s': %w", tunName, err) + if err := netlink.AddrAdd(d.link, addr); err != nil { + return fmt.Errorf("failed to add subnet to TUN/TAP device '%s': %w", d.Interface.Name(), err) } - if err := netlink.LinkSetUp(tunLink); err != nil { - return fmt.Errorf("failed to bring TUN/TAP device '%s' up: %w", tunName, err) + return nil +} + +func (d *tunDevice) bringUp() error { + if err := netlink.LinkSetUp(d.link); err != nil { + return fmt.Errorf("failed to bring TUN/TAP device '%s' up: %w", d.Interface.Name(), err) } return nil } From f11a1c241d6a29c640ad9821d330b138f13ac7fe Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Mon, 23 Oct 2023 19:14:31 -0400 Subject: [PATCH 18/21] add todo to use fwmask --- x/examples/outline-cli/routing_linux.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x/examples/outline-cli/routing_linux.go b/x/examples/outline-cli/routing_linux.go index e690fd7c..b8124485 100644 --- a/x/examples/outline-cli/routing_linux.go +++ b/x/examples/outline-cli/routing_linux.go @@ -103,6 +103,8 @@ func setupIpRule(svrIp string, routingTable, routingPriority int) error { return fmt.Errorf("failed to parse server IP CIDR '%s': %w", svrIp, err) } + // todo: exclude server IP will cause issues when accessing services on the same server, + // use fwmask to protect the shadowsocks socket instead ipRule = netlink.NewRule() ipRule.Priority = routingPriority ipRule.Family = netlink.FAMILY_V4 From a14de64a6bcf58f93bb0dc43928b6a824e1ffcbb Mon Sep 17 00:00:00 2001 From: "J. Yi" <93548144+jyyi1@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:54:01 -0400 Subject: [PATCH 19/21] Update x/config/config.go Co-authored-by: Vinicius Fortuna --- x/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/config/config.go b/x/config/config.go index c1821b2a..943d9da1 100644 --- a/x/config/config.go +++ b/x/config/config.go @@ -138,6 +138,6 @@ func NewpacketListener(transportConfig string) (transport.PacketListener, error) return nil, errors.New("config scheme must be 'ss' for a PacketListener") } - // todo: support nested dialer, the last part must be "ss://" + // TODO: support nested dialer, the last part must be "ss://" return newShadowsocksPacketListenerFromURL(url) } From b948a5f5e89b5ad91aafa3759934cd0e2fd5cfe8 Mon Sep 17 00:00:00 2001 From: "J. Yi" <93548144+jyyi1@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:54:10 -0400 Subject: [PATCH 20/21] Update x/config/shadowsocks.go Co-authored-by: Vinicius Fortuna --- x/config/shadowsocks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/config/shadowsocks.go b/x/config/shadowsocks.go index 6b4a3ddf..effdc546 100644 --- a/x/config/shadowsocks.go +++ b/x/config/shadowsocks.go @@ -60,7 +60,7 @@ func newShadowsocksPacketListenerFromURL(configURL *url.URL) (transport.PacketLi if err != nil { return nil, err } - // todo: accept an inner dialer from the caller and pass it to UDPEndpoint + // 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) } From b9f9730920707649bf66a13af4e95029cc973430 Mon Sep 17 00:00:00 2001 From: "J. Yi" <93548144+jyyi1@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:54:18 -0400 Subject: [PATCH 21/21] Update x/examples/outline-cli/README.md Co-authored-by: Vinicius Fortuna --- x/examples/outline-cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/examples/outline-cli/README.md b/x/examples/outline-cli/README.md index b3d2b6ba..579e3b41 100644 --- a/x/examples/outline-cli/README.md +++ b/x/examples/outline-cli/README.md @@ -1,4 +1,4 @@ -# OutlineVPN CLI +# Outline VPN Command-Line Client A CLI interface of Outline VPN client for Linux.