├── .github └── workflows │ └── release.yaml ├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── config-example.json ├── config └── config.go ├── doc ├── img.png ├── img_1.png ├── img_2.png └── img_3.png ├── go.mod ├── go.sum ├── install └── darwin.sh ├── main.go ├── netcatcher └── netcatcher.go └── route ├── route_darwin.go └── route_windows.go /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Go project 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" # triggers only if push new tag version, like `0.8.4` or else 7 | 8 | jobs: 9 | build: 10 | name: GoReleaser build 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Check out code into the Go module directory 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 # See: https://goreleaser.com/ci/actions/ 18 | 19 | - name: Set up Go 1.19 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.19 23 | id: go 24 | 25 | - name: Run GoReleaser 26 | uses: goreleaser/goreleaser-action@master 27 | with: 28 | version: latest 29 | args: release --rm-dist 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | config.json 3 | netcatcher*.gz* 4 | .DS_Store -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | env: 2 | - GO111MODULE=on 3 | 4 | before: 5 | hooks: 6 | - go mod download 7 | 8 | builds: 9 | - 10 | env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - darwin 14 | - windows 15 | goarch: 16 | - amd64 17 | - arm64 18 | - "386" 19 | 20 | checksum: 21 | name_template: 'checksums.txt' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 attson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 网络接口监听器 2 | 3 | 一个小工具,帮助你在指定的网络接口连接时,添加静态路由。帮助你在多个网络接口(或者vpn)的情况下,让特定的网络流量走指定的网络接口。 4 | 5 | ## 配置文件 6 | 7 | 支持域名、ip、网段的配置,配置文件为json格式,默认配置文件路径为`./config.json`,配置文件内容如下: 8 | 9 | 注意名称要与vpn名称相同 10 | 11 | ```json 12 | { 13 | "interfaces": [ 14 | { 15 | "name": "ppp0", 16 | "routes": [ 17 | "github.com", 18 | "192.168.188.11", 19 | "192.168.188.0/24" 20 | ] 21 | } 22 | ] 23 | } 24 | ``` 25 | 26 | 指定配置文件路径 `netcatcher -c /var/config.json` 27 | 28 | ## 快速开始 29 | 30 | **由于需要操作路由,本程序需要管理员权限** 31 | 32 | ```bash 33 | sudo ./netcatcher 34 | ## windows 用户使用管理员权限运行 35 | ``` 36 | 37 | ## macos 使用 38 | 39 | blog: https://attson.github.io/p/network-interface-auto-route.html 40 | 41 | ### 启动 netcatcher 42 | 43 | #### 手动启动方式 44 | 45 | ```bash 46 | sudo ./netcatcher 47 | ## windows 用户使用管理员权限运行 48 | ``` 49 | 50 | #### 通过 launchctl 启动 51 | 52 | 通过脚本快速配置启动服务,通过`launchctl`方式,可以配置开机自启动 53 | 54 | ``` 55 | curl -s https://raw.githubusercontent.com/attson/netcatcher/main/install/darwin.sh | NETCATCHER_VERSION=v0.2.0 NETCATCHER_OS=darwin_amd64 bash 56 | ``` 57 | 58 | ### 运行日志 59 | 60 | ```bash 61 | $ tail -f /usr/local/var/log/com.attson.netcatcher.log 62 | 2023/02/05 17:01:32 netcatcher started... 63 | 2023/02/05 17:01:32 ppp0: [info] interface status is connected 64 | add host 140.82.113.3: gateway 192.168.199.51 65 | 2023/02/05 17:01:32 ppp0: [debug] add route github.com -> 140.82.113.3 @ 192.168.199.51 66 | add host 192.168.188.11: gateway 192.168.199.51 67 | 2023/02/05 17:01:32 ppp0: [debug] add route 192.168.188.11 -> 192.168.188.11 @ 192.168.199.51 68 | add net 192.168.188.0: gateway 192.168.199.51 69 | 2023/02/05 17:01:32 ppp0: [debug] add route 192.168.188.0/24 -> 192.168.188.0/24 @ 192.168.199.5 70 | ``` 71 | 72 | ## windows 使用 73 | 74 | ### 添加vpn连接 75 | 76 | ![img.png](doc/img.png) 77 | 78 | 在网络设置中按自己的vpn配置,添加vpn连接 79 | 80 | ### 禁用全局连接 81 | 82 | 默认情况下,vpn连接会将所有流量都走vpn,这里我们需要禁用全局连接,才能按需配置路由 83 | 84 | 1. 找到vpn连接,右键属性,选择网络 85 | ![img_1.png](doc/img_1.png) 86 | 87 | 2. 禁用全局连接,ipv6和ipv4都需要禁用 88 | ![img_2.png](doc/img_2.png) 89 | 90 | 此时,所有流量都不会走vpn 91 | 92 | ### 启动 netcatcher 93 | 94 | #### 手动启动方式 95 | 96 | ```bash 97 | # 用管理员权限运行cmd,注意要在当前文件目录下配置 config.json 98 | ./netcatcher.exe 99 | ``` 100 | 101 | #### task scheduler 启动 102 | 103 | 通过脚本快速配置启动服务,通过`task scheduler`方式,可以配置开机自启动 104 | 105 | 先编写netcatcher.vbs,放到 C:\Users\Attson\Downloads\netcatcher_0.2.0_windows_386\netcatcher.vbs 通过vbs启动一个隐藏的cmd 106 | 107 | ``` 108 | set forward=WScript.CreateObject("WScript.Shell") 109 | forward.Run "taskkill /f /im netcatcher.exe",0,True 110 | forward.Run "C:\Users\Attson\Downloads\netcatcher_0.2.0_windows_386\netcatcher.exe -c C:\Users\Attson\Downloads\netcatcher_0.2.0_windows_386\config.json -l C:\Users\Attson\Downloads\netcatcher_0.2.0_windows_386\run.log",0 111 | 112 | ``` 113 | 114 | 然后通过powershell脚本,配置task scheduler(使用管理员权限运行powershell) 115 | 116 | 配置系统启动时,以及网络连接时,并使用系统权限运行 netcatcher.vbs (也可以手动在计划任务中添加) 117 | 118 | ```powershell 119 | $taskName = "netcatcher" 120 | $taskPath = "C:\Users\Attson\Downloads\netcatcher_0.2.0_windows_386\netcatcher.vbs" 121 | $action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument $taskPath 122 | $trigger = New-ScheduledTaskTrigger -AtStartup 123 | $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RunOnlyIfNetworkAvailable -DontStopOnIdleEnd 124 | Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -User "SYSTEM" 125 | ``` 126 | 127 | 128 | ### 运行日志 129 | 130 | ```bash 131 | 2023/10/13 22:04:00 以太网: 132 | 2023/10/13 22:04:00 fdb0:4c12:48f::f27/128 133 | 2023/10/13 22:04:00 fdb0:4c12:48f:0:9dbf:ceb2:c663:bac2/64 134 | 2023/10/13 22:04:00 fdb0:4c12:48f:0:104d:23ff:d96d:a364/128 135 | 2023/10/13 22:04:00 fdb0:4c12:48f:0:40a2:3839:3c82:1a5b/128 136 | 2023/10/13 22:04:00 fdb0:4c12:48f:0:4189:bca7:3776:f81b/128 137 | 2023/10/13 22:04:00 fdb0:4c12:48f:0:b9d6:f8cd:471:406/128 138 | 2023/10/13 22:04:00 fe80::b9a9:2a98:f24b:2ce1/64 139 | 2023/10/13 22:04:00 10.89.89.221/24 140 | 2023/10/13 22:04:00 xxxx: 141 | 2023/10/13 22:04:00 192.168.199.100/32 142 | 2023/10/13 22:04:00 Loopback Pseudo-Interface 1: 143 | 2023/10/13 22:04:00 ::1/128 144 | 2023/10/13 22:04:00 127.0.0.1/8 145 | 2023/10/13 22:14:18 config file: C:\Users\Attson\Downloads\netcatcher_0.2.0_windows_386\config.json 146 | 2023/10/13 22:04:00 netcatcher started... 147 | 2023/10/13 22:04:00 xxxx: [info] interface status is connected 148 | 操作完成! 149 | 2023/10/13 22:04:00 xxxx: [warn] add route fail github.com -> 20.27.177.113 @ 192.168.199.100 invalid write result 150 | 路由添加失败: 对象已存在。 151 | 152 | 2023/10/13 22:04:00 xxxx: [warn] add route fail 192.168.188.11 -> 192.168.188.11 @ 192.168.199.100 invalid write result 153 | 路由添加失败: 对象已存在。 154 | 155 | 2023/10/13 22:04:00 xxxx: [warn] add route fail 192.168.188.0/24 -> 192.168.188.0/24 @ 192.168.199.100 invalid write result 156 | 2023/10/13 22:04:10 xxxx: [info] interface status is disconnected 157 | ``` 158 | 159 | ## tested on 160 | 161 | - [x] macos 162 | - [x] windows 163 | 164 | ## 参考 165 | 166 | - [配置开机启动](https://www.arloor.com/posts/other/start-onboot-windows-macos/) 167 | - [windows route 管理](https://www.163.com/dy/article/FATGQ880053194Z5.html) 168 | - [如何解决windows vpn全局路由问题](https://superuser.com/a/198396) 169 | -------------------------------------------------------------------------------- /config-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "interfaces": [ 3 | { 4 | "name": "ppp0", 5 | "routes": [ 6 | "github.com", 7 | "192.168.188.11", 8 | "192.168.188.0/24" 9 | ] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Interface struct { 4 | Name string `json:"name"` 5 | Routes []string `json:"routes"` 6 | } 7 | 8 | type Config struct { 9 | Interfaces []Interface `json:"interfaces"` 10 | } 11 | -------------------------------------------------------------------------------- /doc/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attson/netcatcher/2b1161127dce6634dff8503f0f4184ff2ef68766/doc/img.png -------------------------------------------------------------------------------- /doc/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attson/netcatcher/2b1161127dce6634dff8503f0f4184ff2ef68766/doc/img_1.png -------------------------------------------------------------------------------- /doc/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attson/netcatcher/2b1161127dce6634dff8503f0f4184ff2ef68766/doc/img_2.png -------------------------------------------------------------------------------- /doc/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attson/netcatcher/2b1161127dce6634dff8503f0f4184ff2ef68766/doc/img_3.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module netcatcher 2 | 3 | go 1.19 4 | 5 | require golang.org/x/text v0.13.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 2 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 3 | -------------------------------------------------------------------------------- /install/darwin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version=$NETCATCHER_VERSION 4 | if [ "$version" = "" ]; then 5 | echo "please provide NETCATCHER_VERSION env" 6 | exit 1 7 | fi 8 | 9 | os=$NETCATCHER_OS 10 | if [ "$os" = "" ]; then 11 | echo "please provide NETCATCHER_OS env" 12 | exit 1 13 | fi 14 | 15 | trimVerion=${version#v} 16 | 17 | echo " 18 | get execute binary file... 19 | - wget "https://github.com/attson/netcatcher/releases/download/${version}/netcatcher_${trimVerion}_${os}.tar.gz" 20 | - mkdir "/usr/local/bin/netcatcher_${trimVerion}_${os}" 21 | - tar -zxf "netcatcher_${trimVerion}_${os}.tar.gz" -C "/usr/local/bin/netcatcher_${trimVerion}_${os}" 22 | - chmod +x "/usr/local/bin/netcatcher_${trimVerion}_${os}/netcaptcher" 23 | " 24 | 25 | wget "https://github.com/attson/netcatcher/releases/download/${version}/netcatcher_${trimVerion}_${os}.tar.gz" 26 | mkdir "/usr/local/bin/netcatcher_${trimVerion}_${os}" 27 | tar -zxf "netcatcher_${trimVerion}_${os}.tar.gz" -C "/usr/local/bin/netcatcher_${trimVerion}_${os}" 28 | chmod +x "/usr/local/bin/netcatcher_${trimVerion}_${os}/netcatcher" 29 | content=$(cat <<-END 30 | 31 | 32 | 33 | 34 | UserName 35 | root 36 | Label 37 | com.attson.netcatcher 38 | RunAtLoad 39 | 40 | KeepAlive 41 | 42 | ProcessType 43 | Background 44 | StandardOutPath 45 | /usr/local/var/log/com.attson.netcatcher.log 46 | StandardErrorPath 47 | /usr/local/var/log/com.attson.netcatcher.log 48 | ProgramArguments 49 | 50 | /usr/local/bin/netcatcher_${trimVerion}_${os}/netcatcher 51 | 52 | WorkingDirectory 53 | /usr/local/bin/netcatcher_${trimVerion}_${os} 54 | 55 | 56 | END 57 | ) 58 | 59 | echo "create launchctl plist to /Library/LaunchDaemons/com.attson.netcatcher.plist with root. maybe need ask for password" 60 | 61 | sudo bash -c "echo \"$content\" > /Library/LaunchDaemons/com.attson.netcatcher.plist" 62 | 63 | sudo launchctl unload /Library/LaunchDaemons/com.attson.netcaptcher.plist || true 64 | sudo launchctl load /Library/LaunchDaemons/com.attson.netcaptcher.plist 65 | 66 | if [ ! -f "/usr/local/bin/netcatcher_${trimVerion}_${os}/config.json" ]; then 67 | config=$(cat <<-END 68 | { 69 | "interfaces": [ 70 | { 71 | "name": "ppp0", 72 | "routes": [ 73 | "github.com", 74 | "192.168.188.11", 75 | "192.168.188.0/24" 76 | ] 77 | } 78 | ] 79 | } 80 | END 81 | ) 82 | echo "$config" > "/usr/local/bin/netcatcher_${trimVerion}_${os}/config.json" 83 | fi 84 | 85 | echo "" 86 | echo "----------- install success. enjoy it! ----------" 87 | echo "" 88 | echo "default config: don't forget update" 89 | cat "/usr/local/bin/netcatcher_${trimVerion}_${os}/config.json" 90 | echo "edit config: vim /usr/local/bin/netcatcher_${trimVerion}_${os}/config.json" 91 | echo "[notice] com.attson.netcatcher runAtLoad..." 92 | echo "start cmd: sudo launchctl load /Library/LaunchDaemons/com.attson.netcatcher.plist" 93 | echo "stop cmd: sudo launchctl unload /Library/LaunchDaemons/com.attson.netcatcher.plist" -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "log" 7 | "net" 8 | "netcatcher/config" 9 | "netcatcher/netcatcher" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | ) 14 | 15 | func waitStop() { 16 | // hook exit signal 17 | sigs := make(chan os.Signal, 1) 18 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) 19 | s := <-sigs 20 | 21 | for _, n := range netcatchers { 22 | n.Stop() 23 | } 24 | log.Printf("stop netcatcher by signal [%v]", s) 25 | 26 | os.Exit(0) 27 | } 28 | 29 | var netcatchers []*netcatcher.NetCatcher 30 | 31 | func main() { 32 | configPath := flag.String("c", "config.json", "config file path") 33 | logPath := flag.String("l", "", "log file path") 34 | flag.Parse() 35 | 36 | if *logPath != "" { 37 | open, err := os.OpenFile(*logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 38 | if err != nil { 39 | panic(err) 40 | } 41 | log.SetOutput(open) 42 | } 43 | 44 | interfaces, err := net.Interfaces() 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | for _, i := range interfaces { 50 | addrs, err := i.Addrs() 51 | if err != nil { 52 | panic(err) 53 | } 54 | log.Printf("%s: ", i.Name) 55 | for _, a := range addrs { 56 | log.Printf("\t%s", a.String()) 57 | } 58 | } 59 | 60 | log.Printf("config file: %s\n", *configPath) 61 | 62 | file, err := os.ReadFile(*configPath) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | c := config.Config{} 68 | 69 | err = json.Unmarshal(file, &c) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | for _, s := range c.Interfaces { 75 | n := netcatcher.NewNetCatcher(s) 76 | 77 | netcatchers = append(netcatchers, n) 78 | 79 | go n.Watch() 80 | } 81 | 82 | log.Printf("netcatcher started...\n") 83 | 84 | waitStop() 85 | } 86 | -------------------------------------------------------------------------------- /netcatcher/netcatcher.go: -------------------------------------------------------------------------------- 1 | package netcatcher 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "netcatcher/config" 8 | "netcatcher/route" 9 | "time" 10 | ) 11 | 12 | type Status int 13 | 14 | const ( 15 | _ Status = iota 16 | Connected 17 | DisConnected 18 | ) 19 | 20 | func (s Status) String() string { 21 | switch s { 22 | case Connected: 23 | return "connected" 24 | case DisConnected: 25 | return "disconnected" 26 | } 27 | 28 | return "unknown" 29 | } 30 | 31 | type Route struct { 32 | For string 33 | Ip string 34 | Gateway string 35 | Mask net.IPMask 36 | } 37 | 38 | func (r Route) String() string { 39 | return fmt.Sprintf("%s -> %s @ %s", r.For, r.Ip, r.Gateway) 40 | } 41 | 42 | type ChangeEvent struct { 43 | Status Status 44 | Addr net.Addr 45 | } 46 | 47 | type NetCatcher struct { 48 | config config.Interface 49 | 50 | onChange chan ChangeEvent 51 | status Status 52 | 53 | routes []Route 54 | } 55 | 56 | func NewNetCatcher(config config.Interface) *NetCatcher { 57 | return &NetCatcher{config: config, onChange: make(chan ChangeEvent)} 58 | } 59 | 60 | func (n *NetCatcher) resolveRoutes(gateway string) { 61 | n.routes = []Route{} 62 | for _, addr := range n.config.Routes { 63 | _, ipnet, err := net.ParseCIDR(addr) 64 | 65 | if err == nil { 66 | n.routes = append(n.routes, Route{ 67 | For: addr, 68 | Ip: addr, 69 | Mask: ipnet.Mask, 70 | Gateway: gateway, 71 | }) 72 | continue 73 | } 74 | 75 | if net.ParseIP(addr) != nil { 76 | n.routes = append(n.routes, Route{ 77 | For: addr, 78 | Ip: addr, 79 | Mask: nil, 80 | Gateway: gateway, 81 | }) 82 | continue 83 | } 84 | 85 | ips, err := net.LookupIP(addr) 86 | if err != nil { 87 | log.Printf("%s: [warn] lookup %s fail %v\n", n.config.Name, addr, err) 88 | } 89 | for _, ip := range ips { 90 | n.routes = append(n.routes, Route{ 91 | For: addr, 92 | Ip: ip.String(), 93 | Gateway: gateway, 94 | }) 95 | } 96 | 97 | continue 98 | } 99 | } 100 | 101 | func (n *NetCatcher) addRoutersTo(addr net.Addr) { 102 | ip, _, err := net.ParseCIDR(addr.String()) 103 | if err != nil { 104 | log.Printf("%s: [error] parse %s CIDR fail %v", n.config.Name, addr.String(), err) 105 | return 106 | } 107 | 108 | n.resolveRoutes(ip.String()) 109 | for _, r := range n.routes { 110 | err := route.AddRoute(r.Ip, r.Gateway, r.Mask) 111 | if err != nil { 112 | log.Printf("%s: [warn] add route fail %s %v", n.config.Name, r, err) 113 | } else { 114 | log.Printf("%s: [debug] add route %s", n.config.Name, r) 115 | } 116 | 117 | } 118 | } 119 | 120 | func (n *NetCatcher) clearRouters() { 121 | for _, r := range n.routes { 122 | err := route.DeleteRoute(r.Ip, r.Gateway, r.Mask) 123 | if err != nil { 124 | log.Printf("%s: [warn] delete route fail %s %v", n.config.Name, r, err) 125 | } else { 126 | log.Printf("%s: [debug] delete route %s", n.config.Name, r) 127 | } 128 | } 129 | } 130 | 131 | func (n *NetCatcher) Watch() { 132 | go func() { 133 | for { 134 | i, err := net.InterfaceByName(n.config.Name) 135 | if err != nil { 136 | if opErr, ok := err.(*net.OpError); ok { 137 | if opErr.Unwrap().Error() == "no such network interface" { 138 | n.onChange <- ChangeEvent{ 139 | Status: DisConnected, 140 | } 141 | continue 142 | } 143 | } 144 | 145 | log.Printf("%s: [warn] get interface fail %v\n", n.config.Name, err) 146 | } else { 147 | addrs, err := i.Addrs() 148 | if err != nil || len(addrs) == 0 { 149 | log.Printf("%s: [warn] get interface addr fail %v\n", n.config.Name, err) 150 | } else { 151 | n.onChange <- ChangeEvent{ 152 | Status: Connected, 153 | Addr: addrs[0], 154 | } 155 | } 156 | } 157 | 158 | time.Sleep(time.Second) 159 | } 160 | }() 161 | 162 | for { 163 | select { 164 | case event := <-n.onChange: 165 | if n.status == event.Status { 166 | break 167 | } 168 | 169 | log.Printf("%s: [info] interface status is %s\n", n.config.Name, event.Status.String()) 170 | 171 | n.status = event.Status 172 | if event.Status == Connected { 173 | n.addRoutersTo(event.Addr) 174 | } else { 175 | // when interface disconnect. the system will clean up routes 176 | // n.clearRouters() 177 | } 178 | } 179 | } 180 | } 181 | 182 | func (n *NetCatcher) Stop() { 183 | n.clearRouters() 184 | } 185 | -------------------------------------------------------------------------------- /route/route_darwin.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "os/exec" 7 | ) 8 | 9 | func AddRoute(ip, gateway string, mask net.IPMask) error { 10 | var command *exec.Cmd 11 | 12 | if mask != nil { 13 | command = exec.Command("route", "add", "-net", ip, gateway) 14 | } else { 15 | command = exec.Command("route", "add", "-host", ip, gateway) 16 | } 17 | 18 | command.Stderr = log.Writer() 19 | command.Stdout = log.Writer() 20 | 21 | return command.Run() 22 | } 23 | 24 | func DeleteRoute(ip, gateway string, mask net.IPMask) error { 25 | var command *exec.Cmd 26 | 27 | if mask != nil { 28 | command = exec.Command("route", "delete", "-net", ip, gateway) 29 | } else { 30 | command = exec.Command("route", "delete", "-host", ip, gateway) 31 | } 32 | 33 | command.Stderr = log.Writer() 34 | command.Stdout = log.Writer() 35 | 36 | return command.Run() 37 | } 38 | -------------------------------------------------------------------------------- /route/route_windows.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "golang.org/x/text/encoding/simplifiedchinese" 7 | "golang.org/x/text/transform" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net" 12 | "os/exec" 13 | ) 14 | 15 | func maskString(mask net.IPMask) string { 16 | str := "" 17 | for _, b := range mask { 18 | // byte to int 19 | str += fmt.Sprintf("%d.", b) 20 | } 21 | 22 | return str[:len(str)-1] 23 | } 24 | 25 | func AddRoute(ip, gateway string, mask net.IPMask) error { 26 | var command *exec.Cmd 27 | 28 | if mask != nil { 29 | command = exec.Command("route", "add", ip, "mask", maskString(mask), gateway) 30 | } else { 31 | command = exec.Command("route", "add", ip, "mask", "255.255.255.255", gateway) 32 | } 33 | 34 | command.Stderr = NewW(log.Writer()) 35 | command.Stdout = NewW(log.Writer()) 36 | 37 | return command.Run() 38 | } 39 | 40 | func DeleteRoute(ip, gateway string, mask net.IPMask) error { 41 | var command *exec.Cmd 42 | 43 | if mask != nil { 44 | command = exec.Command("route", "delete", ip, "mask", maskString(mask)) 45 | } else { 46 | command = exec.Command("route", "delete", ip) 47 | } 48 | 49 | command.Stderr = NewW(log.Writer()) 50 | command.Stdout = NewW(log.Writer()) 51 | 52 | return command.Run() 53 | } 54 | 55 | type GBKW struct { 56 | w io.Writer 57 | } 58 | 59 | func NewW(w io.Writer) *GBKW { 60 | return &GBKW{w: w} 61 | } 62 | 63 | func (w GBKW) Write(p []byte) (n int, err error) { 64 | gbk, err := gbkToUtf8(p) 65 | if err != nil { 66 | return 0, err 67 | } 68 | 69 | return w.w.Write(gbk) 70 | } 71 | 72 | func gbkToUtf8(s []byte) ([]byte, error) { 73 | reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder()) 74 | d, e := ioutil.ReadAll(reader) 75 | if e != nil { 76 | return nil, e 77 | } 78 | return d, nil 79 | } 80 | --------------------------------------------------------------------------------