Skip to content

Commit

Permalink
add dns setting and ipv6 setting support
Browse files Browse the repository at this point in the history
  • Loading branch information
jyyi1 committed Oct 17, 2023
1 parent 3fe4380 commit a22215d
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 155 deletions.
67 changes: 67 additions & 0 deletions x/examples/outline-cli/dns_posix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2023 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//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)
}
51 changes: 51 additions & 0 deletions x/examples/outline-cli/ipv6_posix.go
Original file line number Diff line number Diff line change
@@ -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
}
155 changes: 38 additions & 117 deletions x/examples/outline-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ package main
import (
"flag"
"fmt"
"net"
"io"
"os"
"os/signal"
"sync"

"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)

Expand All @@ -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
}
Loading

0 comments on commit a22215d

Please sign in to comment.