Skip to content

Commit

Permalink
Merge pull request #44 from Mythologyli/tun
Browse files Browse the repository at this point in the history
feat: support tun mode
  • Loading branch information
Mythologyli authored Nov 1, 2023
2 parents ed52b1d + 0687d44 commit e0f46cd
Show file tree
Hide file tree
Showing 54 changed files with 2,790 additions and 2,814 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ jobs:
goarch: riscv64
- goos: windows
goarch: arm64
- goos: android
goarch: arm64
# BEGIN MIPS
- goos: linux
goarch: mips64
Expand Down Expand Up @@ -93,7 +91,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ^1.19
go-version: ^1.21

- name: Get project dependencies
run: go mod download
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# get modules, if they don't change the cache can be used for faster builds
FROM golang:1.19@sha256:7ffa70183b7596e6bc1b78c132dbba9a6e05a26cd30eaa9832fecad64b83f029 AS base
FROM golang:1.21 AS base
ENV GO111MODULE=on
ENV CGO_ENABLED=0
ENV GOOS=linux
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,22 @@ $ docker compose up -d

+ `http-bind`: HTTP 代理监听地址,默认为 `:1081`。为 `""` 时不启用 HTTP 代理

+ `tun-mode`: TUN 模式(实验性)。请阅读后文中的 TUN 模式注意事项

+ `add_route`: 启用 TUN 模式时根据服务端下发配置添加路由

+ `dns-ttl`: DNS 缓存时间,默认为 `3600`

+ `disable-keep-alive`: 禁用定时保活,一般不需要加此参数

+ `zju-dns-server`: ZJU DNS 服务器地址,默认为 `10.10.0.21`

+ `secondary_dns_server`: 当使用 ZJU DNS 服务器无法解析时使用的备用 DNS 服务器,默认为 `114.114.114.114`。留空则使用系统默认 DNS,但在开启 `tun_dns_server` 时必须设置

+ `dns_server_bind`: DNS 服务器监听地址,默认为空即禁用。例如,设置为 `127.0.0.1:53`,则可向 `127.0.0.1:53` 发起 DNS 请求

+ `tun_dns_server`: 启用 TUN 模式时使用的 DNS 服务器,不带端口。例如:`127.0.0.1`。可配合 `dns_server_bind` 实现 TUN 模式下正确的 DNS 解析。目前仅支持 Windows 系统

+ `debug-dump`: 是否开启调试,一般不需要加此参数

+ `tcp-port-forwarding`: TCP 端口转发,格式为 `本地地址-远程地址,本地地址-远程地址,...`,例如 `127.0.0.1:9898-10.10.98.98:80,0.0.0.0:9899-10.10.98.98:80`。多个转发用 `,` 分隔
Expand All @@ -238,6 +248,16 @@ $ docker compose up -d

+ `config`: 指定配置文件,内容参考 `config.toml.example`。启用配置文件时其他参数无效

### TUN 模式注意事项

1. 需要管理员权限运行

2. Windows 系统需要前往 [Wintun 官网](https://www.wintun.net)下载 `wintun.dll` 并放置于可执行文件同目录下

3. Linux 和 macOS 暂不支持通过 `tun_dns_server` 自动配置系统 DNS。为保证 `*.zju.edu.cn` 解析正确,建议配置 `dns_server_bind` 并手动配置系统 DNS

4. macOS 暂不支持通过 TUN 接口访问 `10.0.0.0/8` 外的地址

### 计划表

#### 已完成
Expand All @@ -255,9 +275,13 @@ $ docker compose up -d
- [x] UDP 端口转发功能
- [x] 通过配置文件启动
- [x] 定时保活
- [x] TUN 模式

#### To Do

- [ ] 自动劫持 DNS
- [ ] Fake IP 模式

### 贡献者

<a href="https://github.com/mythologyli/zju-connect/graphs/contributors">
Expand Down
156 changes: 156 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package client

import (
"crypto/tls"
"errors"
"github.com/mythologyli/zju-connect/log"
"inet.af/netaddr"
"net"
"net/http"
"time"
)

type EasyConnectClient struct {
server string // Example: rvpn.zju.edu.cn:443. No protocol prefix
username string
password string
testMultiLine bool
parseResource bool

httpClient *http.Client

twfID string
token *[48]byte

lineList []string

ipResource *netaddr.IPSet
domainResource map[string]bool
dnsResource map[string]net.IP

ip net.IP // Client IP
ipReverse []byte
}

func NewEasyConnectClient(server, username, password, twfID string, testMultiLine, parseResource bool) *EasyConnectClient {
return &EasyConnectClient{
server: server,
username: username,
password: password,
testMultiLine: testMultiLine,
parseResource: parseResource,
httpClient: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}},
twfID: twfID,
}
}

func (c *EasyConnectClient) IP() (net.IP, error) {
if c.ip == nil {
return nil, errors.New("IP not available")
}

return c.ip, nil
}

func (c *EasyConnectClient) IPResource() (*netaddr.IPSet, error) {
if c.ipResource == nil {
return nil, errors.New("IP resource not available")
}

return c.ipResource, nil
}

func (c *EasyConnectClient) DomainResource() (map[string]bool, error) {
if c.domainResource == nil {
return nil, errors.New("domain resource not available")
}

return c.domainResource, nil
}

func (c *EasyConnectClient) DNSResource() (map[string]net.IP, error) {
if c.dnsResource == nil {
return nil, errors.New("DNS resource not available")
}

return c.dnsResource, nil
}

func (c *EasyConnectClient) Setup() error {
// Use username/password/(SMS code) to get the TwfID
if c.twfID == "" {
err := c.requestTwfID()
if err != nil {
return err
}
} // else we use the TwfID provided by user

// Then we can get config from server and find the best line
if c.testMultiLine {
configStr, err := c.requestConfig()
if err != nil {
log.Printf("Error occurred while requesting config: %v", err)
} else {
err := c.parseLineListFromConfig(configStr)
if err != nil {
log.Printf("Error occurred while parsing config: %v", err)
} else {
log.Printf("Line list: %v", c.lineList)

bestLine, err := findBestLine(c.lineList)
if err != nil {
log.Printf("Error occurred while finding best line: %v", err)
} else {
log.Printf("Best line: %v", bestLine)

// Now we use the bestLine as new server
if c.server != bestLine {
c.server = bestLine
c.testMultiLine = false
c.twfID = ""

return c.Setup()
}
}
}
}
}

// Then, use the TwfID to get token
err := c.requestToken()
if err != nil {
return err
}

startTime := time.Now()

// Then we get the resources from server
if c.parseResource {
resources, err := c.requestResources()
if err != nil {
log.Printf("Error occurred while requesting resources: %v", err)
} else {
// Parse the resources
err = c.parseResources(resources)
if err != nil {
log.Printf("Error occurred while parsing resources: %v", err)
}
}
}

// Error may occur if we request too fast
if time.Since(startTime) < time.Second {
time.Sleep(time.Second - time.Since(startTime))
}

// Finally, use the token to get client IP
err = c.requestIP()
if err != nil {
return err
}

return nil
}
53 changes: 17 additions & 36 deletions core/config/ServerList.go → client/line.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
package config
package client

import (
"log"
"errors"
"github.com/cloverstd/tcping/ping"
"strconv"
"strings"
"time"

"github.com/cloverstd/tcping/ping"
)

var serverList []string

const tcpPingNum = 3
const pingNum = 3

func AppendSingleServer(server string, debug bool) {
if debug {
log.Printf("AppendSingleServer: %s", server)
}

serverList = append(serverList, server)
}

func GetBestServer() string {
bestServer := ""
func findBestLine(lineList []string) (string, error) {
bestLine := ""
bestLatency := int64(0)

var tcpingList []ping.TCPing
var pingList []ping.TCPing
var chList []<-chan struct{}

for _, server := range serverList {
for _, server := range lineList {
parts := strings.Split(server, ":")
host := parts[0]
port, err := strconv.Atoi(parts[1])
Expand All @@ -41,13 +30,13 @@ func GetBestServer() string {
Protocol: ping.TCP,
Host: host,
Port: port,
Counter: tcpPingNum,
Counter: pingNum,
Interval: time.Duration(0.5 * float64(time.Second)),
Timeout: time.Duration(1 * float64(time.Second)),
}
tcping.SetTarget(&target)

tcpingList = append(tcpingList, *tcping)
pingList = append(pingList, *tcping)
ch := tcping.Start()
chList = append(chList, ch)
}
Expand All @@ -56,29 +45,21 @@ func GetBestServer() string {
<-ch
}

for i, tcping := range tcpingList {
for i, tcping := range pingList {
result := tcping.Result()
if result.SuccessCounter == tcpPingNum {
if result.SuccessCounter == pingNum {
latency := result.Avg().Milliseconds()

if bestLatency == 0 || latency < bestLatency {
bestServer = serverList[i]
bestLine = lineList[i]
bestLatency = latency
}
}
}

return bestServer
}

func IsServerListAvailable() bool {
return serverList != nil
}

func GetServerListLen() int {
if IsServerListAvailable() {
return len(serverList)
} else {
return 0
if bestLine == "" {
return "", errors.New("no available line")
}

return bestLine, nil
}
Loading

0 comments on commit e0f46cd

Please sign in to comment.