From a22215d131e6acebd54e2d980e0b6a645ca03e43 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 17 Oct 2023 18:06:35 -0400 Subject: [PATCH] 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)