From 486dd5e15204772ed34f4ad56206f56c7ce90e08 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 13:44:09 +0800 Subject: [PATCH 01/28] feat: support tun mode(experimental) --- .github/workflows/build.yml | 4 +- Dockerfile | 2 +- README.md | 4 + config.toml.example | 1 + core/EasyConnectClient.go | 135 +++++++++++---------- core/dialer.go | 10 +- core/{tun_stack.go => gvisor_stack.go} | 38 +++--- core/keep_alive.go | 4 +- core/protocol.go | 158 ++++++++++++++++++++++++- core/tcp_forwarding.go | 4 +- core/tun_stack_darwin.go | 44 +++++++ core/tun_stack_linux.go | 44 +++++++ core/tun_stack_windows.go | 71 +++++++++++ core/udp_forwarding.go | 2 +- go.mod | 26 ++-- go.sum | 55 ++++++--- main.go | 3 + 17 files changed, 479 insertions(+), 126 deletions(-) rename core/{tun_stack.go => gvisor_stack.go} (63%) create mode 100644 core/tun_stack_darwin.go create mode 100644 core/tun_stack_linux.go create mode 100644 core/tun_stack_windows.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0f90bc..f86efe1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,8 +56,6 @@ jobs: goarch: riscv64 - goos: windows goarch: arm64 - - goos: android - goarch: arm64 # BEGIN MIPS - goos: linux goarch: mips64 @@ -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 diff --git a/Dockerfile b/Dockerfile index 0140952..561d7b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.md b/README.md index 65925c3..ce1649c 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,8 @@ $ docker compose up -d + `http-bind`: HTTP 代理监听地址,默认为 `:1081`。为 `""` 时不启用 HTTP 代理 ++ `tun-mode`: TUN 模式(实验性) + + `dns-ttl`: DNS 缓存时间,默认为 `3600` 秒 + `disable-keep-alive`: 禁用定时保活,一般不需要加此参数 @@ -258,6 +260,8 @@ $ docker compose up -d #### To Do +- [ ] TUN 模式 + ### 贡献者 diff --git a/config.toml.example b/config.toml.example index b1edfe8..369a4f6 100644 --- a/config.toml.example +++ b/config.toml.example @@ -13,6 +13,7 @@ socks_bind = ":1080" socks_user = "" socks_passwd = "" http_bind = ":1081" +tun_mode = false dns_ttl = 3600 disable_keep_alive = false zju_dns_server = "10.10.0.21" diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go index 37dcf90..6c7c4d6 100644 --- a/core/EasyConnectClient.go +++ b/core/EasyConnectClient.go @@ -16,7 +16,6 @@ import ( "github.com/mythologyli/zju-connect/core/config" "github.com/mythologyli/zju-connect/parser" - "gvisor.dev/gvisor/pkg/tcpip/stack" ) @@ -35,6 +34,7 @@ var SocksBind string var SocksUser string var SocksPasswd string var HttpBind string +var TunMode bool var DebugDump bool var ParseServConfig bool var ParseZjuConfig bool @@ -53,8 +53,12 @@ type EasyConnectClient struct { token *[48]byte twfId string - endpoint *EasyConnectEndpoint - ipStack *stack.Stack + // Gvisor stack + gvisorEndpoint *EasyConnectGvisorEndpoint + gvisorStack *stack.Stack + + // TUN stack + tunEndpoint *EasyConnectTunEndpoint server string username string @@ -136,74 +140,81 @@ func StartClient(host string, port int, username string, password string, twfId log.Printf("Login success, your IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) } - // Link-level endpoint used in gvisor netstack - client.endpoint = &EasyConnectEndpoint{} - client.ipStack = SetupStack(client.clientIp, client.endpoint) + if TunMode { + // Use TUN stack + client.tunEndpoint = &EasyConnectTunEndpoint{} + SetupTunStack(ip, client.tunEndpoint) - // Sangfor Easyconnect protocol - StartProtocol(client.endpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) + StartProtocolWithTun(client.tunEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) + } else { + // Use Gvisor stack + client.gvisorEndpoint = &EasyConnectGvisorEndpoint{} + client.gvisorStack = SetupGvisorStack(client.clientIp, client.gvisorEndpoint) - for _, singleForwarding := range ForwardingList { - go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) - } + StartProtocolWithStack(client.gvisorEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) - for _, customDNS := range CustomDNSList { - ipAddr := net.ParseIP(customDNS.IP) - if ipAddr == nil { - log.Printf("Custom DNS for host_name %s is invalid, SKIP", customDNS.HostName) + for _, singleForwarding := range ForwardingList { + go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) } - SetPermantDns(customDNS.HostName, ipAddr) - log.Printf("Custom DNS %s -> %s\n", customDNS.HostName, customDNS.IP) - } - dnsResolve := DnsResolve{ - remoteTCPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.Address(net.ParseIP(ZjuDnsServer).To4()), - } - - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.Address(client.clientIp), - } - - return gonet.DialUDP(client.ipStack, &bind, &addrDns, header.IPv4ProtocolNumber) + for _, customDNS := range CustomDNSList { + ipAddr := net.ParseIP(customDNS.IP) + if ipAddr == nil { + log.Printf("Custom DNS for host_name %s is invalid, SKIP", customDNS.HostName) + } + SetPermantDns(customDNS.HostName, ipAddr) + log.Printf("Custom DNS %s -> %s\n", customDNS.HostName, customDNS.IP) + } + + dnsResolve := DnsResolve{ + remoteTCPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := tcpip.FullAddress{ + NIC: defaultNIC, + Port: uint16(53), + Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), + } + + bind := tcpip.FullAddress{ + NIC: defaultNIC, + Addr: tcpip.AddrFromSlice(client.clientIp), + } + + return gonet.DialUDP(client.gvisorStack, &bind, &addrDns, header.IPv4ProtocolNumber) + }, }, - }, - remoteUDPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.Address(net.ParseIP(ZjuDnsServer).To4()), - } - return gonet.DialTCP(client.ipStack, addrDns, header.IPv4ProtocolNumber) + remoteUDPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := tcpip.FullAddress{ + NIC: defaultNIC, + Port: uint16(53), + Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), + } + return gonet.DialTCP(client.gvisorStack, addrDns, header.IPv4ProtocolNumber) + }, }, - }, - useTCP: false, - timer: nil, - } + useTCP: false, + timer: nil, + } - dialer := Dialer{ - selfIp: client.clientIp, - ipStack: client.ipStack, - } + dialer := Dialer{ + selfIp: client.clientIp, + gvisorStack: client.gvisorStack, + } - if SocksBind != "" { - go ServeSocks5(SocksBind, dialer, &dnsResolve) - } + if SocksBind != "" { + go ServeSocks5(SocksBind, dialer, &dnsResolve) + } - if HttpBind != "" { - go ServeHttp(HttpBind, dialer, &dnsResolve) - } + if HttpBind != "" { + go ServeHttp(HttpBind, dialer, &dnsResolve) + } - if EnableKeepAlive { - go KeepAlive(ZjuDnsServer, client.ipStack, client.clientIp) + if EnableKeepAlive { + go KeepAlive(ZjuDnsServer, client.gvisorStack, client.clientIp) + } } for { @@ -295,11 +306,11 @@ func (client *EasyConnectClient) ServeForwarding(networkType string, bindAddress if networkType == "tcp" { log.Printf("Port forwarding (tcp): %s <- %s", bindAddress, remoteAddress) - ServeTcpForwarding(bindAddress, remoteAddress, client.ipStack, client.clientIp) + ServeTcpForwarding(bindAddress, remoteAddress, client.gvisorStack, client.clientIp) } else if networkType == "udp" { log.Printf("Port forwarding (udp): %s <- %s", bindAddress, remoteAddress) - ServeUdpForwarding(bindAddress, remoteAddress, client.ipStack) + ServeUdpForwarding(bindAddress, remoteAddress, client.gvisorStack) } else { log.Println("Only TCP/UDP forwarding is supported yet. Aborting.") } diff --git a/core/dialer.go b/core/dialer.go index 312d987..6411bac 100644 --- a/core/dialer.go +++ b/core/dialer.go @@ -18,7 +18,7 @@ import ( type Dialer struct { selfIp []byte - ipStack *stack.Stack + gvisorStack *stack.Stack } func dialDirect(ctx context.Context, network, addr string) (net.Conn, error) { @@ -121,20 +121,20 @@ func (dialer *Dialer) DialIpAndPort(ctx context.Context, network, addr string) ( addrTarget := tcpip.FullAddress{ NIC: defaultNIC, Port: uint16(port), - Addr: tcpip.Address(target.IP), + Addr: tcpip.AddrFromSlice(target.IP), } bind := tcpip.FullAddress{ NIC: defaultNIC, - Addr: tcpip.Address(dialer.selfIp), + Addr: tcpip.AddrFromSlice(dialer.selfIp), } if network == "tcp" { log.Printf("%s -> PROXY", addr) - return gonet.DialTCPWithBind(context.Background(), dialer.ipStack, bind, addrTarget, header.IPv4ProtocolNumber) + return gonet.DialTCPWithBind(context.Background(), dialer.gvisorStack, bind, addrTarget, header.IPv4ProtocolNumber) } else if network == "udp" { log.Printf("%s -> PROXY", addr) - return gonet.DialUDP(dialer.ipStack, &bind, &addrTarget, header.IPv4ProtocolNumber) + return gonet.DialUDP(dialer.gvisorStack, &bind, &addrTarget, header.IPv4ProtocolNumber) } else { log.Printf("Proxy only support TCP/UDP. Connection to %s will use direct connection.", addr) return dialDirect(ctx, network, addr) diff --git a/core/tun_stack.go b/core/gvisor_stack.go similarity index 63% rename from core/tun_stack.go rename to core/gvisor_stack.go index acee623..1564aaf 100644 --- a/core/tun_stack.go +++ b/core/gvisor_stack.go @@ -1,7 +1,7 @@ package core import ( - "gvisor.dev/gvisor/pkg/bufferv2" + "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" @@ -14,44 +14,48 @@ const defaultNIC tcpip.NICID = 1 const defaultMTU uint32 = 1400 // implements LinkEndpoint -type EasyConnectEndpoint struct { +type EasyConnectGvisorEndpoint struct { dispatcher stack.NetworkDispatcher OnRecv func(buf []byte) } -func (ep *EasyConnectEndpoint) MTU() uint32 { +func (ep *EasyConnectGvisorEndpoint) ParseHeader(ptr stack.PacketBufferPtr) bool { + return true +} + +func (ep *EasyConnectGvisorEndpoint) MTU() uint32 { return defaultMTU } -func (ep *EasyConnectEndpoint) MaxHeaderLength() uint16 { +func (ep *EasyConnectGvisorEndpoint) MaxHeaderLength() uint16 { return 0 } -func (ep *EasyConnectEndpoint) LinkAddress() tcpip.LinkAddress { +func (ep *EasyConnectGvisorEndpoint) LinkAddress() tcpip.LinkAddress { return "" } -func (ep *EasyConnectEndpoint) Capabilities() stack.LinkEndpointCapabilities { +func (ep *EasyConnectGvisorEndpoint) Capabilities() stack.LinkEndpointCapabilities { return stack.CapabilityNone } -func (ep *EasyConnectEndpoint) Attach(dispatcher stack.NetworkDispatcher) { +func (ep *EasyConnectGvisorEndpoint) Attach(dispatcher stack.NetworkDispatcher) { ep.dispatcher = dispatcher } -func (ep *EasyConnectEndpoint) IsAttached() bool { +func (ep *EasyConnectGvisorEndpoint) IsAttached() bool { return ep.dispatcher != nil } -func (ep *EasyConnectEndpoint) Wait() {} +func (ep *EasyConnectGvisorEndpoint) Wait() {} -func (ep *EasyConnectEndpoint) ARPHardwareType() header.ARPHardwareType { +func (ep *EasyConnectGvisorEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone } -func (ep *EasyConnectEndpoint) AddHeader(stack.PacketBufferPtr) {} +func (ep *EasyConnectGvisorEndpoint) AddHeader(stack.PacketBufferPtr) {} -func (ep *EasyConnectEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { +func (ep *EasyConnectGvisorEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { for _, packetBuffer := range list.AsSlice() { var buf []byte for _, t := range packetBuffer.AsSlices() { @@ -65,17 +69,17 @@ func (ep *EasyConnectEndpoint) WritePackets(list stack.PacketBufferList) (int, t return list.Len(), nil } -func (ep *EasyConnectEndpoint) WriteTo(buf []byte) { +func (ep *EasyConnectGvisorEndpoint) WriteTo(buf []byte) { if ep.IsAttached() { packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Payload: bufferv2.MakeWithData(buf), + Payload: buffer.MakeWithData(buf), }) ep.dispatcher.DeliverNetworkPacket(header.IPv4ProtocolNumber, packetBuffer) packetBuffer.DecRef() } } -func SetupStack(ip []byte, endpoint *EasyConnectEndpoint) *stack.Stack { +func SetupGvisorStack(ip []byte, endpoint *EasyConnectGvisorEndpoint) *stack.Stack { // init IP stack ipStack := stack.New(stack.Options{ @@ -84,14 +88,14 @@ func SetupStack(ip []byte, endpoint *EasyConnectEndpoint) *stack.Stack { HandleLocal: true, }) - // create NIC associated to the endpoint + // create NIC associated to the gvisorEndpoint err := ipStack.CreateNIC(defaultNIC, endpoint) if err != nil { panic(err) } // assign ip - addr := tcpip.Address(ip) + addr := tcpip.AddrFromSlice(ip) protoAddr := tcpip.ProtocolAddress{ AddressWithPrefix: tcpip.AddressWithPrefix{ Address: addr, diff --git a/core/keep_alive.go b/core/keep_alive.go index d57cd6a..1418dce 100644 --- a/core/keep_alive.go +++ b/core/keep_alive.go @@ -18,12 +18,12 @@ func KeepAlive(dnsServer string, ipStack *stack.Stack, selfIp []byte) { addrDns := tcpip.FullAddress{ NIC: defaultNIC, Port: uint16(53), - Addr: tcpip.Address(net.ParseIP(dnsServer).To4()), + Addr: tcpip.AddrFromSlice(net.ParseIP(dnsServer).To4()), } bind := tcpip.FullAddress{ NIC: defaultNIC, - Addr: tcpip.Address(selfIp), + Addr: tcpip.AddrFromSlice(selfIp), } return gonet.DialUDP(ipStack, &bind, &addrDns, header.IPv4ProtocolNumber) diff --git a/core/protocol.go b/core/protocol.go index 879813f..7f48329 100644 --- a/core/protocol.go +++ b/core/protocol.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/hex" "errors" + "golang.org/x/net/ipv4" "io" "log" "net" @@ -112,7 +113,7 @@ func QueryIp(server string, token *[48]byte, debugDump bool) ([]byte, *tls.UConn return reply[4:8], conn, nil } -func BlockRXStream(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectEndpoint, debug bool) error { +func BlockRXStreamWithStack(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { conn, err := TLSConn(server) if err != nil { panic(err) @@ -164,7 +165,7 @@ func BlockRXStream(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConne } } -func BlockTXStream(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectEndpoint, debug bool) error { +func BlockTXStreamWithStack(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { conn, err := TLSConn(server) if err != nil { return err @@ -218,11 +219,11 @@ func BlockTXStream(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConne return <-errCh } -func StartProtocol(endpoint *EasyConnectEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { +func StartProtocolWithStack(endpoint *EasyConnectGvisorEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { RX := func() { counter := 0 for counter < 5 { - err := BlockRXStream(server, token, ipRev, endpoint, debug) + err := BlockRXStreamWithStack(server, token, ipRev, endpoint, debug) if err != nil { log.Print("Error occurred while receiving, retrying: " + err.Error()) } @@ -236,7 +237,154 @@ func StartProtocol(endpoint *EasyConnectEndpoint, server string, token *[48]byte TX := func() { counter := 0 for counter < 5 { - err := BlockTXStream(server, token, ipRev, endpoint, debug) + err := BlockTXStreamWithStack(server, token, ipRev, endpoint, debug) + if err != nil { + log.Print("Error occurred while send, retrying: " + err.Error()) + } + counter += 1 + } + panic("send retry limit exceeded.") + } + + go TX() +} + +func BlockRXStreamWithTun(server string, token *[48]byte, ipRev *[4]byte, endpoint *EasyConnectTunEndpoint, debug bool) error { + conn, err := TLSConn(server) + if err != nil { + panic(err) + } + defer conn.Close() + + // RECV STREAM START + message := []byte{0x06, 0x00, 0x00, 0x00} + message = append(message, token[:]...) + message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) + message = append(message, ipRev[:]...) + + n, err := conn.Write(message) + if err != nil { + return err + } + if debug { + log.Printf("recv handshake: wrote %d bytes", n) + DumpHex(message[:n]) + } + + reply := make([]byte, 1500) + n, err = conn.Read(reply) + if err != nil { + return err + } + if debug { + log.Printf("recv handshake: read %d bytes", n) + DumpHex(reply[:n]) + } + + if reply[0] != 0x01 { + return errors.New("unexpected recv handshake reply") + } + + for { + n, err = conn.Read(reply) + if err != nil { + return err + } + + err = endpoint.Write(reply[:n]) + if err != nil { + panic(err) + } + + if debug { + log.Printf("recv: read %d bytes", n) + DumpHex(reply[:n]) + } + } +} + +func BlockTXStreamWithTun(server string, token *[48]byte, ipRev *[4]byte, endpoint *EasyConnectTunEndpoint, debug bool) error { + conn, err := TLSConn(server) + if err != nil { + return err + } + defer conn.Close() + + // SEND STREAM START + message := []byte{0x05, 0x00, 0x00, 0x00} + message = append(message, token[:]...) + message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) + message = append(message, ipRev[:]...) + + n, err := conn.Write(message) + if err != nil { + return err + } + if debug { + log.Printf("send handshake: wrote %d bytes", n) + DumpHex(message[:n]) + } + + reply := make([]byte, 1500) + n, err = conn.Read(reply) + if err != nil { + return err + } + if debug { + log.Printf("send handshake: read %d bytes", n) + DumpHex(reply[:n]) + } + + if reply[0] != 0x02 { + return errors.New("unexpected send handshake reply") + } + + for { + n, err := endpoint.Read(reply) + if err != nil { + return err + } + + header, err := ipv4.ParseHeader(reply[:n]) + if err != nil { + continue + } + + if header.Protocol != 6 && header.Protocol != 17 { + continue + } + + n, err = conn.Write(reply[:n]) + if err != nil { + return err + } + + if debug { + log.Printf("send: wrote %d bytes", n) + DumpHex(reply[:n]) + } + } +} + +func StartProtocolWithTun(endpoint *EasyConnectTunEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { + RX := func() { + counter := 0 + for counter < 50 { + err := BlockRXStreamWithTun(server, token, ipRev, endpoint, debug) + if err != nil { + log.Print("Error occurred while receiving, retrying: " + err.Error()) + } + counter += 1 + } + panic("receive retry limit exceeded.") + } + + go RX() + + TX := func() { + counter := 0 + for counter < 50 { + err := BlockTXStreamWithTun(server, token, ipRev, endpoint, debug) if err != nil { log.Print("Error occurred while send, retrying: " + err.Error()) } diff --git a/core/tcp_forwarding.go b/core/tcp_forwarding.go index 935fc39..0998923 100644 --- a/core/tcp_forwarding.go +++ b/core/tcp_forwarding.go @@ -43,12 +43,12 @@ func handleRequest(conn net.Conn, remoteAddress string, ipStack *stack.Stack, se addrTarget := tcpip.FullAddress{ NIC: defaultNIC, Port: uint16(port), - Addr: tcpip.Address(net.ParseIP(host).To4()), + Addr: tcpip.AddrFromSlice(net.ParseIP(host).To4()), } bind := tcpip.FullAddress{ NIC: defaultNIC, - Addr: tcpip.Address(selfIp), + Addr: tcpip.AddrFromSlice(selfIp), } proxy, err := gonet.DialTCPWithBind(context.Background(), ipStack, bind, addrTarget, header.IPv4ProtocolNumber) diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go new file mode 100644 index 0000000..90170e6 --- /dev/null +++ b/core/tun_stack_darwin.go @@ -0,0 +1,44 @@ +package core + +import ( + "golang.zx2c4.com/wireguard/tun" +) + +type EasyConnectTunEndpoint struct { + dev tun.Device +} + +func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { + bufs := [][]byte{buf} + + _, err := ep.dev.Write(bufs, 0) + if err != nil { + return err + } + + return nil +} + +func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { + bufs := make([][]byte, 1) + for i := range bufs { + bufs[i] = make([]byte, 1500) + } + + sizes := make([]int, 1) + + _, err := ep.dev.Read(bufs, sizes, 0) + if err != nil { + return 0, err + } + + copy(buf, bufs[0][:sizes[0]]) + + return sizes[0], nil +} + +func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { + tun.CreateTUN("zjuconnect", 0) + + endpoint.dev = dev +} diff --git a/core/tun_stack_linux.go b/core/tun_stack_linux.go new file mode 100644 index 0000000..90170e6 --- /dev/null +++ b/core/tun_stack_linux.go @@ -0,0 +1,44 @@ +package core + +import ( + "golang.zx2c4.com/wireguard/tun" +) + +type EasyConnectTunEndpoint struct { + dev tun.Device +} + +func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { + bufs := [][]byte{buf} + + _, err := ep.dev.Write(bufs, 0) + if err != nil { + return err + } + + return nil +} + +func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { + bufs := make([][]byte, 1) + for i := range bufs { + bufs[i] = make([]byte, 1500) + } + + sizes := make([]int, 1) + + _, err := ep.dev.Read(bufs, sizes, 0) + if err != nil { + return 0, err + } + + copy(buf, bufs[0][:sizes[0]]) + + return sizes[0], nil +} + +func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { + tun.CreateTUN("zjuconnect", 0) + + endpoint.dev = dev +} diff --git a/core/tun_stack_windows.go b/core/tun_stack_windows.go new file mode 100644 index 0000000..27668b6 --- /dev/null +++ b/core/tun_stack_windows.go @@ -0,0 +1,71 @@ +package core + +import ( + "fmt" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" + "log" + "net/netip" +) + +type EasyConnectTunEndpoint struct { + dev tun.Device +} + +func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { + bufs := [][]byte{buf} + + _, err := ep.dev.Write(bufs, 0) + if err != nil { + return err + } + + return nil +} + +func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { + bufs := make([][]byte, 1) + for i := range bufs { + bufs[i] = make([]byte, 1500) + } + + sizes := make([]int, 1) + + _, err := ep.dev.Read(bufs, sizes, 0) + if err != nil { + return 0, err + } + + copy(buf, bufs[0][:sizes[0]]) + + return sizes[0], nil +} + +func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { + guid, err := windows.GUIDFromString("{4F5CDE94-D2A3-4AA5-A4A3-0FE6CB909E83}") + if err != nil { + panic(err) + } + + dev, err := tun.CreateTUNWithRequestedGUID("ZJU Connect", &guid, 1400) + if err != nil { + panic(err) + } + + endpoint.dev = dev + + nativeTunDevice := dev.(*tun.NativeTun) + + link := winipcfg.LUID(nativeTunDevice.LUID()) + + prefix, err := netip.ParsePrefix(fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3])) + if err != nil { + log.Printf("ParsePrefix failed: %v", err) + } + + err = link.SetIPAddresses([]netip.Prefix{prefix}) + if err != nil { + log.Printf("SetIPAddresses failed: %v", err) + } +} diff --git a/core/udp_forwarding.go b/core/udp_forwarding.go index af86f5a..5751cc5 100644 --- a/core/udp_forwarding.go +++ b/core/udp_forwarding.go @@ -72,7 +72,7 @@ func newUdpForward(src, dest string, ipStack *stack.Stack) *udpForward { u.dest = &tcpip.FullAddress{ NIC: defaultNIC, Port: uint16(port), - Addr: tcpip.Address(net.ParseIP(host).To4()), + Addr: tcpip.AddrFromSlice(net.ParseIP(host).To4()), } u.listenerConn, err = net.ListenUDP("udp", u.src) diff --git a/go.mod b/go.mod index 10145ef..079b561 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,33 @@ module github.com/mythologyli/zju-connect -go 1.19 +go 1.21 -require github.com/refraction-networking/utls v1.3.1 +require github.com/refraction-networking/utls v1.5.4 require ( - github.com/BurntSushi/toml v1.2.1 + github.com/BurntSushi/toml v1.3.2 github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89 github.com/cloverstd/tcping v0.1.1 github.com/cornelk/hashmap v1.0.8 - github.com/dlclark/regexp2 v1.8.1 + github.com/dlclark/regexp2 v1.10.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/things-go/go-socks5 v0.0.4 - golang.org/x/net v0.15.0 - gvisor.dev/gvisor v0.0.0-20230404014940-87523d4f2f99 + golang.org/x/net v0.17.0 + golang.org/x/sys v0.13.0 + golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb + golang.zx2c4.com/wireguard/windows v0.5.3 + gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 ) require ( - github.com/andybalholm/brotli v1.0.5 // indirect + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/cloudflare/circl v1.3.5 // indirect + github.com/gaukas/godicttls v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/klauspost/compress v1.16.3 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/sys v0.12.0 // indirect + github.com/klauspost/compress v1.17.1 // indirect + github.com/quic-go/quic-go v0.39.1 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect ) diff --git a/go.sum b/go.sum index 34bd917..e3567aa 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,59 @@ -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89 h1:2pkAuIM8OF1fy4ToFpMnI4oE+VeUNRbGrpSLKshK0oQ= github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89/go.mod h1:/09nEjna1UMoasyyQDhOrIn8hi2v2kiJglPWed1idck= +github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo= +github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloverstd/tcping v0.1.1 h1:3Yp9nvSDI7Z63zoVQDJzVk1PUczrF9tJoOrKGV30iOk= github.com/cloverstd/tcping v0.1.1/go.mod h1:NYXTrTDwlwuOKQ0vwksUVUbIr0sxDDsf1J6aFpScCBo= github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc= github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= -github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= +github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= -github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= +github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/refraction-networking/utls v1.3.1 h1:3zVomUqx7nCmyGuU/6kYA/jp5NcqX8KQSGko8pY5Ch4= -github.com/refraction-networking/utls v1.3.1/go.mod h1:kHXvVB66a4BzVRYC4Em7e1HAfp7uwOCCw0+2CZ3sMY8= +github.com/quic-go/quic-go v0.39.1 h1:d/m3oaN/SD2c+f7/yEjZxe2zEVotXprnrCCJ2y/ZZFE= +github.com/quic-go/quic-go v0.39.1/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q= +github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o= +github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0= github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic= +golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gvisor.dev/gvisor v0.0.0-20230404014940-87523d4f2f99 h1:2vpjufMeScKbdWjhdOeP9SR3z0HygM7M9CRQN7OGkcU= -gvisor.dev/gvisor v0.0.0-20230404014940-87523d4f2f99/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q= +gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs= +gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8= diff --git a/main.go b/main.go index f36ecad..20eb7d9 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ type ( SocksUser *string `toml:"socks_user"` SocksPasswd *string `toml:"socks_passwd"` HttpBind *string `toml:"http_bind"` + TunMode *bool `toml:"tun_mode"` DnsTTL *uint64 `toml:"dns_ttl"` DisableKeepAlive *bool `toml:"disable_keep_alive"` ZjuDnsServer *string `toml:"zju_dns_server"` @@ -76,6 +77,7 @@ func main() { flag.StringVar(&core.SocksUser, "socks-user", "", "SOCKS5 username, default is don't use auth") flag.StringVar(&core.SocksPasswd, "socks-passwd", "", "SOCKS5 password, default is don't use auth") flag.StringVar(&core.HttpBind, "http-bind", ":1081", "The address HTTP server listens on (e.g. 127.0.0.1:1081)") + flag.BoolVar(&core.TunMode, "tun-mode", false, "Enable TUN mode (experimental)") flag.Uint64Var(&core.DnsTTL, "dns-ttl", 3600, "DNS record time to live, unit is second") flag.BoolVar(&core.DebugDump, "debug-dump", false, "Enable traffic debug dump (only for debug usage)") flag.StringVar(&tcpPortForwarding, "tcp-port-forwarding", "", "TCP port forwarding (e.g. 0.0.0.0:9898-10.10.98.98:80,127.0.0.1:9899-10.10.98.98:80)") @@ -115,6 +117,7 @@ func main() { core.SocksUser = getTomlVal(conf.SocksUser, "") core.SocksPasswd = getTomlVal(conf.SocksPasswd, "") core.HttpBind = getTomlVal(conf.HttpBind, ":1081") + core.TunMode = getTomlVal(conf.TunMode, false) core.DnsTTL = getTomlVal(conf.DnsTTL, uint64(3600)) core.DebugDump = getTomlVal(conf.DebugDump, false) core.EnableKeepAlive = !getTomlVal(conf.DisableKeepAlive, false) From e4d1265b2b8bcc794dbde20382dade6e393cd65b Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 13:49:22 +0800 Subject: [PATCH 02/28] fix: setup tun stack in linux/darwin --- core/tun_stack_darwin.go | 5 ++++- core/tun_stack_linux.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go index 90170e6..f3175de 100644 --- a/core/tun_stack_darwin.go +++ b/core/tun_stack_darwin.go @@ -38,7 +38,10 @@ func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { } func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { - tun.CreateTUN("zjuconnect", 0) + dev, err := tun.CreateTUN("zjuconnect", 1400) + if err != nil { + panic(err) + } endpoint.dev = dev } diff --git a/core/tun_stack_linux.go b/core/tun_stack_linux.go index 90170e6..f3175de 100644 --- a/core/tun_stack_linux.go +++ b/core/tun_stack_linux.go @@ -38,7 +38,10 @@ func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { } func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { - tun.CreateTUN("zjuconnect", 0) + dev, err := tun.CreateTUN("zjuconnect", 1400) + if err != nil { + panic(err) + } endpoint.dev = dev } From cdc95b29d4d5c741a598e5583e749b59b6153ecb Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 14:53:10 +0800 Subject: [PATCH 03/28] feat: setup ip address in linux/darwin --- core/tun_stack_darwin.go | 45 +++++++++++++++++----------------------- core/tun_stack_linux.go | 45 +++++++++++++++++----------------------- go.mod | 1 + go.sum | 13 ++++++++++++ 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go index f3175de..c73e156 100644 --- a/core/tun_stack_darwin.go +++ b/core/tun_stack_darwin.go @@ -1,47 +1,40 @@ package core import ( - "golang.zx2c4.com/wireguard/tun" + "fmt" + "github.com/songgao/water" + "log" + "os/exec" ) type EasyConnectTunEndpoint struct { - dev tun.Device + ifce *water.Interface } func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { - bufs := [][]byte{buf} - - _, err := ep.dev.Write(bufs, 0) - if err != nil { - return err - } - - return nil + _, err := ep.ifce.Write(buf) + return err } func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { - bufs := make([][]byte, 1) - for i := range bufs { - bufs[i] = make([]byte, 1500) - } - - sizes := make([]int, 1) + return ep.ifce.Read(buf) +} - _, err := ep.dev.Read(bufs, sizes, 0) +func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { + ifce, err := water.New(water.Config{ + DeviceType: water.TUN, + }) if err != nil { - return 0, err + log.Fatal(err) } - copy(buf, bufs[0][:sizes[0]]) + log.Printf("Interface Name: %s\n", ifce.Name()) - return sizes[0], nil -} + endpoint.ifce = ifce -func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { - dev, err := tun.CreateTUN("zjuconnect", 1400) + cmd := exec.Command("/sbin/ifconfig", ifce.Name(), fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "up") + err = cmd.Run() if err != nil { - panic(err) + log.Printf("Run ifconfig failed: %v", err) } - - endpoint.dev = dev } diff --git a/core/tun_stack_linux.go b/core/tun_stack_linux.go index f3175de..c73e156 100644 --- a/core/tun_stack_linux.go +++ b/core/tun_stack_linux.go @@ -1,47 +1,40 @@ package core import ( - "golang.zx2c4.com/wireguard/tun" + "fmt" + "github.com/songgao/water" + "log" + "os/exec" ) type EasyConnectTunEndpoint struct { - dev tun.Device + ifce *water.Interface } func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { - bufs := [][]byte{buf} - - _, err := ep.dev.Write(bufs, 0) - if err != nil { - return err - } - - return nil + _, err := ep.ifce.Write(buf) + return err } func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { - bufs := make([][]byte, 1) - for i := range bufs { - bufs[i] = make([]byte, 1500) - } - - sizes := make([]int, 1) + return ep.ifce.Read(buf) +} - _, err := ep.dev.Read(bufs, sizes, 0) +func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { + ifce, err := water.New(water.Config{ + DeviceType: water.TUN, + }) if err != nil { - return 0, err + log.Fatal(err) } - copy(buf, bufs[0][:sizes[0]]) + log.Printf("Interface Name: %s\n", ifce.Name()) - return sizes[0], nil -} + endpoint.ifce = ifce -func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { - dev, err := tun.CreateTUN("zjuconnect", 1400) + cmd := exec.Command("/sbin/ifconfig", ifce.Name(), fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "up") + err = cmd.Run() if err != nil { - panic(err) + log.Printf("Run ifconfig failed: %v", err) } - - endpoint.dev = dev } diff --git a/go.mod b/go.mod index 079b561..a99a61e 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/cornelk/hashmap v1.0.8 github.com/dlclark/regexp2 v1.10.0 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/things-go/go-socks5 v0.0.4 golang.org/x/net v0.17.0 golang.org/x/sys v0.13.0 diff --git a/go.sum b/go.sum index e3567aa..05700d0 100644 --- a/go.sum +++ b/go.sum @@ -11,28 +11,39 @@ github.com/cloverstd/tcping v0.1.1/go.mod h1:NYXTrTDwlwuOKQ0vwksUVUbIr0sxDDsf1J6 github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc= github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k= 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/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/quic-go v0.39.1 h1:d/m3oaN/SD2c+f7/yEjZxe2zEVotXprnrCCJ2y/ZZFE= github.com/quic-go/quic-go v0.39.1/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q= github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o= github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= +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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0= github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= @@ -48,6 +59,7 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic= @@ -55,5 +67,6 @@ golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQX golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs= gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8= From e83d8304355d018d8bd71c44905851b41f047292 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 16:34:52 +0800 Subject: [PATCH 04/28] feat: port forwarding in tun mode --- core/EasyConnectClient.go | 16 +-- core/protocol.go | 10 +- core/tcp_forwarding.go | 54 ++++++++-- core/udp_forwarding.go | 221 +++++++++++++++++++++++++++++++------- 4 files changed, 244 insertions(+), 57 deletions(-) diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go index 6c7c4d6..501eb53 100644 --- a/core/EasyConnectClient.go +++ b/core/EasyConnectClient.go @@ -151,11 +151,7 @@ func StartClient(host string, port int, username string, password string, twfId client.gvisorEndpoint = &EasyConnectGvisorEndpoint{} client.gvisorStack = SetupGvisorStack(client.clientIp, client.gvisorEndpoint) - StartProtocolWithStack(client.gvisorEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) - - for _, singleForwarding := range ForwardingList { - go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) - } + StartProtocolWithGvisor(client.gvisorEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) for _, customDNS := range CustomDNSList { ipAddr := net.ParseIP(customDNS.IP) @@ -217,6 +213,10 @@ func StartClient(host string, port int, username string, password string, twfId } } + for _, singleForwarding := range ForwardingList { + go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) + } + for { runtime.KeepAlive(client) time.Sleep(time.Second * 10) @@ -293,7 +293,7 @@ func (client *EasyConnectClient) ParseAllConfig() { func (client *EasyConnectClient) GetClientIp() ([]byte, error) { var err error - // Query IP (keep the connection used, so it's not closed too early, otherwise i/o stream will be closed) + // Query IP (keep the gvisorConnection used, so it's not closed too early, otherwise i/o stream will be closed) client.clientIp, client.queryConn, err = QueryIp(client.server, client.token, DebugDump) if err != nil { return nil, err @@ -306,11 +306,11 @@ func (client *EasyConnectClient) ServeForwarding(networkType string, bindAddress if networkType == "tcp" { log.Printf("Port forwarding (tcp): %s <- %s", bindAddress, remoteAddress) - ServeTcpForwarding(bindAddress, remoteAddress, client.gvisorStack, client.clientIp) + ServeTcpForwarding(bindAddress, remoteAddress, client) } else if networkType == "udp" { log.Printf("Port forwarding (udp): %s <- %s", bindAddress, remoteAddress) - ServeUdpForwarding(bindAddress, remoteAddress, client.gvisorStack) + ServeUdpForwarding(bindAddress, remoteAddress, client) } else { log.Println("Only TCP/UDP forwarding is supported yet. Aborting.") } diff --git a/core/protocol.go b/core/protocol.go index 7f48329..0b64a4c 100644 --- a/core/protocol.go +++ b/core/protocol.go @@ -113,7 +113,7 @@ func QueryIp(server string, token *[48]byte, debugDump bool) ([]byte, *tls.UConn return reply[4:8], conn, nil } -func BlockRXStreamWithStack(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { +func BlockRXStreamWithGvisor(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { conn, err := TLSConn(server) if err != nil { panic(err) @@ -165,7 +165,7 @@ func BlockRXStreamWithStack(server string, token *[48]byte, ipRev *[4]byte, ep * } } -func BlockTXStreamWithStack(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { +func BlockTXStreamWithGvisor(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { conn, err := TLSConn(server) if err != nil { return err @@ -219,11 +219,11 @@ func BlockTXStreamWithStack(server string, token *[48]byte, ipRev *[4]byte, ep * return <-errCh } -func StartProtocolWithStack(endpoint *EasyConnectGvisorEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { +func StartProtocolWithGvisor(endpoint *EasyConnectGvisorEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { RX := func() { counter := 0 for counter < 5 { - err := BlockRXStreamWithStack(server, token, ipRev, endpoint, debug) + err := BlockRXStreamWithGvisor(server, token, ipRev, endpoint, debug) if err != nil { log.Print("Error occurred while receiving, retrying: " + err.Error()) } @@ -237,7 +237,7 @@ func StartProtocolWithStack(endpoint *EasyConnectGvisorEndpoint, server string, TX := func() { counter := 0 for counter < 5 { - err := BlockTXStreamWithStack(server, token, ipRev, endpoint, debug) + err := BlockTXStreamWithGvisor(server, token, ipRev, endpoint, debug) if err != nil { log.Print("Error occurred while send, retrying: " + err.Error()) } diff --git a/core/tcp_forwarding.go b/core/tcp_forwarding.go index 0998923..b894365 100644 --- a/core/tcp_forwarding.go +++ b/core/tcp_forwarding.go @@ -13,24 +13,35 @@ import ( "strings" ) -func ServeTcpForwarding(bindAddress string, remoteAddress string, ipStack *stack.Stack, selfIp []byte) { +func ServeTcpForwarding(bindAddress string, remoteAddress string, client *EasyConnectClient) { ln, err := net.Listen("tcp", bindAddress) if err != nil { panic(err) } - for { - conn, err := ln.Accept() - if err != nil { - panic(err) + if TunMode { + for { + conn, err := ln.Accept() + if err != nil { + panic(err) + } + + go handleRequestWithTun(conn, remoteAddress, client.clientIp) } + } else { + for { + conn, err := ln.Accept() + if err != nil { + panic(err) + } - go handleRequest(conn, remoteAddress, ipStack, selfIp) + go handleRequestWithGvisor(conn, remoteAddress, client.gvisorStack, client.clientIp) + } } } -func handleRequest(conn net.Conn, remoteAddress string, ipStack *stack.Stack, selfIp []byte) { +func handleRequestWithGvisor(conn net.Conn, remoteAddress string, ipStack *stack.Stack, selfIp []byte) { log.Printf("Port forwarding (tcp): %s -> %s -> %s", conn.RemoteAddr(), conn.LocalAddr(), remoteAddress) parts := strings.Split(remoteAddress, ":") @@ -60,6 +71,35 @@ func handleRequest(conn net.Conn, remoteAddress string, ipStack *stack.Stack, se go copyIO(proxy, conn) } +func handleRequestWithTun(conn net.Conn, remoteAddress string, selfIp []byte) { + log.Printf("Port forwarding (tcp): %s -> %s -> %s", conn.RemoteAddr(), conn.LocalAddr(), remoteAddress) + + parts := strings.Split(remoteAddress, ":") + host := parts[0] + port, err := strconv.Atoi(parts[1]) + if err != nil { + panic(err) + } + + addrTarget := net.TCPAddr{ + IP: net.ParseIP(host), + Port: port, + } + + bind := net.TCPAddr{ + IP: net.IP(selfIp), + Port: 0, + } + + proxy, err := net.DialTCP("tcp", &bind, &addrTarget) + if err != nil { + panic(err) + } + + go copyIO(conn, proxy) + go copyIO(proxy, conn) +} + func copyIO(src, dest net.Conn) { defer src.Close() defer dest.Close() diff --git a/core/udp_forwarding.go b/core/udp_forwarding.go index 5751cc5..341a81d 100644 --- a/core/udp_forwarding.go +++ b/core/udp_forwarding.go @@ -19,14 +19,16 @@ const DefaultTimeout = time.Minute * 5 type udpForward struct { src *net.UDPAddr - dest *tcpip.FullAddress + destHost net.IP + destPort int destString string ipStack *stack.Stack client *net.UDPAddr listenerConn *net.UDPConn - connections map[string]*connection - connectionsMutex *sync.RWMutex + gvisorConnections map[string]*gvisorConnection + tunConnections map[string]*tunConnection + connectionsMutex *sync.RWMutex connectCallback func(addr string) disconnectCallback func(addr string) @@ -36,24 +38,40 @@ type udpForward struct { closed bool } -type connection struct { +type gvisorConnection struct { available chan struct{} udp *gonet.UDPConn lastActive time.Time } -func ServeUdpForwarding(bindAddress string, remoteAddress string, ipStack *stack.Stack) { - udpForward := newUdpForward(bindAddress, remoteAddress, ipStack) - udpForward.StartUdpForward() +type tunConnection struct { + available chan struct{} + udp *net.UDPConn + lastActive time.Time +} + +func ServeUdpForwarding(bindAddress string, remoteAddress string, client *EasyConnectClient) { + udpForward := newUdpForward(bindAddress, remoteAddress, client) + if TunMode { + udpForward.StartUdpForwardWithTun() + } else { + udpForward.StartUdpForwardWithGvisor() + } } -func newUdpForward(src, dest string, ipStack *stack.Stack) *udpForward { +func newUdpForward(src, dest string, client *EasyConnectClient) *udpForward { u := new(udpForward) - u.ipStack = ipStack + u.ipStack = client.gvisorStack u.connectCallback = func(addr string) {} u.disconnectCallback = func(addr string) {} u.connectionsMutex = new(sync.RWMutex) - u.connections = make(map[string]*connection) + + if TunMode { + u.tunConnections = make(map[string]*tunConnection) + } else { + u.gvisorConnections = make(map[string]*gvisorConnection) + } + u.timeout = DefaultTimeout var err error @@ -69,11 +87,8 @@ func newUdpForward(src, dest string, ipStack *stack.Stack) *udpForward { u.destString = dest - u.dest = &tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(port), - Addr: tcpip.AddrFromSlice(net.ParseIP(host).To4()), - } + u.destHost = net.ParseIP(host) + u.destPort = port u.listenerConn, err = net.ListenUDP("udp", u.src) if err != nil { @@ -84,8 +99,8 @@ func newUdpForward(src, dest string, ipStack *stack.Stack) *udpForward { return u } -func (u *udpForward) StartUdpForward() { - go u.janitor() +func (u *udpForward) StartUdpForwardWithGvisor() { + go u.janitorWithGvisor() for { buf := make([]byte, bufferSize) n, addr, err := u.listenerConn.ReadFromUDP(buf) @@ -95,17 +110,17 @@ func (u *udpForward) StartUdpForward() { } log.Printf("Port forwarding (udp): %s -> %s -> %s", addr.String(), u.src.String(), u.destString) - go u.handle(buf[:n], addr) + go u.handleWithGvisor(buf[:n], addr) } } -func (u *udpForward) janitor() { +func (u *udpForward) janitorWithGvisor() { for !u.closed { time.Sleep(u.timeout) var keysToDelete []string u.connectionsMutex.RLock() - for k, conn := range u.connections { + for k, conn := range u.gvisorConnections { if conn.lastActive.Before(time.Now().Add(-u.timeout)) { keysToDelete = append(keysToDelete, k) } @@ -114,8 +129,8 @@ func (u *udpForward) janitor() { u.connectionsMutex.Lock() for _, k := range keysToDelete { - u.connections[k].udp.Close() - delete(u.connections, k) + u.gvisorConnections[k].udp.Close() + delete(u.gvisorConnections, k) } u.connectionsMutex.Unlock() @@ -125,11 +140,11 @@ func (u *udpForward) janitor() { } } -func (u *udpForward) handle(data []byte, addr *net.UDPAddr) { +func (u *udpForward) handleWithGvisor(data []byte, addr *net.UDPAddr) { u.connectionsMutex.Lock() - conn, found := u.connections[addr.String()] + conn, found := u.gvisorConnections[addr.String()] if !found { - u.connections[addr.String()] = &connection{ + u.gvisorConnections[addr.String()] = &gvisorConnection{ available: make(chan struct{}), udp: nil, lastActive: time.Now(), @@ -143,22 +158,154 @@ func (u *udpForward) handle(data []byte, addr *net.UDPAddr) { addrTarget := tcpip.FullAddress{ NIC: defaultNIC, - Port: u.dest.Port, - Addr: u.dest.Addr, + Port: uint16(u.destPort), + Addr: tcpip.AddrFromSlice(u.destHost.To4()), } udpConn, err = gonet.DialUDP(u.ipStack, nil, &addrTarget, header.IPv4ProtocolNumber) if err != nil { log.Println("UDP forward: failed to dial:", err) - delete(u.connections, addr.String()) + delete(u.gvisorConnections, addr.String()) + return + } + + u.connectionsMutex.Lock() + u.gvisorConnections[addr.String()].udp = udpConn + u.gvisorConnections[addr.String()].lastActive = time.Now() + close(u.gvisorConnections[addr.String()].available) + u.connectionsMutex.Unlock() + + u.connectCallback(addr.String()) + + _, err = udpConn.Write(data) + if err != nil { + log.Println("UDP forward: error sending initial packet to client", err) + } + + for { + buf := make([]byte, bufferSize) + n, err := udpConn.Read(buf) + if err != nil { + u.connectionsMutex.Lock() + udpConn.Close() + delete(u.gvisorConnections, addr.String()) + u.connectionsMutex.Unlock() + u.disconnectCallback(addr.String()) + log.Println("udp-forward: abnormal read, closing:", err) + return + } + + _, _, err = u.listenerConn.WriteMsgUDP(buf[:n], nil, addr) + if err != nil { + log.Println("UDP forward: error sending packet to client:", err) + } + } + } + + <-conn.available + + _, err := conn.udp.Write(data) + if err != nil { + log.Println("UDP forward: error sending packet to server:", err) + } + + shouldChangeTime := false + u.connectionsMutex.RLock() + if _, found := u.gvisorConnections[addr.String()]; found { + if u.gvisorConnections[addr.String()].lastActive.Before( + time.Now().Add(u.timeout / 4)) { + shouldChangeTime = true + } + } + u.connectionsMutex.RUnlock() + + if shouldChangeTime { + u.connectionsMutex.Lock() + + if _, found := u.gvisorConnections[addr.String()]; found { + connWrapper := u.gvisorConnections[addr.String()] + connWrapper.lastActive = time.Now() + u.gvisorConnections[addr.String()] = connWrapper + } + u.connectionsMutex.Unlock() + } +} + +func (u *udpForward) StartUdpForwardWithTun() { + go u.janitorWithTun() + for { + buf := make([]byte, bufferSize) + n, addr, err := u.listenerConn.ReadFromUDP(buf) + if err != nil { + log.Println("UDP forward: failed to read, terminating:", err) + return + } + + log.Printf("Port forwarding (udp): %s -> %s -> %s", addr.String(), u.src.String(), u.destString) + go u.handleWithTun(buf[:n], addr) + } +} + +func (u *udpForward) janitorWithTun() { + for !u.closed { + time.Sleep(u.timeout) + var keysToDelete []string + + u.connectionsMutex.RLock() + for k, conn := range u.tunConnections { + if conn.lastActive.Before(time.Now().Add(-u.timeout)) { + keysToDelete = append(keysToDelete, k) + } + } + u.connectionsMutex.RUnlock() + + u.connectionsMutex.Lock() + for _, k := range keysToDelete { + u.tunConnections[k].udp.Close() + delete(u.tunConnections, k) + } + u.connectionsMutex.Unlock() + + for _, k := range keysToDelete { + u.disconnectCallback(k) + } + } +} + +func (u *udpForward) handleWithTun(data []byte, addr *net.UDPAddr) { + u.connectionsMutex.Lock() + conn, found := u.tunConnections[addr.String()] + if !found { + u.tunConnections[addr.String()] = &tunConnection{ + available: make(chan struct{}), + udp: nil, + lastActive: time.Now(), + } + } + u.connectionsMutex.Unlock() + + if !found { + var udpConn *net.UDPConn + var err error + + addrTarget := net.UDPAddr{ + IP: u.destHost, + Port: u.destPort, + } + + udpConn, err = net.DialUDP("udp", nil, &addrTarget) + + if err != nil { + log.Println("UDP forward: failed to dial:", err) + delete(u.tunConnections, addr.String()) return } u.connectionsMutex.Lock() - u.connections[addr.String()].udp = udpConn - u.connections[addr.String()].lastActive = time.Now() - close(u.connections[addr.String()].available) + u.tunConnections[addr.String()].udp = udpConn + u.tunConnections[addr.String()].lastActive = time.Now() + close(u.tunConnections[addr.String()].available) u.connectionsMutex.Unlock() u.connectCallback(addr.String()) @@ -174,7 +321,7 @@ func (u *udpForward) handle(data []byte, addr *net.UDPAddr) { if err != nil { u.connectionsMutex.Lock() udpConn.Close() - delete(u.connections, addr.String()) + delete(u.tunConnections, addr.String()) u.connectionsMutex.Unlock() u.disconnectCallback(addr.String()) log.Println("udp-forward: abnormal read, closing:", err) @@ -197,8 +344,8 @@ func (u *udpForward) handle(data []byte, addr *net.UDPAddr) { shouldChangeTime := false u.connectionsMutex.RLock() - if _, found := u.connections[addr.String()]; found { - if u.connections[addr.String()].lastActive.Before( + if _, found := u.tunConnections[addr.String()]; found { + if u.tunConnections[addr.String()].lastActive.Before( time.Now().Add(u.timeout / 4)) { shouldChangeTime = true } @@ -208,10 +355,10 @@ func (u *udpForward) handle(data []byte, addr *net.UDPAddr) { if shouldChangeTime { u.connectionsMutex.Lock() - if _, found := u.connections[addr.String()]; found { - connWrapper := u.connections[addr.String()] + if _, found := u.tunConnections[addr.String()]; found { + connWrapper := u.tunConnections[addr.String()] connWrapper.lastActive = time.Now() - u.connections[addr.String()] = connWrapper + u.tunConnections[addr.String()] = connWrapper } u.connectionsMutex.Unlock() } From 138d19c829b27ff681a7f817e6f79b792a5eeee0 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 16:44:12 +0800 Subject: [PATCH 05/28] fix: set laddr in udp forwarding --- core/udp_forwarding.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/udp_forwarding.go b/core/udp_forwarding.go index 341a81d..05e955d 100644 --- a/core/udp_forwarding.go +++ b/core/udp_forwarding.go @@ -23,7 +23,7 @@ type udpForward struct { destPort int destString string ipStack *stack.Stack - client *net.UDPAddr + clientIp []byte listenerConn *net.UDPConn gvisorConnections map[string]*gvisorConnection @@ -62,6 +62,7 @@ func ServeUdpForwarding(bindAddress string, remoteAddress string, client *EasyCo func newUdpForward(src, dest string, client *EasyConnectClient) *udpForward { u := new(udpForward) u.ipStack = client.gvisorStack + u.clientIp = client.clientIp u.connectCallback = func(addr string) {} u.disconnectCallback = func(addr string) {} u.connectionsMutex = new(sync.RWMutex) @@ -289,12 +290,17 @@ func (u *udpForward) handleWithTun(data []byte, addr *net.UDPAddr) { var udpConn *net.UDPConn var err error - addrTarget := net.UDPAddr{ + laddr := net.UDPAddr{ + IP: u.clientIp, + Port: 0, + } + + raddr := net.UDPAddr{ IP: u.destHost, Port: u.destPort, } - udpConn, err = net.DialUDP("udp", nil, &addrTarget) + udpConn, err = net.DialUDP("udp", &laddr, &raddr) if err != nil { log.Println("UDP forward: failed to dial:", err) From 00b307e329ddb4ac4768cffc85c62e38eaf5a18e Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 16:53:28 +0800 Subject: [PATCH 06/28] feat: keep alive in tun mode --- core/EasyConnectClient.go | 8 +++--- core/keep_alive.go | 56 ++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go index 501eb53..5a76ffe 100644 --- a/core/EasyConnectClient.go +++ b/core/EasyConnectClient.go @@ -207,16 +207,16 @@ func StartClient(host string, port int, username string, password string, twfId if HttpBind != "" { go ServeHttp(HttpBind, dialer, &dnsResolve) } - - if EnableKeepAlive { - go KeepAlive(ZjuDnsServer, client.gvisorStack, client.clientIp) - } } for _, singleForwarding := range ForwardingList { go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) } + if EnableKeepAlive { + go KeepAlive(ZjuDnsServer, client) + } + for { runtime.KeepAlive(client) time.Sleep(time.Second * 10) diff --git a/core/keep_alive.go b/core/keep_alive.go index 1418dce..e79482b 100644 --- a/core/keep_alive.go +++ b/core/keep_alive.go @@ -5,29 +5,49 @@ import ( "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" "log" "net" "time" ) -func KeepAlive(dnsServer string, ipStack *stack.Stack, selfIp []byte) { - var remoteResolver = &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.AddrFromSlice(net.ParseIP(dnsServer).To4()), - } - - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.AddrFromSlice(selfIp), - } - - return gonet.DialUDP(ipStack, &bind, &addrDns, header.IPv4ProtocolNumber) - }, +func KeepAlive(dnsServer string, client *EasyConnectClient) { + var remoteResolver net.Resolver + + if TunMode { + remoteResolver = net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := net.UDPAddr{ + IP: net.ParseIP(dnsServer), + Port: 53, + } + + bind := net.UDPAddr{ + IP: net.IP(client.clientIp), + Port: 0, + } + + return net.DialUDP(network, &bind, &addrDns) + }, + } + } else { + remoteResolver = net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := tcpip.FullAddress{ + NIC: defaultNIC, + Port: uint16(53), + Addr: tcpip.AddrFromSlice(net.ParseIP(dnsServer).To4()), + } + + bind := tcpip.FullAddress{ + NIC: defaultNIC, + Addr: tcpip.AddrFromSlice(client.clientIp), + } + + return gonet.DialUDP(client.gvisorStack, &bind, &addrDns, header.IPv4ProtocolNumber) + }, + } } for { From 34387be002f26203e9e15ef19a70708d6d4c5890 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 17:09:06 +0800 Subject: [PATCH 07/28] feat: dns resolve in tun mode --- core/EasyConnectClient.go | 55 ++++++--------------------- core/dns_resolve.go | 80 +++++++++++++++++++++++++++++++++++++++ core/keep_alive.go | 44 +-------------------- 3 files changed, 92 insertions(+), 87 deletions(-) diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go index 5a76ffe..33e6eb9 100644 --- a/core/EasyConnectClient.go +++ b/core/EasyConnectClient.go @@ -1,12 +1,8 @@ package core import ( - "context" "errors" "fmt" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" "log" "net" "runtime" @@ -152,7 +148,15 @@ func StartClient(host string, port int, username string, password string, twfId client.gvisorStack = SetupGvisorStack(client.clientIp, client.gvisorEndpoint) StartProtocolWithGvisor(client.gvisorEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) + } + + for _, singleForwarding := range ForwardingList { + go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) + } + + dnsResolve := SetupDnsResolve(ZjuDnsServer, client) + if !TunMode { for _, customDNS := range CustomDNSList { ipAddr := net.ParseIP(customDNS.IP) if ipAddr == nil { @@ -162,59 +166,22 @@ func StartClient(host string, port int, username string, password string, twfId log.Printf("Custom DNS %s -> %s\n", customDNS.HostName, customDNS.IP) } - dnsResolve := DnsResolve{ - remoteTCPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), - } - - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.AddrFromSlice(client.clientIp), - } - - return gonet.DialUDP(client.gvisorStack, &bind, &addrDns, header.IPv4ProtocolNumber) - }, - }, - remoteUDPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), - } - return gonet.DialTCP(client.gvisorStack, addrDns, header.IPv4ProtocolNumber) - }, - }, - useTCP: false, - timer: nil, - } - dialer := Dialer{ selfIp: client.clientIp, gvisorStack: client.gvisorStack, } if SocksBind != "" { - go ServeSocks5(SocksBind, dialer, &dnsResolve) + go ServeSocks5(SocksBind, dialer, dnsResolve) } if HttpBind != "" { - go ServeHttp(HttpBind, dialer, &dnsResolve) + go ServeHttp(HttpBind, dialer, dnsResolve) } } - for _, singleForwarding := range ForwardingList { - go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) - } - if EnableKeepAlive { - go KeepAlive(ZjuDnsServer, client) + go KeepAlive(dnsResolve.remoteUDPResolver) } for { diff --git a/core/dns_resolve.go b/core/dns_resolve.go index 6072698..24effa8 100644 --- a/core/dns_resolve.go +++ b/core/dns_resolve.go @@ -3,6 +3,9 @@ package core import ( "github.com/mythologyli/zju-connect/core/config" "golang.org/x/net/context" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/header" "log" "net" "sync" @@ -110,3 +113,80 @@ func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Co return resolve.ResolveWithLocal(ctx, host) } } + +func SetupDnsResolve(zjuDnsServer string, client *EasyConnectClient) *DnsResolve { + var dns DnsResolve + if TunMode { + dns = DnsResolve{ + remoteUDPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := net.UDPAddr{ + IP: net.ParseIP(zjuDnsServer), + Port: 53, + } + + bind := net.UDPAddr{ + IP: net.IP(client.clientIp), + Port: 0, + } + + return net.DialUDP(network, &bind, &addrDns) + }, + }, + remoteTCPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := net.TCPAddr{ + IP: net.ParseIP(zjuDnsServer), + Port: 53, + } + + bind := net.TCPAddr{ + IP: net.IP(client.clientIp), + Port: 0, + } + + return net.DialTCP(network, &bind, &addrDns) + }, + }, + useTCP: false, + timer: nil, + } + } else { + dns = DnsResolve{ + remoteUDPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := tcpip.FullAddress{ + NIC: defaultNIC, + Port: uint16(53), + Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), + } + + bind := tcpip.FullAddress{ + NIC: defaultNIC, + Addr: tcpip.AddrFromSlice(client.clientIp), + } + + return gonet.DialUDP(client.gvisorStack, &bind, &addrDns, header.IPv4ProtocolNumber) + }, + }, + remoteTCPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := tcpip.FullAddress{ + NIC: defaultNIC, + Port: uint16(53), + Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), + } + return gonet.DialTCP(client.gvisorStack, addrDns, header.IPv4ProtocolNumber) + }, + }, + useTCP: false, + timer: nil, + } + } + + return &dns +} diff --git a/core/keep_alive.go b/core/keep_alive.go index e79482b..2d51137 100644 --- a/core/keep_alive.go +++ b/core/keep_alive.go @@ -2,54 +2,12 @@ package core import ( "context" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" "log" "net" "time" ) -func KeepAlive(dnsServer string, client *EasyConnectClient) { - var remoteResolver net.Resolver - - if TunMode { - remoteResolver = net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := net.UDPAddr{ - IP: net.ParseIP(dnsServer), - Port: 53, - } - - bind := net.UDPAddr{ - IP: net.IP(client.clientIp), - Port: 0, - } - - return net.DialUDP(network, &bind, &addrDns) - }, - } - } else { - remoteResolver = net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.AddrFromSlice(net.ParseIP(dnsServer).To4()), - } - - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.AddrFromSlice(client.clientIp), - } - - return gonet.DialUDP(client.gvisorStack, &bind, &addrDns, header.IPv4ProtocolNumber) - }, - } - } - +func KeepAlive(remoteResolver *net.Resolver) { for { _, err := remoteResolver.LookupIP(context.Background(), "ip4", "www.baidu.com") if err != nil { From d2d3e37f0e89025ef7cbb6a141c0e0c245f819ac Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 17:28:28 +0800 Subject: [PATCH 08/28] feat: dialer in tun mode --- core/EasyConnectClient.go | 33 ++++++++--------- core/dialer.go | 74 ++++++++++++++++++++++++++++----------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go index 33e6eb9..910ac41 100644 --- a/core/EasyConnectClient.go +++ b/core/EasyConnectClient.go @@ -156,28 +156,25 @@ func StartClient(host string, port int, username string, password string, twfId dnsResolve := SetupDnsResolve(ZjuDnsServer, client) - if !TunMode { - for _, customDNS := range CustomDNSList { - ipAddr := net.ParseIP(customDNS.IP) - if ipAddr == nil { - log.Printf("Custom DNS for host_name %s is invalid, SKIP", customDNS.HostName) - } - SetPermantDns(customDNS.HostName, ipAddr) - log.Printf("Custom DNS %s -> %s\n", customDNS.HostName, customDNS.IP) + for _, customDNS := range CustomDNSList { + ipAddr := net.ParseIP(customDNS.IP) + if ipAddr == nil { + log.Printf("Custom DNS for host_name %s is invalid, SKIP", customDNS.HostName) } + SetPermantDns(customDNS.HostName, ipAddr) + log.Printf("Custom DNS %s -> %s\n", customDNS.HostName, customDNS.IP) + } - dialer := Dialer{ - selfIp: client.clientIp, - gvisorStack: client.gvisorStack, - } + dialer := Dialer{ + client: client, + } - if SocksBind != "" { - go ServeSocks5(SocksBind, dialer, dnsResolve) - } + if SocksBind != "" { + go ServeSocks5(SocksBind, dialer, dnsResolve) + } - if HttpBind != "" { - go ServeHttp(HttpBind, dialer, dnsResolve) - } + if HttpBind != "" { + go ServeHttp(HttpBind, dialer, dnsResolve) } if EnableKeepAlive { diff --git a/core/dialer.go b/core/dialer.go index 6411bac..06a1e2d 100644 --- a/core/dialer.go +++ b/core/dialer.go @@ -8,7 +8,6 @@ import ( "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" "log" "net" "strconv" @@ -16,9 +15,7 @@ import ( ) type Dialer struct { - selfIp []byte - - gvisorStack *stack.Stack + client *EasyConnectClient } func dialDirect(ctx context.Context, network, addr string) (net.Conn, error) { @@ -118,26 +115,61 @@ func (dialer *Dialer) DialIpAndPort(ctx context.Context, network, addr string) ( } if useProxy { - addrTarget := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(port), - Addr: tcpip.AddrFromSlice(target.IP), - } + if TunMode { + if network == "tcp" { + log.Printf("%s -> PROXY", addr) - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.AddrFromSlice(dialer.selfIp), - } + addrTarget := net.TCPAddr{ + IP: target.IP, + Port: port, + } + + bind := net.TCPAddr{ + IP: net.IP(dialer.client.clientIp), + Port: 0, + } + + return net.DialTCP(network, &bind, &addrTarget) + } else if network == "udp" { + log.Printf("%s -> PROXY", addr) - if network == "tcp" { - log.Printf("%s -> PROXY", addr) - return gonet.DialTCPWithBind(context.Background(), dialer.gvisorStack, bind, addrTarget, header.IPv4ProtocolNumber) - } else if network == "udp" { - log.Printf("%s -> PROXY", addr) - return gonet.DialUDP(dialer.gvisorStack, &bind, &addrTarget, header.IPv4ProtocolNumber) + addrTarget := net.UDPAddr{ + IP: target.IP, + Port: port, + } + + bind := net.UDPAddr{ + IP: net.IP(dialer.client.clientIp), + Port: 0, + } + + return net.DialUDP(network, &bind, &addrTarget) + } else { + log.Printf("Proxy only support TCP/UDP. Connection to %s will use direct connection.", addr) + return dialDirect(ctx, network, addr) + } } else { - log.Printf("Proxy only support TCP/UDP. Connection to %s will use direct connection.", addr) - return dialDirect(ctx, network, addr) + addrTarget := tcpip.FullAddress{ + NIC: defaultNIC, + Port: uint16(port), + Addr: tcpip.AddrFromSlice(target.IP), + } + + bind := tcpip.FullAddress{ + NIC: defaultNIC, + Addr: tcpip.AddrFromSlice(dialer.client.clientIp), + } + + if network == "tcp" { + log.Printf("%s -> PROXY", addr) + return gonet.DialTCPWithBind(context.Background(), dialer.client.gvisorStack, bind, addrTarget, header.IPv4ProtocolNumber) + } else if network == "udp" { + log.Printf("%s -> PROXY", addr) + return gonet.DialUDP(dialer.client.gvisorStack, &bind, &addrTarget, header.IPv4ProtocolNumber) + } else { + log.Printf("Proxy only support TCP/UDP. Connection to %s will use direct connection.", addr) + return dialDirect(ctx, network, addr) + } } } else { return dialDirect(ctx, network, addr) From b119f68cd18cdbe66a6ec1a3af942978c5c7a246 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 17:35:10 +0800 Subject: [PATCH 09/28] fix: add 0.0.0.0 route in windows --- core/tun_stack_windows.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/tun_stack_windows.go b/core/tun_stack_windows.go index 27668b6..0eddf3c 100644 --- a/core/tun_stack_windows.go +++ b/core/tun_stack_windows.go @@ -7,6 +7,7 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "log" "net/netip" + "os/exec" ) type EasyConnectTunEndpoint struct { @@ -59,7 +60,9 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { link := winipcfg.LUID(nativeTunDevice.LUID()) - prefix, err := netip.ParsePrefix(fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3])) + ipStr := fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) + + prefix, err := netip.ParsePrefix(ipStr + "/8") if err != nil { log.Printf("ParsePrefix failed: %v", err) } @@ -68,4 +71,10 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { if err != nil { log.Printf("SetIPAddresses failed: %v", err) } + + cmd := exec.Command("route", "add", "0.0.0.0", "mask", "0.0.0.0", ipStr, "metric", "9999") + err = cmd.Run() + if err != nil { + log.Printf("Run route add failed: %v", err) + } } From bb5c57f9933e6a63ecb022a59837ee850f1b5181 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 18:55:43 +0800 Subject: [PATCH 10/28] feat: use ip to set up linux tun --- core/tun_stack_darwin.go | 2 +- core/tun_stack_linux.go | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go index c73e156..57720f4 100644 --- a/core/tun_stack_darwin.go +++ b/core/tun_stack_darwin.go @@ -35,6 +35,6 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { cmd := exec.Command("/sbin/ifconfig", ifce.Name(), fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "up") err = cmd.Run() if err != nil { - log.Printf("Run ifconfig failed: %v", err) + log.Printf("Run %s failed: %v", cmd.String(), err) } } diff --git a/core/tun_stack_linux.go b/core/tun_stack_linux.go index c73e156..05a95a5 100644 --- a/core/tun_stack_linux.go +++ b/core/tun_stack_linux.go @@ -32,9 +32,15 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { endpoint.ifce = ifce - cmd := exec.Command("/sbin/ifconfig", ifce.Name(), fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "up") + cmd := exec.Command("ip", "link", "set", ifce.Name(), "up") err = cmd.Run() if err != nil { - log.Printf("Run ifconfig failed: %v", err) + log.Printf("Run %s failed: %v", cmd.String(), err) + } + + cmd = exec.Command("ip", "addr", "add", fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "dev", ifce.Name()) + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) } } From e5b43d20cfca9b6000998f7dfca1e8f4d461faf1 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 22:13:54 +0800 Subject: [PATCH 11/28] feat: dns server --- config.toml.example | 3 ++ core/EasyConnectClient.go | 13 ++++++-- core/dns_cache.go | 2 +- core/dns_resolve.go | 51 ++++++++++++++++++++---------- core/dns_server.go | 65 +++++++++++++++++++++++++++++++++++++++ core/tun_stack_windows.go | 38 ++++++++++++++++++++--- go.mod | 3 ++ go.sum | 6 ++++ main.go | 9 ++++++ 9 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 core/dns_server.go diff --git a/config.toml.example b/config.toml.example index 369a4f6..2bc349f 100644 --- a/config.toml.example +++ b/config.toml.example @@ -17,6 +17,9 @@ tun_mode = false dns_ttl = 3600 disable_keep_alive = false zju_dns_server = "10.10.0.21" +secondary_dns_server = "114.114.114.114" +dns_server_bind = "" +tun_dns_server = "" debug_dump = false # Port forwarding diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go index 910ac41..ce686f1 100644 --- a/core/EasyConnectClient.go +++ b/core/EasyConnectClient.go @@ -41,6 +41,9 @@ var ProxyAll bool var ForwardingList []Forwarding var EnableKeepAlive bool var ZjuDnsServer string +var SecondaryDnsServer string +var DnsServerBind string +var TunDnsServer string var CustomDNSList []CustomDNS type EasyConnectClient struct { @@ -154,17 +157,21 @@ func StartClient(host string, port int, username string, password string, twfId go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) } - dnsResolve := SetupDnsResolve(ZjuDnsServer, client) - for _, customDNS := range CustomDNSList { ipAddr := net.ParseIP(customDNS.IP) if ipAddr == nil { log.Printf("Custom DNS for host_name %s is invalid, SKIP", customDNS.HostName) } - SetPermantDns(customDNS.HostName, ipAddr) + SetPermanentDns(customDNS.HostName, ipAddr) log.Printf("Custom DNS %s -> %s\n", customDNS.HostName, customDNS.IP) } + dnsResolve := SetupDnsResolve(ZjuDnsServer, client) + + if DnsServerBind != "" { + go ServeDns(DnsServerBind, dnsResolve) + } + dialer := Dialer{ client: client, } diff --git a/core/dns_cache.go b/core/dns_cache.go index b819581..40860f2 100644 --- a/core/dns_cache.go +++ b/core/dns_cache.go @@ -48,7 +48,7 @@ func SetDnsCache(host string, ip net.IP) { dnsCaches.cache.Set(host, ip, cache.DefaultExpiration) } -func SetPermantDns(host string, ip net.IP) { +func SetPermanentDns(host string, ip net.IP) { once.Do(func() { dnsCaches = &DnsCache{ cache: cache.New(time.Duration(DnsTTL)*time.Second, time.Duration(DnsTTL)*2*time.Second), diff --git a/core/dns_resolve.go b/core/dns_resolve.go index 24effa8..93f1ea9 100644 --- a/core/dns_resolve.go +++ b/core/dns_resolve.go @@ -15,25 +15,26 @@ import ( type DnsResolve struct { remoteUDPResolver *net.Resolver remoteTCPResolver *net.Resolver + secondaryResolver *net.Resolver timer *time.Timer useTCP bool lock sync.RWMutex } -func (resolve *DnsResolve) ResolveWithLocal(ctx context.Context, host string) (context.Context, net.IP, error) { - if target, err := net.ResolveIPAddr("ip4", host); err != nil { - log.Printf("Resolve IPv4 addr failed using local DNS: " + host + ". Try IPv6 addr.") +func (resolve *DnsResolve) ResolveWithSecondaryDns(ctx context.Context, host string) (context.Context, net.IP, error) { + if targets, err := resolve.secondaryResolver.LookupIP(ctx, "ip4", host); err != nil { + log.Printf("Resolve IPv4 addr failed using secondary DNS: " + host + ". Try IPv6 addr.") - if target, err = net.ResolveIPAddr("ip6", host); err != nil { - log.Printf("Resolve IPv6 addr failed using local DNS: " + host + ". Reject connection.") + if targets, err = resolve.secondaryResolver.LookupIP(ctx, "ip6", host); err != nil { + log.Printf("Resolve IPv6 addr failed using secondary DNS: " + host + ". Reject connection.") return ctx, nil, err } else { - log.Printf("%s -> %s", host, target.IP.String()) - return ctx, target.IP, nil + log.Printf("%s -> %s", host, targets[0].String()) + return ctx, targets[0], nil } } else { - log.Printf("%s -> %s", host, target.IP.String()) - return ctx, target.IP, nil + log.Printf("%s -> %s", host, targets[0].String()) + return ctx, targets[0], nil } } @@ -72,10 +73,10 @@ func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Co targets, err := resolve.remoteUDPResolver.LookupIP(context.Background(), "ip4", host) if err != nil { if targets, err = resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { - // all zju dns failed, so we keep do nothing but use local dns + // all zju dns failed, so we keep do nothing but use secondary dns // host ipv4 and host ipv6 don't set cache - log.Printf("Resolve IPv4 addr failed using ZJU UDP/TCP DNS: " + host + ", using local DNS instead.") - return resolve.ResolveWithLocal(ctx, host) + log.Printf("Resolve IPv4 addr failed using ZJU UDP/TCP DNS: " + host + ", using secondary DNS instead.") + return resolve.ResolveWithSecondaryDns(ctx, host) } else { resolve.lock.Lock() resolve.useTCP = true @@ -96,10 +97,10 @@ func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Co log.Printf("%s -> %s", host, targets[0].String()) return ctx, targets[0], nil } else { - // only try tcp and local dns + // only try tcp and secondary dns if targets, err := resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { - log.Printf("Resolve IPv4 addr failed using ZJU TCP DNS: " + host + ", using local DNS instead.") - return resolve.ResolveWithLocal(ctx, host) + log.Printf("Resolve IPv4 addr failed using ZJU TCP DNS: " + host + ", using secondary DNS instead.") + return resolve.ResolveWithSecondaryDns(ctx, host) } else { SetDnsCache(host, targets[0]) log.Printf("%s -> %s", host, targets[0].String()) @@ -110,7 +111,7 @@ func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Co } else { // because of OS cache, don't need extra dns memory cache - return resolve.ResolveWithLocal(ctx, host) + return resolve.ResolveWithSecondaryDns(ctx, host) } } @@ -188,5 +189,23 @@ func SetupDnsResolve(zjuDnsServer string, client *EasyConnectClient) *DnsResolve } } + if SecondaryDnsServer != "" { + dns.secondaryResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + addrDns := net.UDPAddr{ + IP: net.ParseIP(SecondaryDnsServer), + Port: 53, + } + + return net.DialUDP(network, nil, &addrDns) + }, + } + } else { + dns.secondaryResolver = &net.Resolver{ + PreferGo: true, + } + } + return &dns } diff --git a/core/dns_server.go b/core/dns_server.go new file mode 100644 index 0000000..57bf30a --- /dev/null +++ b/core/dns_server.go @@ -0,0 +1,65 @@ +package core + +import ( + "context" + "fmt" + "log" +) +import "github.com/miekg/dns" + +type DnsServer struct { + dnsResolve *DnsResolve +} + +func (dnsServer DnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { + m := new(dns.Msg) + m.SetReply(r) + m.Compress = false + + switch r.Opcode { + case dns.OpcodeQuery: + for _, q := range r.Question { + switch q.Qtype { + case dns.TypeA: + if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), q.Name); err == nil { + if ip.To4() != nil { + rr, err := dns.NewRR(fmt.Sprintf("%s A %s", q.Name, ip)) + if err == nil { + m.Answer = append(m.Answer, rr) + } + } + } + case dns.TypeAAAA: + if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), q.Name); err == nil { + if ip.To4() == nil { + rr, err := dns.NewRR(fmt.Sprintf("%s AAAA %s", q.Name, ip)) + if err == nil { + m.Answer = append(m.Answer, rr) + } + } + } + } + } + } + + _ = w.WriteMsg(m) +} + +func ServeDns(bindAddr string, dnsResolve *DnsResolve) { + log.Printf("DNS server listening on " + bindAddr) + + dnsServer := &DnsServer{dnsResolve: dnsResolve} + dns.HandleFunc(".", dnsServer.handleDnsRequest) + + server := &dns.Server{Addr: bindAddr, Net: "udp"} + log.Printf("Starting DNS server at %s", server.Addr) + + err := server.ListenAndServe() + if err != nil { + log.Printf("Failed to start DNS server: %s", err.Error()) + } + + defer func(server *dns.Server) { + _ = server.Shutdown() + }(server) +} diff --git a/core/tun_stack_windows.go b/core/tun_stack_windows.go index 0eddf3c..b3a755a 100644 --- a/core/tun_stack_windows.go +++ b/core/tun_stack_windows.go @@ -6,6 +6,7 @@ import ( "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "log" + "net" "net/netip" "os/exec" ) @@ -43,6 +44,26 @@ func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { return sizes[0], nil } +func AddRoute(target string, interfaceIp string, metric string) error { + ipaddr, ipv4Net, err := net.ParseCIDR(target) + if err != nil { + return err + } + + ip := ipaddr.To4() + if ip == nil { + return fmt.Errorf("not a valid IPv4 address") + } + + command := exec.Command("route", "add", ip.String(), "mask", net.IP(ipv4Net.Mask).String(), interfaceIp, "metric", metric) + err = command.Run() + if err != nil { + return err + } + + return nil +} + func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { guid, err := windows.GUIDFromString("{4F5CDE94-D2A3-4AA5-A4A3-0FE6CB909E83}") if err != nil { @@ -64,17 +85,24 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { prefix, err := netip.ParsePrefix(ipStr + "/8") if err != nil { - log.Printf("ParsePrefix failed: %v", err) + log.Printf("Parse prefix failed: %v", err) } err = link.SetIPAddresses([]netip.Prefix{prefix}) if err != nil { - log.Printf("SetIPAddresses failed: %v", err) + log.Printf("Set IP address failed: %v", err) } - cmd := exec.Command("route", "add", "0.0.0.0", "mask", "0.0.0.0", ipStr, "metric", "9999") - err = cmd.Run() + err = AddRoute("0.0.0.0/0", ipStr, "9999") if err != nil { - log.Printf("Run route add failed: %v", err) + log.Printf("Add route failed: %v", err) + } + + if TunDnsServer != "" { + command := exec.Command("netsh", "interface", "ipv4", "add", "dnsserver", "\"ZJU Connect\"", "address="+TunDnsServer, "index=1") + err = command.Run() + if err != nil { + log.Printf("Run %s failed: %v", command.String(), err) + } } } diff --git a/go.mod b/go.mod index a99a61e..72b8465 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/cloverstd/tcping v0.1.1 github.com/cornelk/hashmap v1.0.8 github.com/dlclark/regexp2 v1.10.0 + github.com/miekg/dns v1.1.56 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/things-go/go-socks5 v0.0.4 @@ -28,7 +29,9 @@ require ( github.com/klauspost/compress v1.17.1 // indirect github.com/quic-go/quic-go v0.39.1 // indirect golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.14.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect ) diff --git a/go.sum b/go.sum index 05700d0..bc20951 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= @@ -48,9 +50,13 @@ github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 20eb7d9..dbf746b 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,9 @@ type ( DnsTTL *uint64 `toml:"dns_ttl"` DisableKeepAlive *bool `toml:"disable_keep_alive"` ZjuDnsServer *string `toml:"zju_dns_server"` + SecondaryDnsServer *string `toml:"secondary_dns_server"` + DnsServerBind *string `toml:"dns_server_bind"` + TunDnsServer *string `toml:"tun_dns_server"` DebugDump *bool `toml:"debug_dump"` PortForwarding []SinglePortForwarding `toml:"port_forwarding"` CustomDns []SingleCustomDns `toml:"custom_dns"` @@ -85,6 +88,9 @@ func main() { flag.StringVar(&customDns, "custom-dns", "", "Custom set dns lookup (e.g. www.cc98.org:10.10.98.98,appservice.zju.edu.cn:10.203.8.198)") flag.BoolVar(&disableKeepAlive, "disable-keep-alive", false, "Disable keep alive") flag.StringVar(&core.ZjuDnsServer, "zju-dns-server", "10.10.0.21", "ZJU DNS server address") + flag.StringVar(&core.SecondaryDnsServer, "secondary-dns-server", "114.114.114.114", "Secondary DNS server address. Leave empty to use system default DNS server") + flag.StringVar(&core.DnsServerBind, "dns-server-bind", "", "The address DNS server listens on (e.g. 127.0.0.1:53)") + flag.StringVar(&core.TunDnsServer, "tun-dns-server", "", "DNS Server address for TUN interface (e.g. 127.0.0.1). You should not specify the port") flag.StringVar(&twfId, "twf-id", "", "Login using twfID captured (mostly for debug usage)") flag.StringVar(&configFile, "config", "", "Config file") flag.BoolVar(&showVersion, "version", false, "Show version") @@ -122,6 +128,9 @@ func main() { core.DebugDump = getTomlVal(conf.DebugDump, false) core.EnableKeepAlive = !getTomlVal(conf.DisableKeepAlive, false) core.ZjuDnsServer = getTomlVal(conf.ZjuDnsServer, "10.10.0.21") + core.SecondaryDnsServer = getTomlVal(conf.SecondaryDnsServer, "114.114.114.114") + core.DnsServerBind = getTomlVal(conf.DnsServerBind, "") + core.TunDnsServer = getTomlVal(conf.TunDnsServer, "") for _, singlePortForwarding := range conf.PortForwarding { if singlePortForwarding.NetworkType == nil { From 481e786924e9b7f7dcfc0658d24b5974fbbb9ef8 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 22:44:51 +0800 Subject: [PATCH 12/28] fix: remove redundant point --- core/dns_server.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/dns_server.go b/core/dns_server.go index 57bf30a..5e4db47 100644 --- a/core/dns_server.go +++ b/core/dns_server.go @@ -19,9 +19,14 @@ func (dnsServer DnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { switch r.Opcode { case dns.OpcodeQuery: for _, q := range r.Question { + name := q.Name + if len(name) > 1 && name[len(name)-1] == '.' { + name = name[:len(name)-1] + } + switch q.Qtype { case dns.TypeA: - if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), q.Name); err == nil { + if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), name); err == nil { if ip.To4() != nil { rr, err := dns.NewRR(fmt.Sprintf("%s A %s", q.Name, ip)) if err == nil { @@ -30,7 +35,7 @@ func (dnsServer DnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { } } case dns.TypeAAAA: - if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), q.Name); err == nil { + if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), name); err == nil { if ip.To4() == nil { rr, err := dns.NewRR(fmt.Sprintf("%s AAAA %s", q.Name, ip)) if err == nil { From 1467ba1673b2acede4b09c04c0804e0e0b15e4d3 Mon Sep 17 00:00:00 2001 From: Myth Date: Sun, 22 Oct 2023 22:50:12 +0800 Subject: [PATCH 13/28] fix: custom dns should have the highest priority --- core/dns_resolve.go | 96 ++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/core/dns_resolve.go b/core/dns_resolve.go index 93f1ea9..875daf2 100644 --- a/core/dns_resolve.go +++ b/core/dns_resolve.go @@ -39,13 +39,6 @@ func (resolve *DnsResolve) ResolveWithSecondaryDns(ctx context.Context, host str } func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Context, net.IP, error) { - if config.IsDnsRuleAvailable() { - if ip, hasDnsRule := config.GetSingleDnsRule(host); hasDnsRule { - ctx = context.WithValue(ctx, "USE_PROXY", true) - log.Printf("%s -> %s", host, ip) - return ctx, net.ParseIP(ip), nil - } - } var useProxy = false if config.IsZjuForceProxyRuleAvailable() { if isInZjuForceProxyRule := config.IsInZjuForceProxyRule(host); isInZjuForceProxyRule { @@ -60,55 +53,62 @@ func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Co ctx = context.WithValue(ctx, "USE_PROXY", useProxy) + if cachedIP, found := GetDnsCache(host); found { + log.Printf("%s -> %s", host, cachedIP.String()) + return ctx, cachedIP, nil + } + + if config.IsDnsRuleAvailable() { + if ip, hasDnsRule := config.GetSingleDnsRule(host); hasDnsRule { + ctx = context.WithValue(ctx, "USE_PROXY", true) + log.Printf("%s -> %s", host, ip) + return ctx, net.ParseIP(ip), nil + } + } + if UseZjuDns { - if cachedIP, found := GetDnsCache(host); found { - log.Printf("%s -> %s", host, cachedIP.String()) - return ctx, cachedIP, nil - } else { - resolve.lock.RLock() - useTCP := resolve.useTCP - resolve.lock.RUnlock() - - if !useTCP { - targets, err := resolve.remoteUDPResolver.LookupIP(context.Background(), "ip4", host) - if err != nil { - if targets, err = resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { - // all zju dns failed, so we keep do nothing but use secondary dns - // host ipv4 and host ipv6 don't set cache - log.Printf("Resolve IPv4 addr failed using ZJU UDP/TCP DNS: " + host + ", using secondary DNS instead.") - return resolve.ResolveWithSecondaryDns(ctx, host) - } else { - resolve.lock.Lock() - resolve.useTCP = true - if resolve.timer == nil { - resolve.timer = time.AfterFunc(10*time.Minute, func() { - resolve.lock.Lock() - resolve.useTCP = false - resolve.timer = nil - resolve.lock.Unlock() - }) - } - resolve.lock.Unlock() + resolve.lock.RLock() + useTCP := resolve.useTCP + resolve.lock.RUnlock() + + if !useTCP { + targets, err := resolve.remoteUDPResolver.LookupIP(context.Background(), "ip4", host) + if err != nil { + if targets, err = resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { + // all zju dns failed, so we keep do nothing but use secondary dns + // host ipv4 and host ipv6 don't set cache + log.Printf("Resolve IPv4 addr failed using ZJU UDP/TCP DNS: " + host + ", using secondary DNS instead.") + return resolve.ResolveWithSecondaryDns(ctx, host) + } else { + resolve.lock.Lock() + resolve.useTCP = true + if resolve.timer == nil { + resolve.timer = time.AfterFunc(10*time.Minute, func() { + resolve.lock.Lock() + resolve.useTCP = false + resolve.timer = nil + resolve.lock.Unlock() + }) } + resolve.lock.Unlock() } - // set dns cache if tcp or udp dns success - //TODO: whether we need all dns records? or only 10.0.0.0/8 ? + } + // set dns cache if tcp or udp dns success + //TODO: whether we need all dns records? or only 10.0.0.0/8 ? + SetDnsCache(host, targets[0]) + log.Printf("%s -> %s", host, targets[0].String()) + return ctx, targets[0], nil + } else { + // only try tcp and secondary dns + if targets, err := resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { + log.Printf("Resolve IPv4 addr failed using ZJU TCP DNS: " + host + ", using secondary DNS instead.") + return resolve.ResolveWithSecondaryDns(ctx, host) + } else { SetDnsCache(host, targets[0]) log.Printf("%s -> %s", host, targets[0].String()) return ctx, targets[0], nil - } else { - // only try tcp and secondary dns - if targets, err := resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { - log.Printf("Resolve IPv4 addr failed using ZJU TCP DNS: " + host + ", using secondary DNS instead.") - return resolve.ResolveWithSecondaryDns(ctx, host) - } else { - SetDnsCache(host, targets[0]) - log.Printf("%s -> %s", host, targets[0].String()) - return ctx, targets[0], nil - } } } - } else { // because of OS cache, don't need extra dns memory cache return resolve.ResolveWithSecondaryDns(ctx, host) From 2f6ecd28a4e19c454115a6b520ad956dd20c56df Mon Sep 17 00:00:00 2001 From: Myth Date: Mon, 23 Oct 2023 01:29:44 +0800 Subject: [PATCH 14/28] feat: add routes from rules --- config.toml.example | 1 + core/EasyConnectClient.go | 20 ++++++++++++++++ core/config/DnsRules.go | 6 +++++ core/config/Ipv4Set.go | 48 +++++++++++++++++++++++++++++++++++++++ core/dialer.go | 40 +++++++------------------------- core/tun_stack_darwin.go | 12 +++++++++- core/tun_stack_linux.go | 10 ++++++++ core/tun_stack_windows.go | 14 ++++++++---- go.mod | 3 +++ go.sum | 29 +++++++++++++++++++++++ main.go | 3 +++ 11 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 core/config/Ipv4Set.go diff --git a/config.toml.example b/config.toml.example index 2bc349f..60551cd 100644 --- a/config.toml.example +++ b/config.toml.example @@ -14,6 +14,7 @@ socks_user = "" socks_passwd = "" http_bind = ":1081" tun_mode = false +add_route = false dns_ttl = 3600 disable_keep_alive = false zju_dns_server = "10.10.0.21" diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go index ce686f1..90725db 100644 --- a/core/EasyConnectClient.go +++ b/core/EasyConnectClient.go @@ -31,6 +31,7 @@ var SocksUser string var SocksPasswd string var HttpBind string var TunMode bool +var AddRoute bool var DebugDump bool var ParseServConfig bool var ParseZjuConfig bool @@ -144,6 +145,20 @@ func StartClient(host string, port int, username string, password string, twfId client.tunEndpoint = &EasyConnectTunEndpoint{} SetupTunStack(ip, client.tunEndpoint) + // Add routes + if AddRoute && config.IsIpv4SetAvailable() { + ipv4Set := config.GetIpv4Set() + + if err != nil { + log.Printf("Cannot get ipset: %v", err) + } else { + for _, prefix := range ipv4Set.Prefixes() { + log.Printf("Add route to %s", prefix.String()) + _ = client.tunEndpoint.AddRoute(prefix.String()) + } + } + } + StartProtocolWithTun(client.tunEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) } else { // Use Gvisor stack @@ -260,6 +275,11 @@ func (client *EasyConnectClient) ParseAllConfig() { parser.ParseZjuIpv4Rules(DebugDump) parser.ParseZjuForceProxyRules(DebugDump) } + + err := config.GenerateIpv4Set() + if err != nil { + return + } } func (client *EasyConnectClient) GetClientIp() ([]byte, error) { diff --git a/core/config/DnsRules.go b/core/config/DnsRules.go index 66eea7a..2d6757f 100644 --- a/core/config/DnsRules.go +++ b/core/config/DnsRules.go @@ -8,6 +8,7 @@ import ( // domain[ip] var dnsRules *hashmap.Map[string, string] +var dnsIps []string func AppendSingleDnsRule(domain, ip string, debug bool) { if dnsRules == nil { @@ -19,6 +20,7 @@ func AppendSingleDnsRule(domain, ip string, debug bool) { } dnsRules.Set(domain, ip) + dnsIps = append(dnsIps, ip) } func GetSingleDnsRule(domain string) (string, bool) { @@ -36,3 +38,7 @@ func GetDnsRuleLen() int { return 0 } } + +func GetDnsIps() []string { + return dnsIps +} diff --git a/core/config/Ipv4Set.go b/core/config/Ipv4Set.go new file mode 100644 index 0000000..82a3681 --- /dev/null +++ b/core/config/Ipv4Set.go @@ -0,0 +1,48 @@ +package config + +import ( + "inet.af/netaddr" + "strings" +) + +var Ipv4Set *netaddr.IPSet + +func GenerateIpv4Set() error { + ipv4SetBuilder := netaddr.IPSetBuilder{} + + dnsIps := GetDnsIps() + if dnsIps != nil { + for _, ip := range dnsIps { + ipv4SetBuilder.Add(netaddr.MustParseIP(ip)) + } + } + + ipv4RangeRules := GetIpv4Rules() + if ipv4RangeRules != nil { + for _, rule := range *ipv4RangeRules { + if rule.CIDR { + ipv4SetBuilder.AddPrefix(netaddr.MustParseIPPrefix(rule.Rule)) + } else { + ip1 := netaddr.MustParseIP(strings.Split(rule.Rule, "~")[0]) + ip2 := netaddr.MustParseIP(strings.Split(rule.Rule, "~")[1]) + ipv4SetBuilder.AddRange(netaddr.IPRangeFrom(ip1, ip2)) + } + } + } + + var err error + Ipv4Set, err = ipv4SetBuilder.IPSet() + if err != nil { + return err + } + + return nil +} + +func IsIpv4SetAvailable() bool { + return Ipv4Set != nil +} + +func GetIpv4Set() *netaddr.IPSet { + return Ipv4Set +} diff --git a/core/dialer.go b/core/dialer.go index 06a1e2d..ae884c8 100644 --- a/core/dialer.go +++ b/core/dialer.go @@ -1,13 +1,13 @@ package core import ( - "bytes" "context" "errors" "github.com/mythologyli/zju-connect/core/config" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/header" + "inet.af/netaddr" "log" "net" "strconv" @@ -77,39 +77,15 @@ func (dialer *Dialer) DialIpAndPort(ctx context.Context, network, addr string) ( } } - if !useProxy && config.IsIpv4RuleAvailable() { + if !useProxy && config.IsIpv4SetAvailable() { if DebugDump { - log.Printf("IPv4 rule is available ") + log.Printf("IPv4 set is available ") } - for _, rule := range *config.GetIpv4Rules() { - if rule.CIDR { - _, cidr, _ := net.ParseCIDR(rule.Rule) - if DebugDump { - log.Printf("CIDR test: %s %s %v", target.IP, rule.Rule, cidr.Contains(target.IP)) - } - - if cidr.Contains(target.IP) { - if DebugDump { - log.Printf("CIDR matched: %s %s", target.IP, rule.Rule) - } - - useProxy = true - } - } else { - if DebugDump { - log.Printf("Raw match test: %s %s", target.IP, rule.Rule) - } - - ip1 := net.ParseIP(strings.Split(rule.Rule, "~")[0]) - ip2 := net.ParseIP(strings.Split(rule.Rule, "~")[1]) - - if bytes.Compare(target.IP, ip1) >= 0 && bytes.Compare(target.IP, ip2) <= 0 { - if DebugDump { - log.Printf("Raw matched: %s %s", ip1, ip2) - } - - useProxy = true - } + ipv4Set := config.GetIpv4Set() + ip, ok := netaddr.FromStdIP(target.IP) + if ok { + if ipv4Set.Contains(ip) { + useProxy = true } } } diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go index 57720f4..675af91 100644 --- a/core/tun_stack_darwin.go +++ b/core/tun_stack_darwin.go @@ -20,6 +20,16 @@ func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { return ep.ifce.Read(buf) } +func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { + command := exec.Command("route", "-n", "add", "-net", target, "-interface", ep.ifce.Name()) + err := command.Run() + if err != nil { + return err + } + + return nil +} + func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { ifce, err := water.New(water.Config{ DeviceType: water.TUN, @@ -32,7 +42,7 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { endpoint.ifce = ifce - cmd := exec.Command("/sbin/ifconfig", ifce.Name(), fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "up") + cmd := exec.Command("ifconfig", ifce.Name(), fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "up") err = cmd.Run() if err != nil { log.Printf("Run %s failed: %v", cmd.String(), err) diff --git a/core/tun_stack_linux.go b/core/tun_stack_linux.go index 05a95a5..b9bee1f 100644 --- a/core/tun_stack_linux.go +++ b/core/tun_stack_linux.go @@ -20,6 +20,16 @@ func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { return ep.ifce.Read(buf) } +func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { + command := exec.Command("ip", "route", "add", target, "dev", ep.ifce.Name()) + err := command.Run() + if err != nil { + return err + } + + return nil +} + func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { ifce, err := water.New(water.Config{ DeviceType: water.TUN, diff --git a/core/tun_stack_windows.go b/core/tun_stack_windows.go index b3a755a..6e56012 100644 --- a/core/tun_stack_windows.go +++ b/core/tun_stack_windows.go @@ -12,7 +12,8 @@ import ( ) type EasyConnectTunEndpoint struct { - dev tun.Device + dev tun.Device + selfIp string } func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { @@ -44,7 +45,7 @@ func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { return sizes[0], nil } -func AddRoute(target string, interfaceIp string, metric string) error { +func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { ipaddr, ipv4Net, err := net.ParseCIDR(target) if err != nil { return err @@ -55,7 +56,7 @@ func AddRoute(target string, interfaceIp string, metric string) error { return fmt.Errorf("not a valid IPv4 address") } - command := exec.Command("route", "add", ip.String(), "mask", net.IP(ipv4Net.Mask).String(), interfaceIp, "metric", metric) + command := exec.Command("route", "add", ip.String(), "mask", net.IP(ipv4Net.Mask).String(), ep.selfIp, "metric", "1") err = command.Run() if err != nil { return err @@ -83,6 +84,8 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { ipStr := fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) + endpoint.selfIp = ipStr + prefix, err := netip.ParsePrefix(ipStr + "/8") if err != nil { log.Printf("Parse prefix failed: %v", err) @@ -93,9 +96,10 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { log.Printf("Set IP address failed: %v", err) } - err = AddRoute("0.0.0.0/0", ipStr, "9999") + command := exec.Command("route", "add", "0.0.0.0", "mask", "0.0.0.0", ipStr, "metric", "9999") + err = command.Run() if err != nil { - log.Printf("Add route failed: %v", err) + log.Printf("Run %s failed: %v", command.String(), err) } if TunDnsServer != "" { diff --git a/go.mod b/go.mod index 72b8465..8bd9295 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb golang.zx2c4.com/wireguard/windows v0.5.3 gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 + inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a ) require ( @@ -28,6 +29,8 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/klauspost/compress v1.17.1 // indirect github.com/quic-go/quic-go v0.39.1 // indirect + go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index bc20951..88f4169 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,7 @@ 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/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -48,24 +49,50 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0= github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic= @@ -76,3 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs= gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo= diff --git a/main.go b/main.go index dbf746b..ce4882c 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ type ( SocksPasswd *string `toml:"socks_passwd"` HttpBind *string `toml:"http_bind"` TunMode *bool `toml:"tun_mode"` + AddRoute *bool `toml:"add_route"` DnsTTL *uint64 `toml:"dns_ttl"` DisableKeepAlive *bool `toml:"disable_keep_alive"` ZjuDnsServer *string `toml:"zju_dns_server"` @@ -81,6 +82,7 @@ func main() { flag.StringVar(&core.SocksPasswd, "socks-passwd", "", "SOCKS5 password, default is don't use auth") flag.StringVar(&core.HttpBind, "http-bind", ":1081", "The address HTTP server listens on (e.g. 127.0.0.1:1081)") flag.BoolVar(&core.TunMode, "tun-mode", false, "Enable TUN mode (experimental)") + flag.BoolVar(&core.AddRoute, "add-route", false, "Add route from rules for TUN interface") flag.Uint64Var(&core.DnsTTL, "dns-ttl", 3600, "DNS record time to live, unit is second") flag.BoolVar(&core.DebugDump, "debug-dump", false, "Enable traffic debug dump (only for debug usage)") flag.StringVar(&tcpPortForwarding, "tcp-port-forwarding", "", "TCP port forwarding (e.g. 0.0.0.0:9898-10.10.98.98:80,127.0.0.1:9899-10.10.98.98:80)") @@ -124,6 +126,7 @@ func main() { core.SocksPasswd = getTomlVal(conf.SocksPasswd, "") core.HttpBind = getTomlVal(conf.HttpBind, ":1081") core.TunMode = getTomlVal(conf.TunMode, false) + core.AddRoute = getTomlVal(conf.AddRoute, false) core.DnsTTL = getTomlVal(conf.DnsTTL, uint64(3600)) core.DebugDump = getTomlVal(conf.DebugDump, false) core.EnableKeepAlive = !getTomlVal(conf.DisableKeepAlive, false) From 7c92c5bc62df68b3d037de1d37eb85ea0f7a1758 Mon Sep 17 00:00:00 2001 From: Myth Date: Mon, 23 Oct 2023 14:27:23 +0800 Subject: [PATCH 15/28] fix: windows set dns command --- core/tun_stack_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tun_stack_windows.go b/core/tun_stack_windows.go index 6e56012..5f080db 100644 --- a/core/tun_stack_windows.go +++ b/core/tun_stack_windows.go @@ -103,7 +103,7 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { } if TunDnsServer != "" { - command := exec.Command("netsh", "interface", "ipv4", "add", "dnsserver", "\"ZJU Connect\"", "address="+TunDnsServer, "index=1") + command := exec.Command("netsh", "interface", "ipv4", "add", "dnsservers", "ZJU Connect", TunDnsServer) err = command.Run() if err != nil { log.Printf("Run %s failed: %v", command.String(), err) From 885101214de6ffa0727a911d376f61b360e89150 Mon Sep 17 00:00:00 2001 From: Myth Date: Mon, 23 Oct 2023 14:55:56 +0800 Subject: [PATCH 16/28] fix: reset dns --- core/tun_stack_windows.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/core/tun_stack_windows.go b/core/tun_stack_windows.go index 5f080db..b79f0df 100644 --- a/core/tun_stack_windows.go +++ b/core/tun_stack_windows.go @@ -28,20 +28,14 @@ func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { } func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { - bufs := make([][]byte, 1) - for i := range bufs { - bufs[i] = make([]byte, 1500) - } - - sizes := make([]int, 1) + bufs := [][]byte{buf} + sizes := []int{1} _, err := ep.dev.Read(bufs, sizes, 0) if err != nil { return 0, err } - copy(buf, bufs[0][:sizes[0]]) - return sizes[0], nil } @@ -103,10 +97,13 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { } if TunDnsServer != "" { - command := exec.Command("netsh", "interface", "ipv4", "add", "dnsservers", "ZJU Connect", TunDnsServer) - err = command.Run() - if err != nil { - log.Printf("Run %s failed: %v", command.String(), err) - } + command = exec.Command("netsh", "interface", "ipv4", "add", "dnsservers", "ZJU Connect", TunDnsServer) + } else { + command = exec.Command("netsh", "interface", "ipv4", "delete", "dnsservers", "ZJU Connect", "all") + } + + err = command.Run() + if err != nil { + log.Printf("Run %s failed: %v", command.String(), err) } } From c2528c52a11b612feae1205f9dc205c95131eb05 Mon Sep 17 00:00:00 2001 From: Myth Date: Mon, 23 Oct 2023 19:41:07 +0800 Subject: [PATCH 17/28] fix: should return err --- core/protocol.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/protocol.go b/core/protocol.go index 0b64a4c..18cff0f 100644 --- a/core/protocol.go +++ b/core/protocol.go @@ -293,7 +293,7 @@ func BlockRXStreamWithTun(server string, token *[48]byte, ipRev *[4]byte, endpoi err = endpoint.Write(reply[:n]) if err != nil { - panic(err) + return err } if debug { @@ -369,7 +369,7 @@ func BlockTXStreamWithTun(server string, token *[48]byte, ipRev *[4]byte, endpoi func StartProtocolWithTun(endpoint *EasyConnectTunEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { RX := func() { counter := 0 - for counter < 50 { + for counter < 5 { err := BlockRXStreamWithTun(server, token, ipRev, endpoint, debug) if err != nil { log.Print("Error occurred while receiving, retrying: " + err.Error()) @@ -383,7 +383,7 @@ func StartProtocolWithTun(endpoint *EasyConnectTunEndpoint, server string, token TX := func() { counter := 0 - for counter < 50 { + for counter < 5 { err := BlockTXStreamWithTun(server, token, ipRev, endpoint, debug) if err != nil { log.Print("Error occurred while send, retrying: " + err.Error()) From 7e5dd7a3fb7ec594bdfe9f2175701bb6d2791a7f Mon Sep 17 00:00:00 2001 From: Myth Date: Tue, 24 Oct 2023 01:35:50 +0800 Subject: [PATCH 18/28] fix: set mtu to 1400 --- core/tun_stack_darwin.go | 6 ++++++ core/tun_stack_linux.go | 6 ++++++ core/tun_stack_windows.go | 8 +++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go index 675af91..1332ee2 100644 --- a/core/tun_stack_darwin.go +++ b/core/tun_stack_darwin.go @@ -47,4 +47,10 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { if err != nil { log.Printf("Run %s failed: %v", cmd.String(), err) } + + cmd = exec.Command("ifconfig", ifce.Name(), "mtu", "1400", "up") + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) + } } diff --git a/core/tun_stack_linux.go b/core/tun_stack_linux.go index b9bee1f..acf920c 100644 --- a/core/tun_stack_linux.go +++ b/core/tun_stack_linux.go @@ -48,6 +48,12 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { log.Printf("Run %s failed: %v", cmd.String(), err) } + cmd = exec.Command("ip", "link", "set", "dev", ifce.Name(), "mtu", "1400") + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) + } + cmd = exec.Command("ip", "addr", "add", fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "dev", ifce.Name()) err = cmd.Run() if err != nil { diff --git a/core/tun_stack_windows.go b/core/tun_stack_windows.go index b79f0df..348f26f 100644 --- a/core/tun_stack_windows.go +++ b/core/tun_stack_windows.go @@ -90,7 +90,13 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { log.Printf("Set IP address failed: %v", err) } - command := exec.Command("route", "add", "0.0.0.0", "mask", "0.0.0.0", ipStr, "metric", "9999") + command := exec.Command("netsh", "interface", "ipv4", "set", "subinterface", "ZJU Connect", "mtu=1400", "store=persistent") + err = command.Run() + if err != nil { + log.Printf("Run %s failed: %v", command.String(), err) + } + + command = exec.Command("route", "add", "0.0.0.0", "mask", "0.0.0.0", ipStr, "metric", "9999") err = command.Run() if err != nil { log.Printf("Run %s failed: %v", command.String(), err) From 11dcd259d4eaa2a19ef96ac3ea78b45e090f2ff5 Mon Sep 17 00:00:00 2001 From: Xuzheng Chen <1092889706@qq.com> Date: Wed, 25 Oct 2023 20:50:12 +0800 Subject: [PATCH 19/28] bugfix: fix macos route and ip setup command --- core/tun_stack_darwin.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go index 1332ee2..dc3d1e1 100644 --- a/core/tun_stack_darwin.go +++ b/core/tun_stack_darwin.go @@ -42,12 +42,17 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { endpoint.ifce = ifce - cmd := exec.Command("ifconfig", ifce.Name(), fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "up") + ipStr := fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) + cmd := exec.Command("ifconfig", ifce.Name(), ipStr, "255.0.0.0", ipStr) err = cmd.Run() if err != nil { log.Printf("Run %s failed: %v", cmd.String(), err) } + if err = endpoint.AddRoute("10.0.0.0/8"); err != nil { + log.Printf("Run AddRoute 10.0.0.0/8 failed: %v", err) + } + cmd = exec.Command("ifconfig", ifce.Name(), "mtu", "1400", "up") err = cmd.Run() if err != nil { From 1988151cd958a4110796146bee2d1822828f9aa8 Mon Sep 17 00:00:00 2001 From: Myth Date: Wed, 25 Oct 2023 23:34:34 +0800 Subject: [PATCH 20/28] refactor: client, stack, resolve, dial and service --- client/client.go | 156 +++++++ core/config/ServerList.go => client/line.go | 53 +-- client/parse.go | 171 ++++++++ client/protocol.go | 135 ++++++ client/request.go | 369 ++++++++++++++++ core/EasyConnectClient.go | 308 -------------- core/config/Constants.go | 4 - core/config/DnsRules.go | 44 -- core/config/DomainWhiteList.go | 48 --- core/config/Ipv4RangeWhiteList.go | 42 -- core/config/Ipv4Set.go | 48 --- core/config/ZjuForceProxyRules.go | 43 -- core/config/conf.go | 207 --------- core/config/rlist.go | 276 ------------ core/dialer.go | 175 -------- core/dns_cache.go | 61 --- core/dns_resolve.go | 211 ---------- core/gvisor_stack.go | 120 ------ core/keep_alive.go | 21 - core/protocol.go | 397 ------------------ core/socks.go | 37 -- core/tcp_forwarding.go | 107 ----- core/tun_stack_darwin.go | 61 --- core/tun_stack_linux.go | 62 --- core/udp_forwarding.go | 371 ---------------- core/web_login.go | 281 ------------- dial/dialer.go | 131 ++++++ go.mod | 7 +- go.sum | 13 +- init.go | 278 ++++++++++++ log/log.go | 82 ++++ main.go | 328 +++++---------- parser/RulesParser.go | 309 -------------- parser/XmlParser.go | 52 --- parser/ZjuParser.go | 15 - resolve/cache.go | 22 + resolve/resolver.go | 173 ++++++++ core/dns_server.go => service/dns.go | 25 +- {core => service}/http.go | 35 +- service/keep_alive.go | 27 ++ service/socks.go | 35 ++ service/tcp_forwarding.go | 60 +++ service/udp_forwarding.go | 218 ++++++++++ stack/gvisor/dial.go | 24 ++ stack/gvisor/stack.go | 194 +++++++++ stack/stack.go | 9 + stack/tun/dial_darwin.go | 13 + stack/tun/dial_linux.go | 13 + stack/tun/dial_windows.go | 19 + stack/tun/stack.go | 99 +++++ stack/tun/stack_darwin.go | 105 +++++ stack/tun/stack_linux.go | 98 +++++ .../tun/stack_windows.go | 67 +-- 53 files changed, 2638 insertions(+), 3621 deletions(-) create mode 100644 client/client.go rename core/config/ServerList.go => client/line.go (52%) create mode 100644 client/parse.go create mode 100644 client/protocol.go create mode 100644 client/request.go delete mode 100644 core/EasyConnectClient.go delete mode 100644 core/config/Constants.go delete mode 100644 core/config/DnsRules.go delete mode 100644 core/config/DomainWhiteList.go delete mode 100644 core/config/Ipv4RangeWhiteList.go delete mode 100644 core/config/Ipv4Set.go delete mode 100644 core/config/ZjuForceProxyRules.go delete mode 100644 core/config/conf.go delete mode 100644 core/config/rlist.go delete mode 100644 core/dialer.go delete mode 100644 core/dns_cache.go delete mode 100644 core/dns_resolve.go delete mode 100644 core/gvisor_stack.go delete mode 100644 core/keep_alive.go delete mode 100644 core/protocol.go delete mode 100644 core/socks.go delete mode 100644 core/tcp_forwarding.go delete mode 100644 core/tun_stack_darwin.go delete mode 100644 core/tun_stack_linux.go delete mode 100644 core/udp_forwarding.go delete mode 100644 core/web_login.go create mode 100644 dial/dialer.go create mode 100644 init.go create mode 100644 log/log.go delete mode 100644 parser/RulesParser.go delete mode 100644 parser/XmlParser.go delete mode 100644 parser/ZjuParser.go create mode 100644 resolve/cache.go create mode 100644 resolve/resolver.go rename core/dns_server.go => service/dns.go (63%) rename {core => service}/http.go (75%) create mode 100644 service/keep_alive.go create mode 100644 service/socks.go create mode 100644 service/tcp_forwarding.go create mode 100644 service/udp_forwarding.go create mode 100644 stack/gvisor/dial.go create mode 100644 stack/gvisor/stack.go create mode 100644 stack/stack.go create mode 100644 stack/tun/dial_darwin.go create mode 100644 stack/tun/dial_linux.go create mode 100644 stack/tun/dial_windows.go create mode 100644 stack/tun/stack.go create mode 100644 stack/tun/stack_darwin.go create mode 100644 stack/tun/stack_linux.go rename core/tun_stack_windows.go => stack/tun/stack_windows.go (50%) diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..8ce609f --- /dev/null +++ b/client/client.go @@ -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 +} diff --git a/core/config/ServerList.go b/client/line.go similarity index 52% rename from core/config/ServerList.go rename to client/line.go index a4f4404..3505f91 100644 --- a/core/config/ServerList.go +++ b/client/line.go @@ -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]) @@ -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) } @@ -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 } diff --git a/client/parse.go b/client/parse.go new file mode 100644 index 0000000..da40887 --- /dev/null +++ b/client/parse.go @@ -0,0 +1,171 @@ +package client + +import ( + "errors" + "github.com/beevik/etree" + "github.com/mythologyli/zju-connect/log" + "inet.af/netaddr" + "net" + "strings" +) + +func (c *EasyConnectClient) parseLineListFromConfig(config string) error { + log.Println("Parsing line list from config") + + doc := etree.NewDocument() + + err := doc.ReadFromString(config) + if err != nil { + return err + } + + element := doc.SelectElement("Conf").SelectElement("Mline") + if element == nil { + return errors.New("no Mline element found") + } + + if element.SelectAttr("enable") == nil && element.SelectAttr("enable").Value != "1" { + return errors.New("server disable Mline") + } + + if element.SelectAttr("list") == nil { + return errors.New("no list attribute found") + } + + lineListStr := element.SelectAttr("list").Value + if lineListStr == "" { + return errors.New("empty line list") + } + + lineList := strings.Split(lineListStr, ";") + + for _, line := range lineList { + if line != "" { + c.lineList = append(c.lineList, line) + } + } + + if len(c.lineList) == 0 { + return errors.New("empty line list") + } + + return nil +} + +func (c *EasyConnectClient) parseResources(resources string) error { + log.Println("Parsing resources...") + + doc := etree.NewDocument() + + err := doc.ReadFromString(resources) + if err != nil { + return err + } + + ipSetBuilder := netaddr.IPSetBuilder{} + c.domainResource = make(map[string]bool) + c.dnsResource = make(map[string]net.IP) + + element := doc.SelectElement("Resource").SelectElement("Rcs") + if element == nil { + return errors.New("no Rcs element found") + } + + for _, rc := range element.SelectElements("Rc") { + hostListStr := rc.SelectAttr("host") + if hostListStr == nil { + continue + } + + for _, hostStr := range strings.Split(hostListStr.Value, ";") { + if strings.Contains(hostStr, "*") { + hostStr = strings.ReplaceAll(hostStr, "*", "") + } + + if hostStr == "" { + continue + } + + // IP range + if strings.Contains(hostStr, "~") { + startIPStr := strings.Split(hostStr, "~")[0] + endIPStr := strings.Split(hostStr, "~")[1] + + startIP, err := netaddr.ParseIP(startIPStr) + if err != nil { + continue + } + + endIP, err := netaddr.ParseIP(endIPStr) + if err != nil { + continue + } + + ipSetBuilder.AddRange(netaddr.IPRangeFrom(startIP, endIP)) + + log.DebugPrintf("Add IP range: %s ~ %s", startIPStr, endIPStr) + + continue + } + + // Domain + if strings.Contains(hostStr, "//") { + hostStr = strings.Split(hostStr, "//")[1] + } + + hostStr := strings.Split(hostStr, "/")[0] + ip, err := netaddr.ParseIP(hostStr) + if err != nil { + if hostStr == "" { + continue + } + + c.domainResource[hostStr] = true + + log.DebugPrintf("Add domain: %s", hostStr) + } else { + ipSetBuilder.Add(ip) + + log.DebugPrintf("Add IP: %s", hostStr) + } + } + } + + element = doc.SelectElement("Resource").SelectElement("Dns") + if element == nil { + return errors.New("no Rcs element found") + } + + dnsListStr := element.SelectAttr("data") + if dnsListStr == nil { + return errors.New("no Dns data attribute found") + } + + for _, dnsStr := range strings.Split(dnsListStr.Value, ";") { + if dnsStr == "" { + continue + } + + dnsParts := strings.Split(dnsStr, ":") + if len(dnsParts) != 3 { + continue + } + + ip, err := netaddr.ParseIP(dnsParts[2]) + if err != nil { + continue + } + + ipSetBuilder.Add(ip) + + c.dnsResource[dnsParts[1]] = ip.IPAddr().IP + log.DebugPrintf("Add DNS rule: %s -> %s", dnsParts[1], dnsParts[2]) + } + + c.ipResource, err = ipSetBuilder.IPSet() + if err != nil { + return err + } + + return nil +} diff --git a/client/protocol.go b/client/protocol.go new file mode 100644 index 0000000..94bf7de --- /dev/null +++ b/client/protocol.go @@ -0,0 +1,135 @@ +package client + +import ( + "crypto/rand" + "errors" + "github.com/mythologyli/zju-connect/log" + "github.com/refraction-networking/utls" + "io" + "net" +) + +type fakeHeartBeatExtension struct { + *tls.GenericExtension +} + +func (e *fakeHeartBeatExtension) Len() int { + return 5 +} + +func (e *fakeHeartBeatExtension) Read(b []byte) (n int, err error) { + if len(b) < e.Len() { + return 0, io.ErrShortBuffer + } + b[1] = 0x0f + b[3] = 1 + b[4] = 1 + + return e.Len(), io.EOF +} + +// Create a special TLS connection to the VPN server +func (c *EasyConnectClient) tlsConn() (*tls.UConn, error) { + // Dial the VPN server + dialConn, err := net.Dial("tcp", c.server) + if err != nil { + return nil, err + } + log.Println("Socket: connected to:", dialConn.RemoteAddr()) + + // Use uTLS to construct a weird TLS Client Hello (required by Sangfor) + // The VPN and HTTP Server share port 443, Sangfor uses a special SessionID to distinguish them + conn := tls.UClient(dialConn, &tls.Config{InsecureSkipVerify: true}, tls.HelloCustom) + + random := make([]byte, 32) + _, _ = rand.Read(random) // Ignore err + _ = conn.SetClientRandom(random) + _ = conn.SetTLSVers(tls.VersionTLS11, tls.VersionTLS11, []tls.TLSExtension{}) + conn.HandshakeState.Hello.Vers = tls.VersionTLS11 + conn.HandshakeState.Hello.CipherSuites = []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV} + conn.HandshakeState.Hello.CompressionMethods = []uint8{1, 0} + conn.HandshakeState.Hello.SessionId = []byte{'L', '3', 'I', 'P', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + conn.Extensions = []tls.TLSExtension{&fakeHeartBeatExtension{}} + + log.Println("TLS: connected to:", conn.RemoteAddr()) + + return conn, nil +} + +// RecvConn create a special TLS connection to receive data from the VPN server +func (c *EasyConnectClient) RecvConn() (*tls.UConn, error) { + if c.token == nil { + return nil, errors.New("token is nil") + } + + conn, err := c.tlsConn() + if err != nil { + return nil, err + } + + // RECV STREAM START + message := []byte{0x06, 0x00, 0x00, 0x00} + message = append(message, c.token[:]...) + message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) + message = append(message, c.ipReverse[:]...) + + n, err := conn.Write(message) + if err != nil { + return nil, err + } + log.DebugPrintf("Recv handshake: wrote %d bytes", n) + log.DebugDumpHex(message[:n]) + + reply := make([]byte, 1500) + n, err = conn.Read(reply) + if err != nil { + return nil, err + } + log.DebugPrintf("Recv handshake: read %d bytes", n) + log.DebugDumpHex(reply[:n]) + + if reply[0] != 0x01 { + return nil, errors.New("unexpected recv handshake reply") + } + + return conn, nil +} + +// SendConn create a special TLS connection to send data to the VPN server +func (c *EasyConnectClient) SendConn() (*tls.UConn, error) { + if c.token == nil { + return nil, errors.New("token is nil") + } + + conn, err := c.tlsConn() + if err != nil { + return nil, err + } + + // SEND STREAM START + message := []byte{0x05, 0x00, 0x00, 0x00} + message = append(message, c.token[:]...) + message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) + message = append(message, c.ipReverse[:]...) + + n, err := conn.Write(message) + if err != nil { + return nil, err + } + log.DebugPrintf("Send handshake: wrote %d bytes", n) + log.DebugDumpHex(message[:n]) + + reply := make([]byte, 1500) + n, err = conn.Read(reply) + if err != nil { + return nil, err + } + log.DebugPrintf("Send handshake: read %d bytes", n) + log.DebugDumpHex(reply[:n]) + + if reply[0] != 0x02 { + return nil, errors.New("unexpected send handshake reply") + } + + return conn, err +} diff --git a/client/request.go b/client/request.go new file mode 100644 index 0000000..94a7c3f --- /dev/null +++ b/client/request.go @@ -0,0 +1,369 @@ +package client + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "encoding/hex" + "errors" + "fmt" + "github.com/mythologyli/zju-connect/log" + utls "github.com/refraction-networking/utls" + "io" + "math/big" + "net" + "net/http" + "net/url" + "regexp" + "runtime" + "runtime/debug" + "strconv" + "strings" + "time" +) + +var errSMSRequired = errors.New("SMS code required") + +func (c *EasyConnectClient) requestTwfID() error { + err := c.loginAuthAndPsw() + if err != nil { + if errors.Is(err, errSMSRequired) { + err = c.loginSMS() + if err != nil { + return err + } + } else { + return err + } + } + + return nil +} + +func (c *EasyConnectClient) loginAuthAndPsw() error { + // First we request the TwfID from server + addr := "https://" + c.server + "/por/login_auth.csp?apiversion=1" + log.Printf("Request: %s", addr) + + resp, err := c.httpClient.Get(addr) + if err != nil { + debug.PrintStack() + return err + } + + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + var buf bytes.Buffer + _, err = io.Copy(&buf, resp.Body) + if err != nil { + return err + } + + c.twfID = string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) + log.Printf("TWFID: %s", c.twfID) + + // Now we need to do authentication + rndImg := string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) + if rndImg == "1" { + log.Print("Due to too many login failures, the server has activated risk control for this IP") + log.Print("Continuing to log in may cause this IP to be banned. The program has stopped the login process") + log.Print("You can wait a minute and try again") + + return errors.New("too many login failures") + } + + rsaKey := string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) + log.Printf("RSA key: %s", rsaKey) + + rsaExpMatch := regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes()) + rsaExp := "" + if rsaExpMatch != nil { + rsaExp = string(rsaExpMatch[1]) + } else { + log.Printf("Warning: No RSA_ENCRYPT_EXP, using default") + rsaExp = "65537" + } + log.Printf("RSA exp: %s", rsaExp) + + csrfMatch := regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes()) + csrfCode := "" + password := c.password + if csrfMatch != nil { + csrfCode = string(csrfMatch[1]) + log.Printf("CSRF Code: %s", csrfCode) + password += "_" + csrfCode + } else { + log.Printf("Warning: No CSRF rand code") + } + + pubKey := rsa.PublicKey{} + pubKey.E, _ = strconv.Atoi(rsaExp) + modulus := big.Int{} + modulus.SetString(rsaKey, 16) + pubKey.N = &modulus + + encryptedPassword, err := rsa.EncryptPKCS1v15(rand.Reader, &pubKey, []byte(password)) + if err != nil { + return err + } + encryptedPasswordHex := hex.EncodeToString(encryptedPassword) + + addr = "https://" + c.server + "/por/login_psw.csp?anti_replay=1&encrypt=1&type=cs" + log.Printf("Request: %s", addr) + + form := url.Values{ + "svpn_rand_code": {""}, + "mitm": {""}, + "svpn_req_randcode": {csrfCode}, + "svpn_name": {c.username}, + "svpn_password": {encryptedPasswordHex}, + } + + req, err := http.NewRequest("POST", addr, strings.NewReader(form.Encode())) + req.Header.Set("Cookie", "TWFID="+c.twfID) + + resp, err = c.httpClient.Do(req) + if err != nil { + return err + } + + buf.Reset() + _, err = io.Copy(&buf, resp.Body) + if err != nil { + return err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + if strings.Contains(buf.String(), "auth/sms") || strings.Contains(buf.String(), "2") { + log.Print("SMS code required") + + return errSMSRequired + } + + if strings.Contains(buf.String(), "-1") || !strings.Contains(buf.String(), "") { + log.Print("No NextAuth found") + } else { + return errors.New("Not implemented auth: " + buf.String()) + } + + if !strings.Contains(buf.String(), "1") { + return errors.New("Login failed: " + buf.String()) + } + + twfIDMatch := regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes()) + if twfIDMatch != nil { + c.twfID = string(twfIDMatch[1]) + log.Printf("Update TWFID: %s", c.twfID) + } + + log.Printf("TWFID has been authorized") + + return nil +} + +func (c *EasyConnectClient) loginSMS() error { + addr := "https://" + c.server + "/por/login_sms.csp?apiversion=1" + log.Printf("SMS request: " + addr) + req, err := http.NewRequest("POST", addr, nil) + req.Header.Set("Cookie", "TWFID="+c.twfID) + + resp, err := c.httpClient.Do(req) + if err != nil { + return err + } + + var buf bytes.Buffer + buf.Reset() + _, err = io.Copy(&buf, resp.Body) + if err != nil { + return err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + if !strings.Contains(buf.String(), "验证码已发送到您的手机") && !strings.Contains(buf.String(), "") { + return errors.New("unexpected SMS response: " + buf.String()) + } + + log.Printf("SMS code is sent or still valid") + + fmt.Print("Please enter your SMS code:") + smsCode := "" + _, err = fmt.Scan(&smsCode) + if err != nil { + return err + } + + addr = "https://" + c.server + "/por/login_sms1.csp?apiversion=1" + log.Printf("SMS Request: " + addr) + form := url.Values{ + "svpn_inputsms": {smsCode}, + } + + req, err = http.NewRequest("POST", addr, strings.NewReader(form.Encode())) + req.Header.Set("Cookie", "TWFID="+c.twfID) + + resp, err = c.httpClient.Do(req) + if err != nil { + return err + } + + _, err = io.Copy(&buf, resp.Body) + if err != nil { + return err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + if !strings.Contains(buf.String(), "Auth sms suc") { + debug.PrintStack() + return errors.New("SMS code verification failed: " + buf.String()) + } + + c.twfID = string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) + log.Print("SMS code verification success") + + return nil +} + +func (c *EasyConnectClient) requestConfig() (string, error) { + addr := "https://" + c.server + "/por/conf.csp" + log.Printf("Request: %s", addr) + + req, err := http.NewRequest("GET", addr, nil) + req.Header.Set("Cookie", "TWFID="+c.twfID) + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", err + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, resp.Body) + if err != nil { + return "", err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + return buf.String(), nil +} + +func (c *EasyConnectClient) requestResources() (string, error) { + addr := "https://" + c.server + "/por/rclist.csp" + log.Printf("Request: %s", addr) + + req, err := http.NewRequest("GET", addr, nil) + req.Header.Set("Cookie", "TWFID="+c.twfID) + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", err + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, resp.Body) + if err != nil { + return "", err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + return buf.String(), nil +} + +func (c *EasyConnectClient) requestToken() error { + dialConn, err := net.Dial("tcp", c.server) + defer func(dialConn net.Conn) { + _ = dialConn.Close() + }(dialConn) + conn := utls.UClient(dialConn, &utls.Config{InsecureSkipVerify: true}, utls.HelloGolang) + defer func(conn *utls.UConn) { + _ = conn.Close() + }(conn) + + // When establish an HTTPS connection to server and send a valid request with TWFID to it + // The **TLS ServerHello SessionId** is the first part of token + log.Printf("ECAgent request: /por/conf.csp & /por/rclist.csp") + _, err = io.WriteString( + conn, + "GET /por/conf.csp HTTP/1.1\r\nHost: "+c.server+ + "\r\nCookie: TWFID="+c.twfID+ + "\r\n\r\nGET /por/rclist.csp HTTP/1.1\r\nHost: "+c.server+ + "\r\nCookie: TWFID="+c.twfID+"\r\n\r\n", + ) + if err != nil { + return err + } + + sessionID := hex.EncodeToString(conn.HandshakeState.ServerHello.SessionId) + log.Printf("Server session ID: %s", sessionID) + + buf := make([]byte, 8) + n, err := conn.Read(buf) + if n == 0 || err != nil { + return errors.New("ECAgent request invalid: error " + err.Error() + "\n" + string(buf[:])) + } + + c.token = (*[48]byte)([]byte(sessionID[:31] + "\x00" + c.twfID)) + + log.Printf("Token: %s", hex.EncodeToString(c.token[:])) + + return nil +} + +func (c *EasyConnectClient) requestIP() error { + conn, err := c.tlsConn() + if err != nil { + return err + } + + // Request IP Packet + message := []byte{0x00, 0x00, 0x00, 0x00} + message = append(message, c.token[:]...) + message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff}...) + + n, err := conn.Write(message) + if err != nil { + return err + } + + log.DebugPrintf("Request IP: wrote %d bytes", n) + log.DebugDumpHex(message[:n]) + + reply := make([]byte, 0x80) + n, err = conn.Read(reply) + if err != nil { + return err + } + + log.DebugPrintf("Request IP: read %d bytes", n) + log.DebugDumpHex(reply[:n]) + + if reply[0] != 0x00 { + return errors.New("unexpected request IP reply") + } + + c.ip = reply[4:8] + c.ipReverse = []byte{c.ip[3], c.ip[2], c.ip[1], c.ip[0]} + + log.Printf("Client IP: %s", c.ip.String()) + + // Request IP conn CAN NOT be closed, otherwise tx/rx handshake will fail + go func() { + for { + time.Sleep(time.Second * 10) + runtime.KeepAlive(conn) + } + }() + + return nil +} diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go deleted file mode 100644 index 90725db..0000000 --- a/core/EasyConnectClient.go +++ /dev/null @@ -1,308 +0,0 @@ -package core - -import ( - "errors" - "fmt" - "log" - "net" - "runtime" - "strconv" - "strings" - "time" - - "github.com/mythologyli/zju-connect/core/config" - "github.com/mythologyli/zju-connect/parser" - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -type Forwarding struct { - NetworkType string - BindAddress string - RemoteAddress string -} - -type CustomDNS struct { - HostName string - IP string -} - -var SocksBind string -var SocksUser string -var SocksPasswd string -var HttpBind string -var TunMode bool -var AddRoute bool -var DebugDump bool -var ParseServConfig bool -var ParseZjuConfig bool -var UseZjuDns bool -var TestMultiLine bool -var DnsTTL uint64 -var ProxyAll bool -var ForwardingList []Forwarding -var EnableKeepAlive bool -var ZjuDnsServer string -var SecondaryDnsServer string -var DnsServerBind string -var TunDnsServer string -var CustomDNSList []CustomDNS - -type EasyConnectClient struct { - queryConn net.Conn - clientIp []byte - token *[48]byte - twfId string - - // Gvisor stack - gvisorEndpoint *EasyConnectGvisorEndpoint - gvisorStack *stack.Stack - - // TUN stack - tunEndpoint *EasyConnectTunEndpoint - - server string - username string - password string -} - -func NewEasyConnectClient(server string) *EasyConnectClient { - return &EasyConnectClient{ - server: server, - } -} - -func StartClient(host string, port int, username string, password string, twfId string) { - server := fmt.Sprintf("%s:%d", host, port) - - client := NewEasyConnectClient(server) - - var ip []byte - var err error - if twfId != "" { - if len(twfId) != 16 { - panic("len(twfid) should be 16!") - } - err = client.LoginByTwfId(twfId) - } else { - err = client.Login(username, password) - if err == ERR_NEXT_AUTH_SMS { - fmt.Print(">>>Please enter your sms code<<<:") - smsCode := "" - _, _err := fmt.Scan(&smsCode) - if _err != nil { - panic(_err) - } - - err = client.AuthSMSCode(smsCode) - } else if err == ERR_NEXT_AUTH_TOTP { - fmt.Print(">>>Please enter your TOTP Auth code<<<:") - TOTPCode := "" - _, _err := fmt.Scan(&TOTPCode) - if _err != nil { - panic(_err) - } - - err = client.AuthTOTP(TOTPCode) - } - } - - if TestMultiLine && config.IsServerListAvailable() { - log.Printf("Testing %d servers...", config.GetServerListLen()) - - server := config.GetBestServer() - - if server != "" { - log.Printf("Find best server: %s", server) - - TestMultiLine = false - - parts := strings.Split(server, ":") - host := parts[0] - port, _ := strconv.Atoi(parts[1]) - - log.Printf("Login again...") - - StartClient(host, port, username, password, twfId) - return - } else { - log.Printf("Find best server failed. Connect %s", client.server) - } - } - // check error after trying best server - if err != nil { - log.Fatal(err.Error()) - } - - client.ParseAllConfig() - if ip, err = client.GetClientIp(); err != nil { - log.Fatal(err.Error()) - } else { - log.Printf("Login success, your IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) - } - - if TunMode { - // Use TUN stack - client.tunEndpoint = &EasyConnectTunEndpoint{} - SetupTunStack(ip, client.tunEndpoint) - - // Add routes - if AddRoute && config.IsIpv4SetAvailable() { - ipv4Set := config.GetIpv4Set() - - if err != nil { - log.Printf("Cannot get ipset: %v", err) - } else { - for _, prefix := range ipv4Set.Prefixes() { - log.Printf("Add route to %s", prefix.String()) - _ = client.tunEndpoint.AddRoute(prefix.String()) - } - } - } - - StartProtocolWithTun(client.tunEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) - } else { - // Use Gvisor stack - client.gvisorEndpoint = &EasyConnectGvisorEndpoint{} - client.gvisorStack = SetupGvisorStack(client.clientIp, client.gvisorEndpoint) - - StartProtocolWithGvisor(client.gvisorEndpoint, client.server, client.token, &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, DebugDump) - } - - for _, singleForwarding := range ForwardingList { - go client.ServeForwarding(strings.ToLower(singleForwarding.NetworkType), singleForwarding.BindAddress, singleForwarding.RemoteAddress) - } - - for _, customDNS := range CustomDNSList { - ipAddr := net.ParseIP(customDNS.IP) - if ipAddr == nil { - log.Printf("Custom DNS for host_name %s is invalid, SKIP", customDNS.HostName) - } - SetPermanentDns(customDNS.HostName, ipAddr) - log.Printf("Custom DNS %s -> %s\n", customDNS.HostName, customDNS.IP) - } - - dnsResolve := SetupDnsResolve(ZjuDnsServer, client) - - if DnsServerBind != "" { - go ServeDns(DnsServerBind, dnsResolve) - } - - dialer := Dialer{ - client: client, - } - - if SocksBind != "" { - go ServeSocks5(SocksBind, dialer, dnsResolve) - } - - if HttpBind != "" { - go ServeHttp(HttpBind, dialer, dnsResolve) - } - - if EnableKeepAlive { - go KeepAlive(dnsResolve.remoteUDPResolver) - } - - for { - runtime.KeepAlive(client) - time.Sleep(time.Second * 10) - } -} - -func (client *EasyConnectClient) Login(username string, password string) error { - client.username = username - client.password = password - - // Web login part (Get TWFID & ECAgent Token => Final token used in binary stream) - twfId, err := WebLogin(client.server, client.username, client.password) - - // Store TWFID for AuthSMS - client.twfId = twfId - if err != nil { - return err - } - - return client.LoginByTwfId(twfId) -} - -func (client *EasyConnectClient) AuthSMSCode(code string) error { - if client.twfId == "" { - return errors.New("SMS Auth not required") - } - - twfId, err := AuthSms(client.server, client.username, client.password, client.twfId, code) - if err != nil { - return err - } - - return client.LoginByTwfId(twfId) -} - -func (client *EasyConnectClient) AuthTOTP(code string) error { - if client.twfId == "" { - return errors.New("TOTP Auth not required") - } - - twfId, err := TOTPAuth(client.server, client.username, client.password, client.twfId, code) - if err != nil { - return err - } - - return client.LoginByTwfId(twfId) -} - -func (client *EasyConnectClient) LoginByTwfId(twfId string) error { - agentToken, err := ECAgentToken(client.server, twfId) - if err != nil { - return err - } - - parser.ParseConfLists(client.server, twfId, DebugDump) - - client.token = (*[48]byte)([]byte(agentToken + twfId)) - return nil -} - -func (client *EasyConnectClient) ParseAllConfig() { - // Parse Server config - if ParseServConfig { - parser.ParseResourceLists(client.server, client.twfId, DebugDump) - } - - // Parse ZJU config - if ParseZjuConfig { - parser.ParseZjuDnsRules(DebugDump) - parser.ParseZjuIpv4Rules(DebugDump) - parser.ParseZjuForceProxyRules(DebugDump) - } - - err := config.GenerateIpv4Set() - if err != nil { - return - } -} - -func (client *EasyConnectClient) GetClientIp() ([]byte, error) { - var err error - // Query IP (keep the gvisorConnection used, so it's not closed too early, otherwise i/o stream will be closed) - client.clientIp, client.queryConn, err = QueryIp(client.server, client.token, DebugDump) - if err != nil { - return nil, err - } - - return client.clientIp, nil -} - -func (client *EasyConnectClient) ServeForwarding(networkType string, bindAddress string, remoteAddress string) { - if networkType == "tcp" { - log.Printf("Port forwarding (tcp): %s <- %s", bindAddress, remoteAddress) - - ServeTcpForwarding(bindAddress, remoteAddress, client) - } else if networkType == "udp" { - log.Printf("Port forwarding (udp): %s <- %s", bindAddress, remoteAddress) - - ServeUdpForwarding(bindAddress, remoteAddress, client) - } else { - log.Println("Only TCP/UDP forwarding is supported yet. Aborting.") - } -} diff --git a/core/config/Constants.go b/core/config/Constants.go deleted file mode 100644 index 8bacdb4..0000000 --- a/core/config/Constants.go +++ /dev/null @@ -1,4 +0,0 @@ -package config - -const PathConf string = "/por/conf.csp" -const PathRlist string = "/por/rclist.csp" diff --git a/core/config/DnsRules.go b/core/config/DnsRules.go deleted file mode 100644 index 2d6757f..0000000 --- a/core/config/DnsRules.go +++ /dev/null @@ -1,44 +0,0 @@ -package config - -import ( - "log" - - "github.com/cornelk/hashmap" -) - -// domain[ip] -var dnsRules *hashmap.Map[string, string] -var dnsIps []string - -func AppendSingleDnsRule(domain, ip string, debug bool) { - if dnsRules == nil { - dnsRules = hashmap.New[string, string]() - } - - if debug { - log.Printf("AppendSingleDnsRule: %s[%s]", domain, ip) - } - - dnsRules.Set(domain, ip) - dnsIps = append(dnsIps, ip) -} - -func GetSingleDnsRule(domain string) (string, bool) { - return dnsRules.Get(domain) -} - -func IsDnsRuleAvailable() bool { - return dnsRules != nil -} - -func GetDnsRuleLen() int { - if IsDnsRuleAvailable() { - return dnsRules.Len() - } else { - return 0 - } -} - -func GetDnsIps() []string { - return dnsIps -} diff --git a/core/config/DomainWhiteList.go b/core/config/DomainWhiteList.go deleted file mode 100644 index 161907a..0000000 --- a/core/config/DomainWhiteList.go +++ /dev/null @@ -1,48 +0,0 @@ -package config - -import ( - "github.com/bobesa/go-domain-util/domainutil" - "github.com/cornelk/hashmap" - "log" -) - -// domain[[]int {min, max}] -var domainRules *hashmap.Map[string, []int] - -func AppendSingleDomainRule(host string, ports []int, debug bool) { - if domainRules == nil { - domainRules = hashmap.New[string, []int]() - } - - var domain = domainutil.Domain(host) - if domain == "" { - domain = host - } - - if debug { - log.Printf("AppendSingleDomainRule: %s[%v]", domain, ports) - } - - domainRules.Set(domain, ports) -} - -func GetSingleDomainRule(host string) ([]int, bool) { - var domain = domainutil.Domain(host) - if domain == "" { - domain = host - } - - return domainRules.Get(domain) -} - -func IsDomainRuleAvailable() bool { - return domainRules != nil -} - -func GetDomainRuleLen() int { - if IsDomainRuleAvailable() { - return domainRules.Len() - } else { - return 0 - } -} diff --git a/core/config/Ipv4RangeWhiteList.go b/core/config/Ipv4RangeWhiteList.go deleted file mode 100644 index d5539ac..0000000 --- a/core/config/Ipv4RangeWhiteList.go +++ /dev/null @@ -1,42 +0,0 @@ -package config - -import ( - "log" -) - -var Ipv4RangeRules *[]Ipv4RangeRule - -// Ipv4RangeRule Ipv4 rule with range -type Ipv4RangeRule struct { - Rule string - Ports []int - CIDR bool -} - -func AppendSingleIpv4RangeRule(rule string, ports []int, cidr bool, debug bool) { - if Ipv4RangeRules == nil { - Ipv4RangeRules = &[]Ipv4RangeRule{} - } - - if debug { - log.Printf("AppendSingleIpv4RangeRule: %s%v cidr: %v", rule, ports, cidr) - } - - *Ipv4RangeRules = append(*Ipv4RangeRules, Ipv4RangeRule{Rule: rule, Ports: ports, CIDR: cidr}) -} - -func GetIpv4Rules() *[]Ipv4RangeRule { - return Ipv4RangeRules -} - -func IsIpv4RuleAvailable() bool { - return Ipv4RangeRules != nil -} - -func GetIpv4RuleLen() int { - if IsDomainRuleAvailable() { - return len(*Ipv4RangeRules) - } else { - return 0 - } -} diff --git a/core/config/Ipv4Set.go b/core/config/Ipv4Set.go deleted file mode 100644 index 82a3681..0000000 --- a/core/config/Ipv4Set.go +++ /dev/null @@ -1,48 +0,0 @@ -package config - -import ( - "inet.af/netaddr" - "strings" -) - -var Ipv4Set *netaddr.IPSet - -func GenerateIpv4Set() error { - ipv4SetBuilder := netaddr.IPSetBuilder{} - - dnsIps := GetDnsIps() - if dnsIps != nil { - for _, ip := range dnsIps { - ipv4SetBuilder.Add(netaddr.MustParseIP(ip)) - } - } - - ipv4RangeRules := GetIpv4Rules() - if ipv4RangeRules != nil { - for _, rule := range *ipv4RangeRules { - if rule.CIDR { - ipv4SetBuilder.AddPrefix(netaddr.MustParseIPPrefix(rule.Rule)) - } else { - ip1 := netaddr.MustParseIP(strings.Split(rule.Rule, "~")[0]) - ip2 := netaddr.MustParseIP(strings.Split(rule.Rule, "~")[1]) - ipv4SetBuilder.AddRange(netaddr.IPRangeFrom(ip1, ip2)) - } - } - } - - var err error - Ipv4Set, err = ipv4SetBuilder.IPSet() - if err != nil { - return err - } - - return nil -} - -func IsIpv4SetAvailable() bool { - return Ipv4Set != nil -} - -func GetIpv4Set() *netaddr.IPSet { - return Ipv4Set -} diff --git a/core/config/ZjuForceProxyRules.go b/core/config/ZjuForceProxyRules.go deleted file mode 100644 index 72284ba..0000000 --- a/core/config/ZjuForceProxyRules.go +++ /dev/null @@ -1,43 +0,0 @@ -package config - -import ( - "container/list" - "log" - "strings" -) - -var ZjuForceProxyRules *list.List - -func AppendSingleZjuForceProxyRule(keyword string, debug bool) { - if ZjuForceProxyRules == nil { - ZjuForceProxyRules = list.New() - } - - if debug { - log.Printf("AppendSingleZjuForceProxyRule: %s", keyword) - } - - ZjuForceProxyRules.PushBack(keyword) -} - -func IsInZjuForceProxyRule(domain string) bool { - for e := ZjuForceProxyRules.Front(); e != nil; e = e.Next() { - if strings.Contains(domain, e.Value.(string)) { - return true - } - } - - return false -} - -func IsZjuForceProxyRuleAvailable() bool { - return ZjuForceProxyRules != nil -} - -func GetZjuForceProxyRuleLen() int { - if IsZjuForceProxyRuleAvailable() { - return ZjuForceProxyRules.Len() - } else { - return 0 - } -} diff --git a/core/config/conf.go b/core/config/conf.go deleted file mode 100644 index c4f296b..0000000 --- a/core/config/conf.go +++ /dev/null @@ -1,207 +0,0 @@ -package config - -type ConfWeb struct { - Conf Conf - LocalConf struct { - Session struct { - NameFix string `xml:"nameFix,attr" json:"nameFix"` - } `xml:"Session,attr" json:"Session"` - } `xml:"LocalConf,attr" json:"LocalConf"` -} - -type Conf struct { - EMM struct { - NetworkWhiteList struct { - ForbidIntranet string `xml:"forbid_intranet,attr"` - ForbidInternet string `xml:"forbid_internet,attr"` - } `xml:"NetworkWhiteList"` - TicketEnable string `xml:"TicketEnable"` - TicketLoginType string `xml:"TicketLoginType"` - TicketLoginCode string `xml:"TicketLoginCode"` - MdmPolicyEnable string `xml:"MdmPolicyEnable"` - RdbUpdateTime string `xml:"RdbUpdateTime"` - } `xml:"EMM"` - AworkName string `xml:"AworkName"` - WebSecLogin struct { - LastLoginRes string `xml:"LastLoginRes"` - LastLoginTime string `xml:"LastLoginTime"` - LastLoginType string `xml:"LastLoginType"` - LastOsType string `xml:"LastOsType"` - LastLoginIp string `xml:"LastLoginIp"` - LastLoginFails string `xml:"LastLoginFails"` - LastLoginSuccTime string `xml:"LastLoginSuccTime"` - LastLoginSwitch string `xml:"LastLoginSwitch"` - } `xml:"WebSecLogin"` - SysTray struct { - Enable string `xml:"enable,attr"` - SSLTrayIconMd5 string `xml:"SSLTrayIconMd5,attr"` - SysShortCutName string `xml:"SysShortCutName,attr"` - SSLTrayIconPath string `xml:"SSLTrayIconPath,attr"` - } `xml:"SysTray"` - Webagent struct { - Enable string `xml:"enable,attr"` - Address string `xml:"address,attr"` - } `xml:"Webagent"` - Mline struct { - Enable string `xml:"enable,attr"` - Number string `xml:"number,attr"` - List string `xml:"list,attr"` - Interval string `xml:"interval,attr"` - Timeout string `xml:"timeout,attr"` - } `xml:"Mline"` - Vpnline struct { - Address string `xml:"address,attr"` - } `xml:"Vpnline"` - Htp struct { - Enable string `xml:"enable,attr"` - Auto string `xml:"auto,attr"` - Param string `xml:"param,attr"` - Port string `xml:"port,attr"` - Mtu string `xml:"mtu,attr"` - } `xml:"Htp"` - WebCache struct { - Enable string `xml:"enable,attr"` - Mode string `xml:"mode,attr"` - Count string `xml:"count,attr"` - URL string `xml:"url,attr"` - } `xml:"WebCache"` - WebOpt string `xml:"WebOpt"` - Bandwidth struct { - Recvlimit string `xml:"recvlimit,attr"` - Sendlimit string `xml:"sendlimit,attr"` - } `xml:"Bandwidth"` - TcpApplication struct { - UserMode string `xml:"userMode,attr"` - Compress string `xml:"compress,attr"` - Maxthread string `xml:"maxthread,attr"` - Maxsession string `xml:"maxsession,attr"` - } `xml:"TcpApplication"` - L3VPN struct { - IptunDns string `xml:"iptunDns,attr"` - IptunDnsBak string `xml:"iptunDnsBak,attr"` - } `xml:"L3VPN"` - SCache struct { - Enable string `xml:"enable,attr"` - Gwid string `xml:"gwid,attr"` - ID string `xml:"id,attr"` - Cfgmd5 string `xml:"cfgmd5,attr"` - Dllmd5 string `xml:"dllmd5,attr"` - } `xml:"SCache"` - DnsRuleExceptions struct { - Text string `xml:",chardata"` - Exception []string `xml:"Exception"` - } `xml:"DnsRuleExceptions"` - UsbKey struct { - Version string `xml:"version,attr"` - Certinput string `xml:"certinput,attr"` - Typeinfo string `xml:"typeinfo,attr"` - Typecount string `xml:"typecount,attr"` - } `xml:"UsbKey"` - CDC struct { - Enable string `xml:"enable,attr"` - LogKey string `xml:"LogKey,attr"` - UseUsersLog string `xml:"useUsersLog,attr"` - LogInterval string `xml:"LogInterval,attr"` - GrpIdInt string `xml:"GrpIdInt,attr"` - AuthPast string `xml:"AuthPast,attr"` - } `xml:"CDC"` - Autorule struct { - Enable string `xml:"enable,attr"` - EnableLimit string `xml:"enable_limit,attr"` - GatherRuleLimit string `xml:"gather_rule_limit,attr"` - Mode string `xml:"mode,attr"` - Count string `xml:"count,attr"` - RuleLimit string `xml:"rule_limit,attr"` - Domain string `xml:"domain,attr"` - } `xml:"Autorule"` - Other struct { - LoginName string `xml:"login_name,attr"` - SddnEnable string `xml:"sddn_enable,attr"` - Sslctx string `xml:"sslctx,attr"` - Displayhost string `xml:"displayhost,attr"` - DenyNormalAccess string `xml:"denyNormalAccess,attr"` - EnableAutoRelogin string `xml:"enableAutoRelogin,attr"` - Autorelogininterval string `xml:"autorelogininterval,attr"` - Autorelogintimes string `xml:"autorelogintimes,attr"` - IsRelogin string `xml:"isRelogin,attr"` - RelogTimeLast string `xml:"RelogTimeLast,attr"` - RelogIPLast string `xml:"RelogIPLast,attr"` - AutoStartCS string `xml:"autoStartCS,attr"` - PwpRemindMsg string `xml:"pwp_remind_msg,attr"` - Svpnlanaddr string `xml:"svpnlanaddr,attr"` - IsPubUser string `xml:"isPubUser,attr"` - IsExtern string `xml:"isExtern,attr"` - IsHidUser string `xml:"isHidUser,attr"` - Enablesavepwd string `xml:"enablesavepwd,attr"` - EnableCsRcWindows string `xml:"enable_cs_rc_windows,attr"` - Enableautologin string `xml:"enableautologin,attr"` - ChgPwdEnable string `xml:"chg_pwd_enable,attr"` - ChgPhoneEnable string `xml:"chg_phone_enable,attr"` - ChgNoteEnable string `xml:"chg_note_enable,attr"` - AuthSms string `xml:"auth_sms,attr"` - Mobilephone string `xml:"mobilephone,attr"` - UserNote string `xml:"user_note,attr"` - PswMinlen string `xml:"psw_minlen,attr"` - PptpGrpolicy string `xml:"pptp_grpolicy,attr"` - PptpDetaddr string `xml:"pptp_detaddr,attr"` - Vpntype string `xml:"vpntype,attr"` - Deviceversion string `xml:"deviceversion,attr"` - UserAuthPast string `xml:"UserAuthPast,attr"` - UserPwd string `xml:"UserPwd,attr"` - AccessibleAddr string `xml:"AccessibleAddr,attr"` - } `xml:"Other"` - RemoteApp struct { - AccountPolicy string `xml:"account_policy,attr"` - SessionKeeptime string `xml:"session_keeptime,attr"` - MapDisk string `xml:"MapDisk,attr"` - MapClipboard string `xml:"MapClipboard,attr"` - MapPrinter string `xml:"MapPrinter,attr"` - VirtualPrinter string `xml:"VirtualPrinter,attr"` - RappResuse string `xml:"rapp_resuse,attr"` - VirtualPrintMode string `xml:"VirtualPrintMode,attr"` - UseRdp string `xml:"UseRdp,attr"` - PrintPaper string `xml:"PrintPaper"` - PrivateFolderName string `xml:"PrivateFolderName"` - SRAPOption struct { - LossCompressor struct { - Type string `xml:"type,attr"` - Ratio string `xml:"ratio,attr"` - Quality string `xml:"quality,attr"` - } `xml:"LossCompressor"` - GlyphCompress struct { - Option string `xml:"option,attr"` - JpegQuality string `xml:"jpeg_quality,attr"` - } `xml:"GlyphCompress"` - NoLossCompressor struct { - BmpCompressor string `xml:"bmp_compressor,attr"` - CompressorType string `xml:"compressor_type,attr"` - } `xml:"NoLossCompressor"` - CacheHash struct { - OpType string `xml:"op_type,attr"` - } `xml:"CacheHash"` - StreamMerge struct { - Type string `xml:"type,attr"` - Threshold string `xml:"threshold,attr"` - Uptime string `xml:"uptime,attr"` - } `xml:"StreamMerge"` - } `xml:"SRAPOption"` - } `xml:"RemoteApp"` - SSLCipherSuite struct { - EC string `xml:"EC"` - TCP string `xml:"TCP"` - L3VPN string `xml:"L3VPN"` - } `xml:"SSLCipherSuite"` - SSLEigenvalue struct { - TCP string `xml:"TCP"` - L3VPN string `xml:"L3VPN"` - } `xml:"SSLEigenvalue"` - Logo struct { - Custom string `xml:"custom,attr"` - LogoMd5 string `xml:"LogoMd5,attr"` - LogoPath string `xml:"LogoPath,attr"` - } `xml:"Logo"` - WebHttpEnable struct { - HttpPort string `xml:"httpPort,attr"` - Enable string `xml:"enable,attr"` - } `xml:"WebHttpEnable"` -} diff --git a/core/config/rlist.go b/core/config/rlist.go deleted file mode 100644 index c94d5b4..0000000 --- a/core/config/rlist.go +++ /dev/null @@ -1,276 +0,0 @@ -package config - -type RcData struct { - ID string `xml:"id,attr"` - Name string `xml:"name,attr"` - Type string `xml:"type,attr"` - Proto string `xml:"proto,attr"` - Svc string `xml:"svc,attr"` - Host string `xml:"host,attr"` - Port string `xml:"port,attr"` - EnableDisguise string `xml:"enable_disguise,attr"` - Note string `xml:"note,attr"` - Attr string `xml:"attr,attr"` - AppPath string `xml:"app_path,attr"` - RcGrpID string `xml:"rc_grp_id,attr"` - RcLogo string `xml:"rc_logo,attr"` - Authorization string `xml:"authorization,attr"` - AuthSpID string `xml:"auth_sp_id,attr"` - Selectid string `xml:"selectid,attr"` -} - -type ResourceWeb struct { - Resource Resource - LocalConf struct { - Session struct { - NameFix string `xml:"nameFix,attr" json:"nameFix"` - } `xml:"Session,attr" json:"Session"` - } `xml:"LocalConf,attr" json:"LocalConf"` -} - -type Resource struct { - Rcs struct { - Rc []struct { - ID string `xml:"id,attr"` - Name string `xml:"name,attr"` - Type string `xml:"type,attr"` - Proto string `xml:"proto,attr"` - Svc string `xml:"svc,attr"` - Host string `xml:"host,attr"` - Port string `xml:"port,attr"` - EnableDisguise string `xml:"enable_disguise,attr"` - Note string `xml:"note,attr"` - Attr string `xml:"attr,attr"` - AppPath string `xml:"app_path,attr"` - RcGrpID string `xml:"rc_grp_id,attr"` - RcLogo string `xml:"rc_logo,attr"` - Authorization string `xml:"authorization,attr"` - AuthSpID string `xml:"auth_sp_id,attr"` - Selectid string `xml:"selectid,attr"` - } `xml:"Rc"` - } `xml:"Rcs"` - RcGroups struct { - Group []struct { - ID string `xml:"id,attr"` - Name string `xml:"name,attr"` - Type string `xml:"type,attr"` - Logowidth string `xml:"logowidth,attr"` - Logoheight string `xml:"logoheight,attr"` - LoadBalance string `xml:"load_balance,attr"` - ShowNote string `xml:"show_note,attr"` - } `xml:"Group"` - } `xml:"RcGroups"` - SD struct { - Global struct { - Enable string `xml:"enable,attr"` - SDRedirectFile string `xml:"SDRedirectFile,attr"` - } `xml:"Global"` - Policy struct { - ID string `xml:"id,attr"` - } `xml:"Policy"` - DesktopFormat struct { - Safedesk string `xml:"safedesk,attr"` - Com string `xml:"com,attr"` - Infrared string `xml:"infrared,attr"` - Bluetooth string `xml:"bluetooth,attr"` - Printer string `xml:"printer,attr"` - Changedesk string `xml:"changedesk,attr"` - RegisterUnMon string `xml:"register_un_mon,attr"` - SafedeskLocalTransport string `xml:"safedesk_local_transport,attr"` - RappInSafedesk string `xml:"rapp_in_safedesk,attr"` - } `xml:"DesktopFormat"` - Internet struct { - Tempbuf string `xml:"tempbuf,attr"` - History string `xml:"history,attr"` - Tables string `xml:"tables,attr"` - Cookies string `xml:"cookies,attr"` - } `xml:"Internet"` - Iplist struct { - Iplist string `xml:"iplist,attr"` - } `xml:"iplist"` - Rclist struct { - Rclist string `xml:"rclist,attr"` - } `xml:"rclist"` - } `xml:"SD"` - Dns struct { - Dnsserver string `xml:"dnsserver,attr"` - Data string `xml:"data,attr"` - Filter string `xml:"filter,attr"` - } `xml:"Dns"` - FileLock struct { - Data string `xml:"data,attr"` - Filecount string `xml:"filecount,attr"` - Maxfilecount string `xml:"maxfilecount,attr"` - } `xml:"FileLock"` - UB struct { - IndexInner string `xml:"index_inner,attr"` - Ubdllinfo string `xml:"ubdllinfo,attr"` - } `xml:"UB"` - Easylink struct { - ElnkRc struct { - ID string `xml:"Id"` - ElnkRewrite string `xml:"ElnkRewrite"` - Mode string `xml:"Mode"` - MapAddr string `xml:"MapAddr"` - } `xml:"ElnkRc"` - } `xml:"Easylink"` - Other struct { - DefaultRcId string `xml:"defaultRcId,attr"` - AllocateVip string `xml:"allocateVip,attr"` - Balanceinfo string `xml:"balanceinfo,attr"` - } `xml:"Other"` - UrlWarrentRules struct { - Enable string `xml:"enable,attr"` - Filter string `xml:"filter,attr"` - Tips string `xml:"tips,attr"` - } `xml:"UrlWarrentRules"` - MSGINFO string `xml:"MSG_INFO"` - WebSsoInfos string `xml:"WebSsoInfos"` - VSP struct { - Misc struct { - SDTitle string `xml:"SDTitle,attr"` - ShowRcInSD string `xml:"ShowRcInSD,attr"` - ShowUserShortCutIconInSD string `xml:"ShowUserShortCutIconInSD,attr"` - } `xml:"Misc"` - WallPaper struct { - Type string `xml:"Type,attr"` - URL string `xml:"Url,attr"` - Compress string `xml:"Compress,attr"` - MD5 string `xml:"MD5,attr"` - } `xml:"WallPaper"` - Inject struct { - Type string `xml:"Type,attr"` - } `xml:"Inject"` - NavBar struct { - IconUrl string `xml:"IconUrl,attr"` - MD5 string `xml:"MD5,attr"` - } `xml:"NavBar"` - OfflineVisit struct { - Enable string `xml:"Enable,attr"` - VisitTime string `xml:"VisitTime,attr"` - IsBind string `xml:"IsBind,attr"` - } `xml:"OfflineVisit"` - RedirectData struct { - ProcessType string `xml:"ProcessType,attr"` - UseCustomDefine string `xml:"UseCustomDefine,attr"` - } `xml:"RedirectData"` - Crypto struct { - Type string `xml:"Type,attr"` - Length string `xml:"Length,attr"` - Ctx string `xml:"Ctx,attr"` - } `xml:"Crypto"` - ReDirect struct { - NameRule string `xml:"NameRule,attr"` - Ctx string `xml:"Ctx,attr"` - } `xml:"ReDirect"` - FileExport struct { - Enable string `xml:"Enable,attr"` - AuditLog string `xml:"AuditLog,attr"` - MaxAuditFileSize string `xml:"MaxAuditFileSize,attr"` - Compress string `xml:"Compress,attr"` - } `xml:"FileExport"` - ActiveXProxyInstall struct { - Enable string `xml:"Enable,attr"` - } `xml:"ActiveXProxyInstall"` - LocalCommunication struct { - Enable string `xml:"Enable,attr"` - } `xml:"LocalCommunication"` - ExecutableProcess struct { - Enable string `xml:"Enable,attr"` - } `xml:"ExecutableProcess"` - } `xml:"VSP"` - StaticSd struct { - SpecileFile string `xml:"SpecileFile"` - SpecileKey struct { - Text string `xml:",chardata"` - KeyList []string `xml:"KeyList"` - } `xml:"SpecileKey"` - SpecileProc struct { - ProcList string `xml:"ProcList"` - } `xml:"SpecileProc"` - DefaultExecutableProcess struct { - WhiteListItem []struct { - FileName string `xml:"FileName"` - Value string `xml:"Value"` - VerifyType string `xml:"VerifyType"` - } `xml:"WhiteListItem"` - } `xml:"DefaultExecutableProcess"` - NotifySize string `xml:"NotifySize"` - EnTopTool string `xml:"EnTopTool"` - ActiveXProxyProcess struct { - Process struct { - Name string `xml:"Name,attr"` - } `xml:"Process"` - } `xml:"ActiveXProxyProcess"` - RedirectObjectRule struct { - Count string `xml:"Count,attr"` - Rule []struct { - ObjectType string `xml:"ObjectType,attr"` - Disable string `xml:"Disable,attr"` - MatchRule string `xml:"MatchRule,attr"` - ObjectName string `xml:"ObjectName,attr"` - ProcessName string `xml:"ProcessName,attr"` - } `xml:"Rule"` - } `xml:"RedirectObjectRule"` - DeniService struct { - Count string `xml:"Count,attr"` - Service []struct { - Name string `xml:"name,attr"` - } `xml:"Service"` - } `xml:"DeniService"` - InterceptSet struct { - Count string `xml:"Count,attr"` - Item []struct { - Type string `xml:"Type,attr"` - Name string `xml:"Name,attr"` - } `xml:"Item"` - } `xml:"InterceptSet"` - InjectAgentWhiteList struct { - Count string `xml:"Count,attr"` - ProcName []struct { - Name string `xml:"name,attr"` - } `xml:"ProcName"` - } `xml:"InjectAgentWhiteList"` - VBRule struct { - Text string `xml:",chardata"` - Enable string `xml:"Enable,attr"` - ProcName []string `xml:"ProcName"` - } `xml:"VBRule"` - NetDrvInfo struct { - WorkMode string `xml:"WorkMode,attr"` - WhiteListItem []struct { - FileName string `xml:"FileName,attr"` - VerifyType string `xml:"VerifyType,attr"` - Value string `xml:"Value,attr"` - } `xml:"WhiteListItem"` - } `xml:"NetDrvInfo"` - DenyProcess struct { - Item []struct { - FileName string `xml:"FileName,attr"` - Info string `xml:"Info,attr"` - } `xml:"Item"` - } `xml:"DenyProcess"` - WhitePipeOfProcess struct { - EnablePipeRule string `xml:"EnablePipeRule,attr"` - Item []struct { - FileName string `xml:"FileName,attr"` - Info string `xml:"Info,attr"` - PipeName string `xml:"PipeName,attr"` - } `xml:"Item"` - } `xml:"WhitePipeOfProcess"` - Drivers struct { - Enable string `xml:"Enable,attr"` - Driver []struct { - Name string `xml:"Name,attr"` - Enable string `xml:"Enable,attr"` - } `xml:"Driver"` - } `xml:"Drivers"` - UsbWhiteProcess struct { - Rule []struct { - ProcessName string `xml:"ProcessName,attr"` - Info string `xml:"Info,attr"` - Type string `xml:"Type,attr"` - } `xml:"Rule"` - } `xml:"UsbWhiteProcess"` - } `xml:"StaticSd"` -} diff --git a/core/dialer.go b/core/dialer.go deleted file mode 100644 index ae884c8..0000000 --- a/core/dialer.go +++ /dev/null @@ -1,175 +0,0 @@ -package core - -import ( - "context" - "errors" - "github.com/mythologyli/zju-connect/core/config" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" - "inet.af/netaddr" - "log" - "net" - "strconv" - "strings" -) - -type Dialer struct { - client *EasyConnectClient -} - -func dialDirect(ctx context.Context, network, addr string) (net.Conn, error) { - goDialer := &net.Dialer{} - goDial := goDialer.DialContext - - log.Printf("%s -> DIRECT", addr) - - return goDial(ctx, network, addr) -} - -func (dialer *Dialer) DialIpAndPort(ctx context.Context, network, addr string) (net.Conn, error) { - // Check if is IPv6 - if strings.Count(addr, ":") > 1 { - return dialDirect(ctx, network, addr) - } - - parts := strings.Split(addr, ":") - - // in normal situation, addr must be a pure valid IP - // because we use `DnsResolve` to resolve domain name before call `DialIpAndPort` - host := parts[0] - port, err := strconv.Atoi(parts[1]) - if err != nil { - return nil, errors.New("Invalid port in address: " + addr) - } - - var isInZjuForceProxyRule = false - var useProxy = false - - var target *net.IPAddr - - if pureIp := net.ParseIP(host); pureIp != nil { - // host is pure IP format, e.g.: "10.10.10.10" - target = &net.IPAddr{IP: pureIp} - } else { - // illegal situation - log.Printf("Illegal situation, host is not pure IP format: %s", host) - return dialDirect(ctx, network, addr) - } - - if ProxyAll { - useProxy = true - } - - if res := ctx.Value("USE_PROXY"); res != nil && res.(bool) { - useProxy = true - } - - if !useProxy && config.IsDomainRuleAvailable() { - _, useProxy = config.GetSingleDomainRule(target.IP.String()) - } - - if !useProxy && config.IsZjuForceProxyRuleAvailable() { - isInZjuForceProxyRule = config.IsInZjuForceProxyRule(target.IP.String()) - - if isInZjuForceProxyRule { - useProxy = true - } - } - - if !useProxy && config.IsIpv4SetAvailable() { - if DebugDump { - log.Printf("IPv4 set is available ") - } - ipv4Set := config.GetIpv4Set() - ip, ok := netaddr.FromStdIP(target.IP) - if ok { - if ipv4Set.Contains(ip) { - useProxy = true - } - } - } - - if useProxy { - if TunMode { - if network == "tcp" { - log.Printf("%s -> PROXY", addr) - - addrTarget := net.TCPAddr{ - IP: target.IP, - Port: port, - } - - bind := net.TCPAddr{ - IP: net.IP(dialer.client.clientIp), - Port: 0, - } - - return net.DialTCP(network, &bind, &addrTarget) - } else if network == "udp" { - log.Printf("%s -> PROXY", addr) - - addrTarget := net.UDPAddr{ - IP: target.IP, - Port: port, - } - - bind := net.UDPAddr{ - IP: net.IP(dialer.client.clientIp), - Port: 0, - } - - return net.DialUDP(network, &bind, &addrTarget) - } else { - log.Printf("Proxy only support TCP/UDP. Connection to %s will use direct connection.", addr) - return dialDirect(ctx, network, addr) - } - } else { - addrTarget := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(port), - Addr: tcpip.AddrFromSlice(target.IP), - } - - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.AddrFromSlice(dialer.client.clientIp), - } - - if network == "tcp" { - log.Printf("%s -> PROXY", addr) - return gonet.DialTCPWithBind(context.Background(), dialer.client.gvisorStack, bind, addrTarget, header.IPv4ProtocolNumber) - } else if network == "udp" { - log.Printf("%s -> PROXY", addr) - return gonet.DialUDP(dialer.client.gvisorStack, &bind, &addrTarget, header.IPv4ProtocolNumber) - } else { - log.Printf("Proxy only support TCP/UDP. Connection to %s will use direct connection.", addr) - return dialDirect(ctx, network, addr) - } - } - } else { - return dialDirect(ctx, network, addr) - } -} - -func (dialer *Dialer) Dial(ctx context.Context, dnsResolve *DnsResolve, network string, addr string) (net.Conn, error) { - // Check if is IPv6 - if strings.Count(addr, ":") > 1 { - return dialDirect(ctx, network, addr) - } - - parts := strings.Split(addr, ":") - host := parts[0] - port := parts[1] - - ctx, ip, err := dnsResolve.Resolve(ctx, host) - if err != nil { - return nil, err - } - - if strings.Count(ip.String(), ":") > 0 { - return dialDirect(ctx, network, addr) - } - - return dialer.DialIpAndPort(ctx, network, ip.String()+":"+port) -} diff --git a/core/dns_cache.go b/core/dns_cache.go deleted file mode 100644 index 40860f2..0000000 --- a/core/dns_cache.go +++ /dev/null @@ -1,61 +0,0 @@ -package core - -import ( - "log" - "net" - "sync" - "time" - - "github.com/patrickmn/go-cache" -) - -// only store domain -> ip, don't store ip -> ip -var dnsCaches *DnsCache -var once sync.Once - -type DnsCache struct { - cache *cache.Cache -} - -func GetDnsCache(host string) (net.IP, bool) { - once.Do(func() { - dnsCaches = &DnsCache{ - cache: cache.New(time.Duration(DnsTTL)*time.Second, time.Duration(DnsTTL)*2*time.Second), - } - }) - if item, found := dnsCaches.cache.Get(host); found { - if DebugDump { - log.Printf("GetDnsCache: %s -> %s", host, item.(net.IP).String()) - } - return item.(net.IP), found - } else { - if DebugDump { - log.Printf("GetDnsCache: %s -> not found", host) - } - return nil, found - } -} - -func SetDnsCache(host string, ip net.IP) { - once.Do(func() { - dnsCaches = &DnsCache{ - cache: cache.New(time.Duration(DnsTTL)*time.Second, time.Duration(DnsTTL)*2*time.Second), - } - }) - if DebugDump { - log.Printf("SetDnsCache: %s -> %s", host, ip.String()) - } - dnsCaches.cache.Set(host, ip, cache.DefaultExpiration) -} - -func SetPermanentDns(host string, ip net.IP) { - once.Do(func() { - dnsCaches = &DnsCache{ - cache: cache.New(time.Duration(DnsTTL)*time.Second, time.Duration(DnsTTL)*2*time.Second), - } - }) - if DebugDump { - log.Printf("SetPermantDnsCache: %s -> %s", host, ip.String()) - } - dnsCaches.cache.Set(host, ip, cache.NoExpiration) -} diff --git a/core/dns_resolve.go b/core/dns_resolve.go deleted file mode 100644 index 875daf2..0000000 --- a/core/dns_resolve.go +++ /dev/null @@ -1,211 +0,0 @@ -package core - -import ( - "github.com/mythologyli/zju-connect/core/config" - "golang.org/x/net/context" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" - "log" - "net" - "sync" - "time" -) - -type DnsResolve struct { - remoteUDPResolver *net.Resolver - remoteTCPResolver *net.Resolver - secondaryResolver *net.Resolver - timer *time.Timer - useTCP bool - lock sync.RWMutex -} - -func (resolve *DnsResolve) ResolveWithSecondaryDns(ctx context.Context, host string) (context.Context, net.IP, error) { - if targets, err := resolve.secondaryResolver.LookupIP(ctx, "ip4", host); err != nil { - log.Printf("Resolve IPv4 addr failed using secondary DNS: " + host + ". Try IPv6 addr.") - - if targets, err = resolve.secondaryResolver.LookupIP(ctx, "ip6", host); err != nil { - log.Printf("Resolve IPv6 addr failed using secondary DNS: " + host + ". Reject connection.") - return ctx, nil, err - } else { - log.Printf("%s -> %s", host, targets[0].String()) - return ctx, targets[0], nil - } - } else { - log.Printf("%s -> %s", host, targets[0].String()) - return ctx, targets[0], nil - } -} - -func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Context, net.IP, error) { - var useProxy = false - if config.IsZjuForceProxyRuleAvailable() { - if isInZjuForceProxyRule := config.IsInZjuForceProxyRule(host); isInZjuForceProxyRule { - useProxy = true - } - } - if !useProxy && config.IsDomainRuleAvailable() { - if _, found := config.GetSingleDomainRule(host); found { - useProxy = true - } - } - - ctx = context.WithValue(ctx, "USE_PROXY", useProxy) - - if cachedIP, found := GetDnsCache(host); found { - log.Printf("%s -> %s", host, cachedIP.String()) - return ctx, cachedIP, nil - } - - if config.IsDnsRuleAvailable() { - if ip, hasDnsRule := config.GetSingleDnsRule(host); hasDnsRule { - ctx = context.WithValue(ctx, "USE_PROXY", true) - log.Printf("%s -> %s", host, ip) - return ctx, net.ParseIP(ip), nil - } - } - - if UseZjuDns { - resolve.lock.RLock() - useTCP := resolve.useTCP - resolve.lock.RUnlock() - - if !useTCP { - targets, err := resolve.remoteUDPResolver.LookupIP(context.Background(), "ip4", host) - if err != nil { - if targets, err = resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { - // all zju dns failed, so we keep do nothing but use secondary dns - // host ipv4 and host ipv6 don't set cache - log.Printf("Resolve IPv4 addr failed using ZJU UDP/TCP DNS: " + host + ", using secondary DNS instead.") - return resolve.ResolveWithSecondaryDns(ctx, host) - } else { - resolve.lock.Lock() - resolve.useTCP = true - if resolve.timer == nil { - resolve.timer = time.AfterFunc(10*time.Minute, func() { - resolve.lock.Lock() - resolve.useTCP = false - resolve.timer = nil - resolve.lock.Unlock() - }) - } - resolve.lock.Unlock() - } - } - // set dns cache if tcp or udp dns success - //TODO: whether we need all dns records? or only 10.0.0.0/8 ? - SetDnsCache(host, targets[0]) - log.Printf("%s -> %s", host, targets[0].String()) - return ctx, targets[0], nil - } else { - // only try tcp and secondary dns - if targets, err := resolve.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { - log.Printf("Resolve IPv4 addr failed using ZJU TCP DNS: " + host + ", using secondary DNS instead.") - return resolve.ResolveWithSecondaryDns(ctx, host) - } else { - SetDnsCache(host, targets[0]) - log.Printf("%s -> %s", host, targets[0].String()) - return ctx, targets[0], nil - } - } - } else { - // because of OS cache, don't need extra dns memory cache - return resolve.ResolveWithSecondaryDns(ctx, host) - } -} - -func SetupDnsResolve(zjuDnsServer string, client *EasyConnectClient) *DnsResolve { - var dns DnsResolve - if TunMode { - dns = DnsResolve{ - remoteUDPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := net.UDPAddr{ - IP: net.ParseIP(zjuDnsServer), - Port: 53, - } - - bind := net.UDPAddr{ - IP: net.IP(client.clientIp), - Port: 0, - } - - return net.DialUDP(network, &bind, &addrDns) - }, - }, - remoteTCPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := net.TCPAddr{ - IP: net.ParseIP(zjuDnsServer), - Port: 53, - } - - bind := net.TCPAddr{ - IP: net.IP(client.clientIp), - Port: 0, - } - - return net.DialTCP(network, &bind, &addrDns) - }, - }, - useTCP: false, - timer: nil, - } - } else { - dns = DnsResolve{ - remoteUDPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), - } - - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.AddrFromSlice(client.clientIp), - } - - return gonet.DialUDP(client.gvisorStack, &bind, &addrDns, header.IPv4ProtocolNumber) - }, - }, - remoteTCPResolver: &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(53), - Addr: tcpip.AddrFromSlice(net.ParseIP(ZjuDnsServer).To4()), - } - return gonet.DialTCP(client.gvisorStack, addrDns, header.IPv4ProtocolNumber) - }, - }, - useTCP: false, - timer: nil, - } - } - - if SecondaryDnsServer != "" { - dns.secondaryResolver = &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - addrDns := net.UDPAddr{ - IP: net.ParseIP(SecondaryDnsServer), - Port: 53, - } - - return net.DialUDP(network, nil, &addrDns) - }, - } - } else { - dns.secondaryResolver = &net.Resolver{ - PreferGo: true, - } - } - - return &dns -} diff --git a/core/gvisor_stack.go b/core/gvisor_stack.go deleted file mode 100644 index 1564aaf..0000000 --- a/core/gvisor_stack.go +++ /dev/null @@ -1,120 +0,0 @@ -package core - -import ( - "gvisor.dev/gvisor/pkg/buffer" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" - "gvisor.dev/gvisor/pkg/tcpip/transport/udp" -) - -const defaultNIC tcpip.NICID = 1 -const defaultMTU uint32 = 1400 - -// implements LinkEndpoint -type EasyConnectGvisorEndpoint struct { - dispatcher stack.NetworkDispatcher - OnRecv func(buf []byte) -} - -func (ep *EasyConnectGvisorEndpoint) ParseHeader(ptr stack.PacketBufferPtr) bool { - return true -} - -func (ep *EasyConnectGvisorEndpoint) MTU() uint32 { - return defaultMTU -} - -func (ep *EasyConnectGvisorEndpoint) MaxHeaderLength() uint16 { - return 0 -} - -func (ep *EasyConnectGvisorEndpoint) LinkAddress() tcpip.LinkAddress { - return "" -} - -func (ep *EasyConnectGvisorEndpoint) Capabilities() stack.LinkEndpointCapabilities { - return stack.CapabilityNone -} - -func (ep *EasyConnectGvisorEndpoint) Attach(dispatcher stack.NetworkDispatcher) { - ep.dispatcher = dispatcher -} - -func (ep *EasyConnectGvisorEndpoint) IsAttached() bool { - return ep.dispatcher != nil -} - -func (ep *EasyConnectGvisorEndpoint) Wait() {} - -func (ep *EasyConnectGvisorEndpoint) ARPHardwareType() header.ARPHardwareType { - return header.ARPHardwareNone -} - -func (ep *EasyConnectGvisorEndpoint) AddHeader(stack.PacketBufferPtr) {} - -func (ep *EasyConnectGvisorEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { - for _, packetBuffer := range list.AsSlice() { - var buf []byte - for _, t := range packetBuffer.AsSlices() { - buf = append(buf, t...) - } - - if ep.OnRecv != nil { - ep.OnRecv(buf) - } - } - return list.Len(), nil -} - -func (ep *EasyConnectGvisorEndpoint) WriteTo(buf []byte) { - if ep.IsAttached() { - packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Payload: buffer.MakeWithData(buf), - }) - ep.dispatcher.DeliverNetworkPacket(header.IPv4ProtocolNumber, packetBuffer) - packetBuffer.DecRef() - } -} - -func SetupGvisorStack(ip []byte, endpoint *EasyConnectGvisorEndpoint) *stack.Stack { - - // init IP stack - ipStack := stack.New(stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol}, - TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol}, - HandleLocal: true, - }) - - // create NIC associated to the gvisorEndpoint - err := ipStack.CreateNIC(defaultNIC, endpoint) - if err != nil { - panic(err) - } - - // assign ip - addr := tcpip.AddrFromSlice(ip) - protoAddr := tcpip.ProtocolAddress{ - AddressWithPrefix: tcpip.AddressWithPrefix{ - Address: addr, - PrefixLen: 32, - }, - Protocol: ipv4.ProtocolNumber, - } - - err = ipStack.AddProtocolAddress(defaultNIC, protoAddr, stack.AddressProperties{}) - if err != nil { - panic(err) - } - - // other settings - sOpt := tcpip.TCPSACKEnabled(true) - ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt) - cOpt := tcpip.CongestionControlOption("cubic") - ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt) - ipStack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: defaultNIC}) - - return ipStack -} diff --git a/core/keep_alive.go b/core/keep_alive.go deleted file mode 100644 index 2d51137..0000000 --- a/core/keep_alive.go +++ /dev/null @@ -1,21 +0,0 @@ -package core - -import ( - "context" - "log" - "net" - "time" -) - -func KeepAlive(remoteResolver *net.Resolver) { - for { - _, err := remoteResolver.LookupIP(context.Background(), "ip4", "www.baidu.com") - if err != nil { - log.Printf("KeepAlive: %s", err) - } else { - log.Printf("KeepAlive: OK") - } - - time.Sleep(60 * time.Second) - } -} diff --git a/core/protocol.go b/core/protocol.go deleted file mode 100644 index 18cff0f..0000000 --- a/core/protocol.go +++ /dev/null @@ -1,397 +0,0 @@ -package core - -import ( - "crypto/rand" - "encoding/hex" - "errors" - "golang.org/x/net/ipv4" - "io" - "log" - "net" - "os" - "runtime/debug" - - tls "github.com/refraction-networking/utls" -) - -type FakeHeartBeatExtension struct { - *tls.GenericExtension -} - -func (e *FakeHeartBeatExtension) Len() int { - return 5 -} - -func (e *FakeHeartBeatExtension) Read(b []byte) (n int, err error) { - if len(b) < e.Len() { - return 0, io.ErrShortBuffer - } - b[1] = 0x0f - b[3] = 1 - b[4] = 1 - - return e.Len(), io.EOF -} - -func DumpHex(buf []byte) { - stdoutDumper := hex.Dumper(os.Stdout) - defer stdoutDumper.Close() - stdoutDumper.Write(buf) -} - -func TLSConn(server string) (*tls.UConn, error) { - // dial vpn server - dialConn, err := net.Dial("tcp", server) - if err != nil { - return nil, err - } - log.Println("socket: connected to: ", dialConn.RemoteAddr()) - - // using uTLS to construct a weird TLS Client Hello (required by Sangfor) - // The VPN and HTTP Server share port 443, Sangfor uses a special SessionId to distinguish them. (which is very stupid...) - conn := tls.UClient(dialConn, &tls.Config{InsecureSkipVerify: true}, tls.HelloCustom) - - random := make([]byte, 32) - rand.Read(random) // Ignore the err - conn.SetClientRandom(random) - conn.SetTLSVers(tls.VersionTLS11, tls.VersionTLS11, []tls.TLSExtension{}) - conn.HandshakeState.Hello.Vers = tls.VersionTLS11 - conn.HandshakeState.Hello.CipherSuites = []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV} - conn.HandshakeState.Hello.CompressionMethods = []uint8{1, 0} - conn.HandshakeState.Hello.SessionId = []byte{'L', '3', 'I', 'P', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - conn.Extensions = []tls.TLSExtension{ - &FakeHeartBeatExtension{}, - } - - log.Println("tls: connected to: ", conn.RemoteAddr()) - - return conn, nil -} - -func QueryIp(server string, token *[48]byte, debugDump bool) ([]byte, *tls.UConn, error) { - conn, err := TLSConn(server) - if err != nil { - debug.PrintStack() - return nil, nil, err - } - // defer conn.Close() - // Query IP conn CAN NOT be closed, otherwise tx/rx handshake will fail - - // QUERY IP PACKET - message := []byte{0x00, 0x00, 0x00, 0x00} - message = append(message, token[:]...) - message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff}...) - - n, err := conn.Write(message) - if err != nil { - debug.PrintStack() - return nil, nil, err - } - - if debugDump { - log.Printf("query ip: wrote %d bytes", n) - DumpHex(message[:n]) - } - - reply := make([]byte, 0x80) - n, err = conn.Read(reply) - if err != nil { - debug.PrintStack() - return nil, nil, err - } - - if debugDump { - log.Printf("query ip: read %d bytes", n) - DumpHex(reply[:n]) - } - - if reply[0] != 0x00 { - debug.PrintStack() - return nil, nil, errors.New("unexpected query ip reply") - } - - return reply[4:8], conn, nil -} - -func BlockRXStreamWithGvisor(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { - conn, err := TLSConn(server) - if err != nil { - panic(err) - } - defer conn.Close() - - // RECV STREAM START - message := []byte{0x06, 0x00, 0x00, 0x00} - message = append(message, token[:]...) - message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) - message = append(message, ipRev[:]...) - - n, err := conn.Write(message) - if err != nil { - return err - } - if debug { - log.Printf("recv handshake: wrote %d bytes", n) - DumpHex(message[:n]) - } - - reply := make([]byte, 1500) - n, err = conn.Read(reply) - if err != nil { - return err - } - if debug { - log.Printf("recv handshake: read %d bytes", n) - DumpHex(reply[:n]) - } - - if reply[0] != 0x01 { - return errors.New("unexpected recv handshake reply") - } - - for { - n, err = conn.Read(reply) - - if err != nil { - return err - } - - ep.WriteTo(reply[:n]) - - if debug { - log.Printf("recv: read %d bytes", n) - DumpHex(reply[:n]) - } - } -} - -func BlockTXStreamWithGvisor(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectGvisorEndpoint, debug bool) error { - conn, err := TLSConn(server) - if err != nil { - return err - } - defer conn.Close() - - // SEND STREAM START - message := []byte{0x05, 0x00, 0x00, 0x00} - message = append(message, token[:]...) - message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) - message = append(message, ipRev[:]...) - - n, err := conn.Write(message) - if err != nil { - return err - } - if debug { - log.Printf("send handshake: wrote %d bytes", n) - DumpHex(message[:n]) - } - - reply := make([]byte, 1500) - n, err = conn.Read(reply) - if err != nil { - return err - } - if debug { - log.Printf("send handshake: read %d bytes", n) - DumpHex(reply[:n]) - } - - if reply[0] != 0x02 { - return errors.New("unexpected send handshake reply") - } - - errCh := make(chan error) - - ep.OnRecv = func(buf []byte) { - var n, err = conn.Write(buf) - if err != nil { - errCh <- err - return - } - - if debug { - log.Printf("send: wrote %d bytes", n) - DumpHex([]byte(buf[:n])) - } - } - - return <-errCh -} - -func StartProtocolWithGvisor(endpoint *EasyConnectGvisorEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { - RX := func() { - counter := 0 - for counter < 5 { - err := BlockRXStreamWithGvisor(server, token, ipRev, endpoint, debug) - if err != nil { - log.Print("Error occurred while receiving, retrying: " + err.Error()) - } - counter += 1 - } - panic("receive retry limit exceeded.") - } - - go RX() - - TX := func() { - counter := 0 - for counter < 5 { - err := BlockTXStreamWithGvisor(server, token, ipRev, endpoint, debug) - if err != nil { - log.Print("Error occurred while send, retrying: " + err.Error()) - } - counter += 1 - } - panic("send retry limit exceeded.") - } - - go TX() -} - -func BlockRXStreamWithTun(server string, token *[48]byte, ipRev *[4]byte, endpoint *EasyConnectTunEndpoint, debug bool) error { - conn, err := TLSConn(server) - if err != nil { - panic(err) - } - defer conn.Close() - - // RECV STREAM START - message := []byte{0x06, 0x00, 0x00, 0x00} - message = append(message, token[:]...) - message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) - message = append(message, ipRev[:]...) - - n, err := conn.Write(message) - if err != nil { - return err - } - if debug { - log.Printf("recv handshake: wrote %d bytes", n) - DumpHex(message[:n]) - } - - reply := make([]byte, 1500) - n, err = conn.Read(reply) - if err != nil { - return err - } - if debug { - log.Printf("recv handshake: read %d bytes", n) - DumpHex(reply[:n]) - } - - if reply[0] != 0x01 { - return errors.New("unexpected recv handshake reply") - } - - for { - n, err = conn.Read(reply) - if err != nil { - return err - } - - err = endpoint.Write(reply[:n]) - if err != nil { - return err - } - - if debug { - log.Printf("recv: read %d bytes", n) - DumpHex(reply[:n]) - } - } -} - -func BlockTXStreamWithTun(server string, token *[48]byte, ipRev *[4]byte, endpoint *EasyConnectTunEndpoint, debug bool) error { - conn, err := TLSConn(server) - if err != nil { - return err - } - defer conn.Close() - - // SEND STREAM START - message := []byte{0x05, 0x00, 0x00, 0x00} - message = append(message, token[:]...) - message = append(message, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) - message = append(message, ipRev[:]...) - - n, err := conn.Write(message) - if err != nil { - return err - } - if debug { - log.Printf("send handshake: wrote %d bytes", n) - DumpHex(message[:n]) - } - - reply := make([]byte, 1500) - n, err = conn.Read(reply) - if err != nil { - return err - } - if debug { - log.Printf("send handshake: read %d bytes", n) - DumpHex(reply[:n]) - } - - if reply[0] != 0x02 { - return errors.New("unexpected send handshake reply") - } - - for { - n, err := endpoint.Read(reply) - if err != nil { - return err - } - - header, err := ipv4.ParseHeader(reply[:n]) - if err != nil { - continue - } - - if header.Protocol != 6 && header.Protocol != 17 { - continue - } - - n, err = conn.Write(reply[:n]) - if err != nil { - return err - } - - if debug { - log.Printf("send: wrote %d bytes", n) - DumpHex(reply[:n]) - } - } -} - -func StartProtocolWithTun(endpoint *EasyConnectTunEndpoint, server string, token *[48]byte, ipRev *[4]byte, debug bool) { - RX := func() { - counter := 0 - for counter < 5 { - err := BlockRXStreamWithTun(server, token, ipRev, endpoint, debug) - if err != nil { - log.Print("Error occurred while receiving, retrying: " + err.Error()) - } - counter += 1 - } - panic("receive retry limit exceeded.") - } - - go RX() - - TX := func() { - counter := 0 - for counter < 5 { - err := BlockTXStreamWithTun(server, token, ipRev, endpoint, debug) - if err != nil { - log.Print("Error occurred while send, retrying: " + err.Error()) - } - counter += 1 - } - panic("send retry limit exceeded.") - } - - go TX() -} diff --git a/core/socks.go b/core/socks.go deleted file mode 100644 index 757c081..0000000 --- a/core/socks.go +++ /dev/null @@ -1,37 +0,0 @@ -package core - -import ( - "github.com/things-go/go-socks5" - "log" - "os" -) - -func ServeSocks5(bindAddr string, dialer Dialer, dnsResolve *DnsResolve) { - - var authMethods []socks5.Authenticator - if SocksUser != "" && SocksPasswd != "" { - authMethods = append(authMethods, socks5.UserPassAuthenticator{ - Credentials: socks5.StaticCredentials{SocksUser: SocksPasswd}, - }) - } else { - authMethods = append(authMethods, socks5.NoAuthAuthenticator{}) - } - - server := socks5.NewServer( - socks5.WithAuthMethods(authMethods), - socks5.WithResolver(dnsResolve), - socks5.WithDial(dialer.DialIpAndPort), - socks5.WithLogger(socks5.NewLogger(log.New(os.Stdout, "", log.LstdFlags))), - ) - - log.Printf("SOCKS5 server listening on " + bindAddr) - - if SocksUser != "" && SocksPasswd != "" { - log.Printf("\u001B[31mNeither traffic nor credentials are encrypted in the SOCKS5 protocol!\u001B[0m") - log.Printf("\u001B[31mDO NOT deploy it to the public network. All consequences and responsibilities have nothing to do with the developer.\u001B[0m") - } - - if err := server.ListenAndServe("tcp", bindAddr); err != nil { - panic("SOCKS5 listen failed: " + err.Error()) - } -} diff --git a/core/tcp_forwarding.go b/core/tcp_forwarding.go deleted file mode 100644 index b894365..0000000 --- a/core/tcp_forwarding.go +++ /dev/null @@ -1,107 +0,0 @@ -package core - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "io" - "log" - "net" - "strconv" - "strings" -) - -func ServeTcpForwarding(bindAddress string, remoteAddress string, client *EasyConnectClient) { - - ln, err := net.Listen("tcp", bindAddress) - if err != nil { - panic(err) - } - - if TunMode { - for { - conn, err := ln.Accept() - if err != nil { - panic(err) - } - - go handleRequestWithTun(conn, remoteAddress, client.clientIp) - } - } else { - for { - conn, err := ln.Accept() - if err != nil { - panic(err) - } - - go handleRequestWithGvisor(conn, remoteAddress, client.gvisorStack, client.clientIp) - } - } -} - -func handleRequestWithGvisor(conn net.Conn, remoteAddress string, ipStack *stack.Stack, selfIp []byte) { - log.Printf("Port forwarding (tcp): %s -> %s -> %s", conn.RemoteAddr(), conn.LocalAddr(), remoteAddress) - - parts := strings.Split(remoteAddress, ":") - host := parts[0] - port, err := strconv.Atoi(parts[1]) - if err != nil { - panic(err) - } - - addrTarget := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(port), - Addr: tcpip.AddrFromSlice(net.ParseIP(host).To4()), - } - - bind := tcpip.FullAddress{ - NIC: defaultNIC, - Addr: tcpip.AddrFromSlice(selfIp), - } - - proxy, err := gonet.DialTCPWithBind(context.Background(), ipStack, bind, addrTarget, header.IPv4ProtocolNumber) - if err != nil { - panic(err) - } - - go copyIO(conn, proxy) - go copyIO(proxy, conn) -} - -func handleRequestWithTun(conn net.Conn, remoteAddress string, selfIp []byte) { - log.Printf("Port forwarding (tcp): %s -> %s -> %s", conn.RemoteAddr(), conn.LocalAddr(), remoteAddress) - - parts := strings.Split(remoteAddress, ":") - host := parts[0] - port, err := strconv.Atoi(parts[1]) - if err != nil { - panic(err) - } - - addrTarget := net.TCPAddr{ - IP: net.ParseIP(host), - Port: port, - } - - bind := net.TCPAddr{ - IP: net.IP(selfIp), - Port: 0, - } - - proxy, err := net.DialTCP("tcp", &bind, &addrTarget) - if err != nil { - panic(err) - } - - go copyIO(conn, proxy) - go copyIO(proxy, conn) -} - -func copyIO(src, dest net.Conn) { - defer src.Close() - defer dest.Close() - io.Copy(src, dest) -} diff --git a/core/tun_stack_darwin.go b/core/tun_stack_darwin.go deleted file mode 100644 index dc3d1e1..0000000 --- a/core/tun_stack_darwin.go +++ /dev/null @@ -1,61 +0,0 @@ -package core - -import ( - "fmt" - "github.com/songgao/water" - "log" - "os/exec" -) - -type EasyConnectTunEndpoint struct { - ifce *water.Interface -} - -func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { - _, err := ep.ifce.Write(buf) - return err -} - -func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { - return ep.ifce.Read(buf) -} - -func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { - command := exec.Command("route", "-n", "add", "-net", target, "-interface", ep.ifce.Name()) - err := command.Run() - if err != nil { - return err - } - - return nil -} - -func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { - ifce, err := water.New(water.Config{ - DeviceType: water.TUN, - }) - if err != nil { - log.Fatal(err) - } - - log.Printf("Interface Name: %s\n", ifce.Name()) - - endpoint.ifce = ifce - - ipStr := fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) - cmd := exec.Command("ifconfig", ifce.Name(), ipStr, "255.0.0.0", ipStr) - err = cmd.Run() - if err != nil { - log.Printf("Run %s failed: %v", cmd.String(), err) - } - - if err = endpoint.AddRoute("10.0.0.0/8"); err != nil { - log.Printf("Run AddRoute 10.0.0.0/8 failed: %v", err) - } - - cmd = exec.Command("ifconfig", ifce.Name(), "mtu", "1400", "up") - err = cmd.Run() - if err != nil { - log.Printf("Run %s failed: %v", cmd.String(), err) - } -} diff --git a/core/tun_stack_linux.go b/core/tun_stack_linux.go deleted file mode 100644 index acf920c..0000000 --- a/core/tun_stack_linux.go +++ /dev/null @@ -1,62 +0,0 @@ -package core - -import ( - "fmt" - "github.com/songgao/water" - "log" - "os/exec" -) - -type EasyConnectTunEndpoint struct { - ifce *water.Interface -} - -func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { - _, err := ep.ifce.Write(buf) - return err -} - -func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { - return ep.ifce.Read(buf) -} - -func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { - command := exec.Command("ip", "route", "add", target, "dev", ep.ifce.Name()) - err := command.Run() - if err != nil { - return err - } - - return nil -} - -func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { - ifce, err := water.New(water.Config{ - DeviceType: water.TUN, - }) - if err != nil { - log.Fatal(err) - } - - log.Printf("Interface Name: %s\n", ifce.Name()) - - endpoint.ifce = ifce - - cmd := exec.Command("ip", "link", "set", ifce.Name(), "up") - err = cmd.Run() - if err != nil { - log.Printf("Run %s failed: %v", cmd.String(), err) - } - - cmd = exec.Command("ip", "link", "set", "dev", ifce.Name(), "mtu", "1400") - err = cmd.Run() - if err != nil { - log.Printf("Run %s failed: %v", cmd.String(), err) - } - - cmd = exec.Command("ip", "addr", "add", fmt.Sprintf("%d.%d.%d.%d/8", ip[0], ip[1], ip[2], ip[3]), "dev", ifce.Name()) - err = cmd.Run() - if err != nil { - log.Printf("Run %s failed: %v", cmd.String(), err) - } -} diff --git a/core/udp_forwarding.go b/core/udp_forwarding.go deleted file mode 100644 index 05e955d..0000000 --- a/core/udp_forwarding.go +++ /dev/null @@ -1,371 +0,0 @@ -package core - -import ( - "fmt" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "log" - "net" - "strconv" - "strings" - "sync" - "time" -) - -const bufferSize = 40960 -const DefaultTimeout = time.Minute * 5 - -type udpForward struct { - src *net.UDPAddr - destHost net.IP - destPort int - destString string - ipStack *stack.Stack - clientIp []byte - listenerConn *net.UDPConn - - gvisorConnections map[string]*gvisorConnection - tunConnections map[string]*tunConnection - connectionsMutex *sync.RWMutex - - connectCallback func(addr string) - disconnectCallback func(addr string) - - timeout time.Duration - - closed bool -} - -type gvisorConnection struct { - available chan struct{} - udp *gonet.UDPConn - lastActive time.Time -} - -type tunConnection struct { - available chan struct{} - udp *net.UDPConn - lastActive time.Time -} - -func ServeUdpForwarding(bindAddress string, remoteAddress string, client *EasyConnectClient) { - udpForward := newUdpForward(bindAddress, remoteAddress, client) - if TunMode { - udpForward.StartUdpForwardWithTun() - } else { - udpForward.StartUdpForwardWithGvisor() - } -} - -func newUdpForward(src, dest string, client *EasyConnectClient) *udpForward { - u := new(udpForward) - u.ipStack = client.gvisorStack - u.clientIp = client.clientIp - u.connectCallback = func(addr string) {} - u.disconnectCallback = func(addr string) {} - u.connectionsMutex = new(sync.RWMutex) - - if TunMode { - u.tunConnections = make(map[string]*tunConnection) - } else { - u.gvisorConnections = make(map[string]*gvisorConnection) - } - - u.timeout = DefaultTimeout - - var err error - u.src, err = net.ResolveUDPAddr("udp", src) - if err != nil { - fmt.Println(err) - return nil - } - - parts := strings.Split(dest, ":") - host := parts[0] - port, err := strconv.Atoi(parts[1]) - - u.destString = dest - - u.destHost = net.ParseIP(host) - u.destPort = port - - u.listenerConn, err = net.ListenUDP("udp", u.src) - if err != nil { - fmt.Println(err) - return nil - } - - return u -} - -func (u *udpForward) StartUdpForwardWithGvisor() { - go u.janitorWithGvisor() - for { - buf := make([]byte, bufferSize) - n, addr, err := u.listenerConn.ReadFromUDP(buf) - if err != nil { - log.Println("UDP forward: failed to read, terminating:", err) - return - } - - log.Printf("Port forwarding (udp): %s -> %s -> %s", addr.String(), u.src.String(), u.destString) - go u.handleWithGvisor(buf[:n], addr) - } -} - -func (u *udpForward) janitorWithGvisor() { - for !u.closed { - time.Sleep(u.timeout) - var keysToDelete []string - - u.connectionsMutex.RLock() - for k, conn := range u.gvisorConnections { - if conn.lastActive.Before(time.Now().Add(-u.timeout)) { - keysToDelete = append(keysToDelete, k) - } - } - u.connectionsMutex.RUnlock() - - u.connectionsMutex.Lock() - for _, k := range keysToDelete { - u.gvisorConnections[k].udp.Close() - delete(u.gvisorConnections, k) - } - u.connectionsMutex.Unlock() - - for _, k := range keysToDelete { - u.disconnectCallback(k) - } - } -} - -func (u *udpForward) handleWithGvisor(data []byte, addr *net.UDPAddr) { - u.connectionsMutex.Lock() - conn, found := u.gvisorConnections[addr.String()] - if !found { - u.gvisorConnections[addr.String()] = &gvisorConnection{ - available: make(chan struct{}), - udp: nil, - lastActive: time.Now(), - } - } - u.connectionsMutex.Unlock() - - if !found { - var udpConn *gonet.UDPConn - var err error - - addrTarget := tcpip.FullAddress{ - NIC: defaultNIC, - Port: uint16(u.destPort), - Addr: tcpip.AddrFromSlice(u.destHost.To4()), - } - - udpConn, err = gonet.DialUDP(u.ipStack, nil, &addrTarget, header.IPv4ProtocolNumber) - - if err != nil { - log.Println("UDP forward: failed to dial:", err) - delete(u.gvisorConnections, addr.String()) - return - } - - u.connectionsMutex.Lock() - u.gvisorConnections[addr.String()].udp = udpConn - u.gvisorConnections[addr.String()].lastActive = time.Now() - close(u.gvisorConnections[addr.String()].available) - u.connectionsMutex.Unlock() - - u.connectCallback(addr.String()) - - _, err = udpConn.Write(data) - if err != nil { - log.Println("UDP forward: error sending initial packet to client", err) - } - - for { - buf := make([]byte, bufferSize) - n, err := udpConn.Read(buf) - if err != nil { - u.connectionsMutex.Lock() - udpConn.Close() - delete(u.gvisorConnections, addr.String()) - u.connectionsMutex.Unlock() - u.disconnectCallback(addr.String()) - log.Println("udp-forward: abnormal read, closing:", err) - return - } - - _, _, err = u.listenerConn.WriteMsgUDP(buf[:n], nil, addr) - if err != nil { - log.Println("UDP forward: error sending packet to client:", err) - } - } - } - - <-conn.available - - _, err := conn.udp.Write(data) - if err != nil { - log.Println("UDP forward: error sending packet to server:", err) - } - - shouldChangeTime := false - u.connectionsMutex.RLock() - if _, found := u.gvisorConnections[addr.String()]; found { - if u.gvisorConnections[addr.String()].lastActive.Before( - time.Now().Add(u.timeout / 4)) { - shouldChangeTime = true - } - } - u.connectionsMutex.RUnlock() - - if shouldChangeTime { - u.connectionsMutex.Lock() - - if _, found := u.gvisorConnections[addr.String()]; found { - connWrapper := u.gvisorConnections[addr.String()] - connWrapper.lastActive = time.Now() - u.gvisorConnections[addr.String()] = connWrapper - } - u.connectionsMutex.Unlock() - } -} - -func (u *udpForward) StartUdpForwardWithTun() { - go u.janitorWithTun() - for { - buf := make([]byte, bufferSize) - n, addr, err := u.listenerConn.ReadFromUDP(buf) - if err != nil { - log.Println("UDP forward: failed to read, terminating:", err) - return - } - - log.Printf("Port forwarding (udp): %s -> %s -> %s", addr.String(), u.src.String(), u.destString) - go u.handleWithTun(buf[:n], addr) - } -} - -func (u *udpForward) janitorWithTun() { - for !u.closed { - time.Sleep(u.timeout) - var keysToDelete []string - - u.connectionsMutex.RLock() - for k, conn := range u.tunConnections { - if conn.lastActive.Before(time.Now().Add(-u.timeout)) { - keysToDelete = append(keysToDelete, k) - } - } - u.connectionsMutex.RUnlock() - - u.connectionsMutex.Lock() - for _, k := range keysToDelete { - u.tunConnections[k].udp.Close() - delete(u.tunConnections, k) - } - u.connectionsMutex.Unlock() - - for _, k := range keysToDelete { - u.disconnectCallback(k) - } - } -} - -func (u *udpForward) handleWithTun(data []byte, addr *net.UDPAddr) { - u.connectionsMutex.Lock() - conn, found := u.tunConnections[addr.String()] - if !found { - u.tunConnections[addr.String()] = &tunConnection{ - available: make(chan struct{}), - udp: nil, - lastActive: time.Now(), - } - } - u.connectionsMutex.Unlock() - - if !found { - var udpConn *net.UDPConn - var err error - - laddr := net.UDPAddr{ - IP: u.clientIp, - Port: 0, - } - - raddr := net.UDPAddr{ - IP: u.destHost, - Port: u.destPort, - } - - udpConn, err = net.DialUDP("udp", &laddr, &raddr) - - if err != nil { - log.Println("UDP forward: failed to dial:", err) - delete(u.tunConnections, addr.String()) - return - } - - u.connectionsMutex.Lock() - u.tunConnections[addr.String()].udp = udpConn - u.tunConnections[addr.String()].lastActive = time.Now() - close(u.tunConnections[addr.String()].available) - u.connectionsMutex.Unlock() - - u.connectCallback(addr.String()) - - _, err = udpConn.Write(data) - if err != nil { - log.Println("UDP forward: error sending initial packet to client", err) - } - - for { - buf := make([]byte, bufferSize) - n, err := udpConn.Read(buf) - if err != nil { - u.connectionsMutex.Lock() - udpConn.Close() - delete(u.tunConnections, addr.String()) - u.connectionsMutex.Unlock() - u.disconnectCallback(addr.String()) - log.Println("udp-forward: abnormal read, closing:", err) - return - } - - _, _, err = u.listenerConn.WriteMsgUDP(buf[:n], nil, addr) - if err != nil { - log.Println("UDP forward: error sending packet to client:", err) - } - } - } - - <-conn.available - - _, err := conn.udp.Write(data) - if err != nil { - log.Println("UDP forward: error sending packet to server:", err) - } - - shouldChangeTime := false - u.connectionsMutex.RLock() - if _, found := u.tunConnections[addr.String()]; found { - if u.tunConnections[addr.String()].lastActive.Before( - time.Now().Add(u.timeout / 4)) { - shouldChangeTime = true - } - } - u.connectionsMutex.RUnlock() - - if shouldChangeTime { - u.connectionsMutex.Lock() - - if _, found := u.tunConnections[addr.String()]; found { - connWrapper := u.tunConnections[addr.String()] - connWrapper.lastActive = time.Now() - u.tunConnections[addr.String()] = connWrapper - } - u.connectionsMutex.Unlock() - } -} diff --git a/core/web_login.go b/core/web_login.go deleted file mode 100644 index 3534ee5..0000000 --- a/core/web_login.go +++ /dev/null @@ -1,281 +0,0 @@ -package core - -import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "encoding/hex" - "errors" - "io" - "log" - "math/big" - "net" - "net/http" - "net/url" - "os" - "regexp" - "runtime/debug" - "strconv" - "strings" - - utls "github.com/refraction-networking/utls" -) - -var ERR_NEXT_AUTH_SMS = errors.New("SMS Code required") -var ERR_NEXT_AUTH_TOTP = errors.New("current user's TOTP bound") - -func WebLogin(server string, username string, password string) (string, error) { - server = "https://" + server - - c := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }} - - addr := server + "/por/login_auth.csp?apiversion=1" - log.Printf("Login Request: %s", addr) - - resp, err := c.Get(addr) - if err != nil { - debug.PrintStack() - return "", err - } - - defer resp.Body.Close() - - var buf bytes.Buffer - io.Copy(&buf, resp.Body) - - twfId := string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) - log.Printf("Twf Id: %s", twfId) - - rndImg := string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) - if rndImg == "1" { - log.Print("\u001B[31mDue to too many login failures, the server has activated risk control for this IP.\u001B[0m") - log.Print("\u001B[31mContinuing to log in may cause this IP to be banned. The program has stopped the login process.\u001B[0m") - log.Print("\u001B[31mYou can wait a minute and try again.\u001B[0m") - - os.Exit(1) - } - - rsaKey := string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) - log.Printf("RSA Key: %s", rsaKey) - - rsaExpMatch := regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes()) - rsaExp := "" - if rsaExpMatch != nil { - rsaExp = string(rsaExpMatch[1]) - } else { - log.Printf("Warning: No RSA_ENCRYPT_EXP, using default.") - rsaExp = "65537" - } - log.Printf("RSA Exp: %s", rsaExp) - - csrfMatch := regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes()) - csrfCode := "" - if csrfMatch != nil { - csrfCode = string(csrfMatch[1]) - log.Printf("CSRF Code: %s", csrfCode) - password += "_" + csrfCode - } else { - log.Printf("WARNING: No CSRF Code Match. Maybe you're connecting to an older server? Continue anyway...") - } - - pubKey := rsa.PublicKey{} - pubKey.E, _ = strconv.Atoi(rsaExp) - moduls := big.Int{} - moduls.SetString(rsaKey, 16) - pubKey.N = &moduls - - encryptedPassword, err := rsa.EncryptPKCS1v15(rand.Reader, &pubKey, []byte(password)) - if err != nil { - debug.PrintStack() - return "", err - } - encryptedPasswordHex := hex.EncodeToString(encryptedPassword) - - addr = server + "/por/login_psw.csp?anti_replay=1&encrypt=1&type=cs" - log.Printf("Login Request: %s", addr) - - form := url.Values{ - "svpn_rand_code": {""}, - "mitm": {""}, - "svpn_req_randcode": {csrfCode}, - "svpn_name": {username}, - "svpn_password": {encryptedPasswordHex}, - } - - req, err := http.NewRequest("POST", addr, strings.NewReader(form.Encode())) - req.Header.Set("Cookie", "TWFID="+twfId) - - resp, err = c.Do(req) - if err != nil { - debug.PrintStack() - return "", err - } - - buf.Reset() - io.Copy(&buf, resp.Body) - defer resp.Body.Close() - - // log.Printf("First stage login response: %s", string(buf[:n])) - - // SMS Code Process - if strings.Contains(buf.String(), "auth/sms") || strings.Contains(buf.String(), "2") { - log.Print("SMS code required.") - - addr = server + "/por/login_sms.csp?apiversion=1" - log.Printf("SMS Request: " + addr) - req, err = http.NewRequest("POST", addr, nil) - req.Header.Set("Cookie", "TWFID="+twfId) - - resp, err = c.Do(req) - if err != nil { - debug.PrintStack() - return "", err - } - - buf.Reset() - io.Copy(&buf, resp.Body) - defer resp.Body.Close() - - if !strings.Contains(buf.String(), "验证码已发送到您的手机") && !strings.Contains(buf.String(), "") { - debug.PrintStack() - return "", errors.New("unexpected sms resp: " + buf.String()) - } - - log.Printf("SMS Code is sent or still valid.") - - return twfId, ERR_NEXT_AUTH_SMS - } - - // TOTP Authnication Process (Edited by JHong) - if strings.Contains(buf.String(), "auth/token") || strings.Contains(buf.String(), "totp") { - log.Print("TOTP Authnication required.") - return twfId, ERR_NEXT_AUTH_TOTP - } - - if strings.Contains(buf.String(), "-1") || !strings.Contains(buf.String(), "") { - log.Print("No NextAuth found.") - } else { - debug.PrintStack() - return "", errors.New("Not implemented auth: " + buf.String()) - } - - if !strings.Contains(buf.String(), "1") { - debug.PrintStack() - return "", errors.New("Login FAILED: " + buf.String()) - } - - twfIdMatch := regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes()) - if twfIdMatch != nil { - twfId = string(twfIdMatch[1]) - log.Printf("Update twfId: %s", twfId) - } - - log.Printf("Web Login process done.") - - return twfId, nil -} - -func AuthSms(server string, username string, password string, twfId string, smsCode string) (string, error) { - c := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }} - - addr := "https://" + server + "/por/login_sms1.csp?apiversion=1" - log.Printf("SMS Request: " + addr) - form := url.Values{ - "svpn_inputsms": {smsCode}, - } - - req, err := http.NewRequest("POST", addr, strings.NewReader(form.Encode())) - req.Header.Set("Cookie", "TWFID="+twfId) - - resp, err := c.Do(req) - if err != nil { - debug.PrintStack() - return "", err - } - - var buf bytes.Buffer - io.Copy(&buf, resp.Body) - defer resp.Body.Close() - - if !strings.Contains(buf.String(), "Auth sms suc") { - debug.PrintStack() - return "", errors.New("SMS Code verification FAILED: " + buf.String()) - } - - twfId = string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) - log.Print("SMS Code verification SUCCESS") - - return twfId, nil -} - -// JHong Implementing....... -func TOTPAuth(server string, username string, password string, twfId string, TOTPCode string) (string, error) { - c := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }} - - addr := "https://" + server + "/por/login_token.csp" - log.Printf("TOTP token Request: " + addr) - form := url.Values{ - "svpn_inputtoken": {TOTPCode}, - } - - req, err := http.NewRequest("POST", addr, strings.NewReader(form.Encode())) - req.Header.Set("Cookie", "TWFID="+twfId) - - resp, err := c.Do(req) - if err != nil { - debug.PrintStack() - return "", err - } - - var buf bytes.Buffer - io.Copy(&buf, resp.Body) - - defer resp.Body.Close() - - if !strings.Contains(buf.String(), "suc") { - debug.PrintStack() - return "", errors.New("TOTP token verification FAILED: " + buf.String()) - } - - twfId = string(regexp.MustCompile(`(.*)`).FindSubmatch(buf.Bytes())[1]) - log.Print("TOTP verification SUCCESS") - - return twfId, nil -} - -func ECAgentToken(server string, twfId string) (string, error) { - dialConn, err := net.Dial("tcp", server) - defer dialConn.Close() - conn := utls.UClient(dialConn, &utls.Config{InsecureSkipVerify: true}, utls.HelloGolang) - defer conn.Close() - - // WTF??? - // When you establish a HTTPS connection to server and send a valid request with TWFID to it - // The **TLS ServerHello SessionId** is the first part of token - log.Printf("ECAgent Request: /por/conf.csp & /por/rclist.csp") - _, err = io.WriteString(conn, "GET /por/conf.csp HTTP/1.1\r\nHost: "+server+"\r\nCookie: TWFID="+twfId+"\r\n\r\nGET /por/rclist.csp HTTP/1.1\r\nHost: "+server+"\r\nCookie: TWFID="+twfId+"\r\n\r\n") - if err != nil { - panic(err) - } - - log.Printf("Server Session ID: %s", hex.EncodeToString(conn.HandshakeState.ServerHello.SessionId)) - - buf := make([]byte, 8) - n, err := conn.Read(buf) - if n == 0 || err != nil { - debug.PrintStack() - return "", errors.New("ECAgent Request invalid: error " + err.Error() + "\n" + string(buf[:])) - } - - return hex.EncodeToString(conn.HandshakeState.ServerHello.SessionId)[:31] + "\x00", nil -} diff --git a/dial/dialer.go b/dial/dialer.go new file mode 100644 index 0000000..9f654cc --- /dev/null +++ b/dial/dialer.go @@ -0,0 +1,131 @@ +package dial + +import ( + "github.com/mythologyli/zju-connect/log" + "github.com/mythologyli/zju-connect/resolve" + "github.com/mythologyli/zju-connect/stack" + "inet.af/netaddr" + "net" + "strconv" + "strings" +) + +import ( + "context" + "errors" +) + +type Dialer struct { + stack stack.Stack + resolver *resolve.Resolver + ipResource *netaddr.IPSet + alwaysUseVPN bool +} + +func dialDirect(ctx context.Context, network, addr string) (net.Conn, error) { + goDialer := &net.Dialer{} + goDial := goDialer.DialContext + + log.Printf("%s -> DIRECT", addr) + + return goDial(ctx, network, addr) +} + +func (d *Dialer) DialIPPort(ctx context.Context, network, addr string) (net.Conn, error) { + // If addr is IPv6, use direct connection + if strings.Count(addr, ":") > 1 { + return dialDirect(ctx, network, addr) + } + + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, errors.New("Invalid address: " + addr) + } + + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, errors.New("Invalid port in address: " + addr) + } + + var useVPN = false + var target *net.IPAddr + + if pureIp := net.ParseIP(host); pureIp != nil { + target = &net.IPAddr{IP: pureIp} + } else { + log.Printf("Illegal situation, host is not pure IP format: %s", host) + return dialDirect(ctx, network, addr) + } + + if d.alwaysUseVPN { + useVPN = true + } + + if res := ctx.Value("USE_VPN"); res != nil && res.(bool) { + useVPN = true + } + + if !useVPN && d.ipResource != nil { + ip, ok := netaddr.FromStdIP(target.IP) + if ok { + if d.ipResource.Contains(ip) { + useVPN = true + } + } + } + + if useVPN { + if network == "tcp" { + log.Printf("%s -> VPN", addr) + + return d.stack.DialTCP(&net.TCPAddr{ + IP: target.IP, + Port: port, + }) + } else if network == "udp" { + log.Printf("%s -> VPN", addr) + + return d.stack.DialUDP(&net.UDPAddr{ + IP: target.IP, + Port: port, + }) + } else { + log.Printf("VPN only support TCP/UDP. Connection to %s will use direct connection", addr) + return dialDirect(ctx, network, addr) + } + } else { + return dialDirect(ctx, network, addr) + } +} + +func (d *Dialer) Dial(ctx context.Context, network string, addr string) (net.Conn, error) { + // If addr is IPv6, use direct connection + if strings.Count(addr, ":") > 1 { + return dialDirect(ctx, network, addr) + } + + host, port, err := net.SplitHostPort(addr) + if err != nil { + return dialDirect(ctx, network, addr) + } + + ctx, ip, err := d.resolver.Resolve(ctx, host) + if err != nil { + return dialDirect(ctx, network, addr) + } + + if strings.Count(ip.String(), ":") > 0 { + return dialDirect(ctx, network, addr) + } + + return d.DialIPPort(ctx, network, ip.String()+":"+port) +} + +func NewDialer(stack stack.Stack, resolver *resolve.Resolver, ipResource *netaddr.IPSet, alwaysUseVPN bool) *Dialer { + return &Dialer{ + stack: stack, + resolver: resolver, + ipResource: ipResource, + alwaysUseVPN: alwaysUseVPN, + } +} diff --git a/go.mod b/go.mod index 8bd9295..0279ff9 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,9 @@ go 1.21 require github.com/refraction-networking/utls v1.5.4 require ( - github.com/BurntSushi/toml v1.3.2 - github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89 + github.com/BurntSushi/toml v1.2.1 + github.com/beevik/etree v1.2.0 github.com/cloverstd/tcping v0.1.1 - github.com/cornelk/hashmap v1.0.8 - github.com/dlclark/regexp2 v1.10.0 github.com/miekg/dns v1.1.56 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 @@ -33,7 +31,6 @@ require ( go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.14.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect diff --git a/go.sum b/go.sum index 88f4169..eb73a98 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,15 @@ -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89 h1:2pkAuIM8OF1fy4ToFpMnI4oE+VeUNRbGrpSLKshK0oQ= -github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89/go.mod h1:/09nEjna1UMoasyyQDhOrIn8hi2v2kiJglPWed1idck= +github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw= +github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo= github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloverstd/tcping v0.1.1 h1:3Yp9nvSDI7Z63zoVQDJzVk1PUczrF9tJoOrKGV30iOk= github.com/cloverstd/tcping v0.1.1/go.mod h1:NYXTrTDwlwuOKQ0vwksUVUbIr0sxDDsf1J6aFpScCBo= -github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc= -github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k= 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/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= -github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= @@ -63,7 +59,6 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= diff --git a/init.go b/init.go new file mode 100644 index 0000000..f4a5c2e --- /dev/null +++ b/init.go @@ -0,0 +1,278 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "github.com/BurntSushi/toml" + "os" + "strings" +) + +type ( + Config struct { + ServerAddress string + ServerPort int + Username string + Password string + DisableServerConfig bool + DisableZJUConfig bool + DisableZJUDNS bool + DisableMultiLine bool + ProxyAll bool + SocksBind string + SocksUser string + SocksPasswd string + HTTPBind string + TUNMode bool + AddRoute bool + DNSTTL uint64 + DisableKeepAlive bool + ZJUDNSServer string + SecondaryDNSServer string + DNSServerBind string + TUNDNSServer string + DebugDump bool + PortForwardingList []SinglePortForwarding + CustomDNSList []SingleCustomDNS + TwfID string + } + + SinglePortForwarding struct { + NetworkType string + BindAddress string + RemoteAddress string + } + + SingleCustomDNS struct { + HostName string `toml:"host_name"` + IP string `toml:"ip"` + } +) + +type ( + ConfigTOML struct { + ServerAddress *string `toml:"server_address"` + ServerPort *int `toml:"server_port"` + Username *string `toml:"username"` + Password *string `toml:"password"` + DisableServerConfig *bool `toml:"disable_server_config"` + DisableZJUConfig *bool `toml:"disable_zju_config"` + DisableZJUDNS *bool `toml:"disable_zju_dns"` + DisableMultiLine *bool `toml:"disable_multi_line"` + ProxyAll *bool `toml:"proxy_all"` + SocksBind *string `toml:"socks_bind"` + SocksUser *string `toml:"socks_user"` + SocksPasswd *string `toml:"socks_passwd"` + HTTPBind *string `toml:"http_bind"` + TUNMode *bool `toml:"tun_mode"` + AddRoute *bool `toml:"add_route"` + DNSTTL *uint64 `toml:"dns_ttl"` + DisableKeepAlive *bool `toml:"disable_keep_alive"` + ZJUDNSServer *string `toml:"zju_dns_server"` + SecondaryDNSServer *string `toml:"secondary_dns_server"` + DNSServerBind *string `toml:"dns_server_bind"` + TUNDNSServer *string `toml:"tun_dns_server"` + DebugDump *bool `toml:"debug_dump"` + PortForwarding []SinglePortForwardingTOML `toml:"port_forwarding"` + CustomDNS []SingleCustomDNSTOML `toml:"custom_dns"` + } + + SinglePortForwardingTOML struct { + NetworkType *string `toml:"network_type"` + BindAddress *string `toml:"bind_address"` + RemoteAddress *string `toml:"remote_address"` + } + + SingleCustomDNSTOML struct { + HostName *string `toml:"host_name"` + IP *string `toml:"ip"` + } +) + +func getTOMLVal[T int | uint64 | string | bool](valPointer *T, defaultVal T) T { + if valPointer == nil { + return defaultVal + } else { + return *valPointer + } +} + +func parseTOMLConfig(configFile string, conf *Config) error { + var confTOML ConfigTOML + + _, err := toml.DecodeFile(configFile, &confTOML) + if err != nil { + return errors.New("ZJU Connect: error parsing the config file") + } + + conf.ServerAddress = getTOMLVal(confTOML.ServerAddress, "rvpn.zju.edu.cn") + conf.ServerPort = getTOMLVal(confTOML.ServerPort, 443) + conf.Username = getTOMLVal(confTOML.Username, "") + conf.Password = getTOMLVal(confTOML.Password, "") + conf.DisableServerConfig = getTOMLVal(confTOML.DisableServerConfig, false) + conf.DisableZJUConfig = getTOMLVal(confTOML.DisableZJUConfig, false) + conf.DisableZJUDNS = getTOMLVal(confTOML.DisableZJUDNS, false) + conf.DisableMultiLine = getTOMLVal(confTOML.DisableMultiLine, false) + conf.ProxyAll = getTOMLVal(confTOML.ProxyAll, false) + conf.SocksBind = getTOMLVal(confTOML.SocksBind, ":1080") + conf.SocksUser = getTOMLVal(confTOML.SocksUser, "") + conf.SocksPasswd = getTOMLVal(confTOML.SocksPasswd, "") + conf.HTTPBind = getTOMLVal(confTOML.HTTPBind, ":1081") + conf.TUNMode = getTOMLVal(confTOML.TUNMode, false) + conf.AddRoute = getTOMLVal(confTOML.AddRoute, false) + conf.DNSTTL = getTOMLVal(confTOML.DNSTTL, uint64(3600)) + conf.DebugDump = getTOMLVal(confTOML.DebugDump, false) + conf.DisableKeepAlive = getTOMLVal(confTOML.DisableKeepAlive, false) + conf.ZJUDNSServer = getTOMLVal(confTOML.ZJUDNSServer, "10.10.0.21") + conf.SecondaryDNSServer = getTOMLVal(confTOML.SecondaryDNSServer, "114.114.114.114") + conf.DNSServerBind = getTOMLVal(confTOML.DNSServerBind, "") + conf.TUNDNSServer = getTOMLVal(confTOML.TUNDNSServer, "") + + for _, singlePortForwarding := range confTOML.PortForwarding { + if singlePortForwarding.NetworkType == nil { + return errors.New("ZJU Connect: network type is not set") + } + + if singlePortForwarding.BindAddress == nil { + return errors.New("ZJU Connect: bind address is not set") + } + + if singlePortForwarding.RemoteAddress == nil { + return errors.New("ZJU Connect: remote address is not set") + } + + conf.PortForwardingList = append(conf.PortForwardingList, SinglePortForwarding{ + NetworkType: *singlePortForwarding.NetworkType, + BindAddress: *singlePortForwarding.BindAddress, + RemoteAddress: *singlePortForwarding.RemoteAddress, + }) + } + + for _, singleCustomDns := range confTOML.CustomDNS { + if singleCustomDns.HostName == nil { + return errors.New("ZJU Connect: host name is not set") + } + + if singleCustomDns.IP == nil { + fmt.Println("ZJU Connect: IP is not set") + return errors.New("ZJU Connect: IP is not set") + } + + conf.CustomDNSList = append(conf.CustomDNSList, SingleCustomDNS{ + HostName: *singleCustomDns.HostName, + IP: *singleCustomDns.IP, + }) + } + + return nil +} + +func init() { + configFile, tcpPortForwarding, udpPortForwarding, customDns := "", "", "", "" + showVersion := false + + flag.StringVar(&conf.ServerAddress, "server", "rvpn.zju.edu.cn", "EasyConnect server address") + flag.IntVar(&conf.ServerPort, "port", 443, "EasyConnect port address") + flag.StringVar(&conf.Username, "username", "", "Your username") + flag.StringVar(&conf.Password, "password", "", "Your password") + flag.BoolVar(&conf.DisableServerConfig, "disable-server-config", false, "Don't parse server config") + flag.BoolVar(&conf.DisableZJUConfig, "disable-zju-config", false, "Don't use ZJU config") + flag.BoolVar(&conf.DisableZJUDNS, "disable-zju-dns", false, "Use local DNS instead of ZJU DNS") + flag.BoolVar(&conf.DisableMultiLine, "disable-multi-line", false, "Disable multi line auto select") + flag.BoolVar(&conf.ProxyAll, "proxy-all", false, "Proxy all IPv4 traffic") + flag.StringVar(&conf.SocksBind, "socks-bind", ":1080", "The address SOCKS5 server listens on (e.g. 127.0.0.1:1080)") + flag.StringVar(&conf.SocksUser, "socks-user", "", "SOCKS5 username, default is don't use auth") + flag.StringVar(&conf.SocksPasswd, "socks-passwd", "", "SOCKS5 password, default is don't use auth") + flag.StringVar(&conf.HTTPBind, "http-bind", ":1081", "The address HTTP server listens on (e.g. 127.0.0.1:1081)") + flag.BoolVar(&conf.TUNMode, "tun-mode", false, "Enable TUN mode (experimental)") + flag.BoolVar(&conf.AddRoute, "add-route", false, "Add route from rules for TUN interface") + flag.Uint64Var(&conf.DNSTTL, "dns-ttl", 3600, "DNS record time to live, unit is second") + flag.BoolVar(&conf.DebugDump, "debug-dump", false, "Enable traffic debug dump (only for debug usage)") + flag.BoolVar(&conf.DisableKeepAlive, "disable-keep-alive", false, "Disable keep alive") + flag.StringVar(&conf.ZJUDNSServer, "zju-dns-server", "10.10.0.21", "ZJU DNS server address") + flag.StringVar(&conf.SecondaryDNSServer, "secondary-dns-server", "114.114.114.114", "Secondary DNS server address. Leave empty to use system default DNS server") + flag.StringVar(&conf.DNSServerBind, "dns-server-bind", "", "The address DNS server listens on (e.g. 127.0.0.1:53)") + flag.StringVar(&conf.TUNDNSServer, "tun-dns-server", "", "DNS Server address for TUN interface (e.g. 127.0.0.1). You should not specify the port") + flag.StringVar(&conf.TwfID, "twf-id", "", "Login using twfID captured (mostly for debug usage)") + flag.StringVar(&tcpPortForwarding, "tcp-port-forwarding", "", "TCP port forwarding (e.g. 0.0.0.0:9898-10.10.98.98:80,127.0.0.1:9899-10.10.98.98:80)") + flag.StringVar(&udpPortForwarding, "udp-port-forwarding", "", "UDP port forwarding (e.g. 127.0.0.1:53-10.10.0.21:53)") + flag.StringVar(&customDns, "custom-dns", "", "Custom set dns lookup (e.g. www.cc98.org:10.10.98.98,appservice.zju.edu.cn:10.203.8.198)") + flag.StringVar(&configFile, "config", "", "Config file") + flag.BoolVar(&showVersion, "version", false, "Show version") + + flag.Parse() + + if showVersion { + fmt.Printf("ZJU Connect v%s\n", zjuConnectVersion) + return + } + + if configFile != "" { + err := parseTOMLConfig(configFile, &conf) + if err != nil { + fmt.Println(err) + return + } + } else { + if tcpPortForwarding != "" { + forwardingStringList := strings.Split(tcpPortForwarding, ",") + for _, forwardingString := range forwardingStringList { + addressStringList := strings.Split(forwardingString, "-") + if len(addressStringList) != 2 { + fmt.Println("ZJU Connect: wrong tcp port forwarding format") + return + } + + conf.PortForwardingList = append(conf.PortForwardingList, SinglePortForwarding{ + NetworkType: "tcp", + BindAddress: addressStringList[0], + RemoteAddress: addressStringList[1], + }) + } + } + + if udpPortForwarding != "" { + forwardingStringList := strings.Split(udpPortForwarding, ",") + for _, forwardingString := range forwardingStringList { + addressStringList := strings.Split(forwardingString, "-") + if len(addressStringList) != 2 { + fmt.Println("ZJU Connect: wrong udp port forwarding format") + return + } + + conf.PortForwardingList = append(conf.PortForwardingList, SinglePortForwarding{ + NetworkType: "udp", + BindAddress: addressStringList[0], + RemoteAddress: addressStringList[1], + }) + } + } + + if customDns != "" { + dnsList := strings.Split(customDns, ",") + for _, dnsString := range dnsList { + dnsStringSplit := strings.Split(dnsString, ":") + if len(dnsStringSplit) != 2 { + fmt.Println("ZJU Connect: wrong custom dns format") + return + } + + conf.CustomDNSList = append(conf.CustomDNSList, SingleCustomDNS{ + HostName: dnsStringSplit[0], + IP: dnsStringSplit[1], + }) + } + } + } + + if conf.ServerAddress == "" || ((conf.Username == "" || conf.Password == "") && conf.TwfID == "") { + fmt.Println("ZJU Connect") + fmt.Println("Please see: https://github.com/mythologyli/zju-connect") + fmt.Printf("\nUsage: %s -username -password \n", os.Args[0]) + fmt.Println("\nFull usage:") + flag.PrintDefaults() + + return + } +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..37f8809 --- /dev/null +++ b/log/log.go @@ -0,0 +1,82 @@ +package log + +import ( + "encoding/hex" + "io" + "log" + "os" +) + +var debug bool + +func Init() { + log.SetOutput(os.Stdout) +} + +func EnableDebug() { + debug = true +} + +func DisableDebug() { + debug = false +} + +func Print(v ...any) { + log.Print(v...) +} + +func DebugPrint(v ...any) { + if debug { + log.Print(v...) + } +} + +func Println(v ...any) { + log.Println(v...) +} + +func DebugPrintln(v ...any) { + if debug { + log.Println(v...) + } +} + +func Printf(format string, v ...any) { + log.Printf(format, v...) +} + +func DebugPrintf(format string, v ...any) { + if debug { + log.Printf(format, v...) + } +} + +func Fatal(v ...any) { + log.Fatal(v...) +} + +func Fatalf(format string, v ...any) { + log.Fatalf(format, v...) +} + +func DumpHex(buf []byte) { + stdoutDumper := hex.Dumper(os.Stdout) + defer func(stdoutDumper io.WriteCloser) { + _ = stdoutDumper.Close() + }(stdoutDumper) + _, _ = stdoutDumper.Write(buf) +} + +func DebugDumpHex(buf []byte) { + if debug { + stdoutDumper := hex.Dumper(os.Stdout) + defer func(stdoutDumper io.WriteCloser) { + _ = stdoutDumper.Close() + }(stdoutDumper) + _, _ = stdoutDumper.Write(buf) + } +} + +func NewLogger(prefix string) *log.Logger { + return log.New(os.Stdout, prefix, log.LstdFlags) +} diff --git a/main.go b/main.go index ce4882c..1066f92 100644 --- a/main.go +++ b/main.go @@ -1,254 +1,146 @@ package main import ( - "flag" "fmt" - "log" - "os" - "strings" - - "github.com/BurntSushi/toml" - "github.com/mythologyli/zju-connect/core" + "github.com/mythologyli/zju-connect/client" + "github.com/mythologyli/zju-connect/dial" + "github.com/mythologyli/zju-connect/log" + "github.com/mythologyli/zju-connect/resolve" + "github.com/mythologyli/zju-connect/service" + "github.com/mythologyli/zju-connect/stack" + "github.com/mythologyli/zju-connect/stack/gvisor" + "github.com/mythologyli/zju-connect/stack/tun" + "inet.af/netaddr" + "net" ) -type ( - Config struct { - ServerAddress *string `toml:"server_address"` - ServerPort *int `toml:"server_port"` - Username *string `toml:"username"` - Password *string `toml:"password"` - DisableServerConfig *bool `toml:"disable_server_config"` - DisableZjuConfig *bool `toml:"disable_zju_config"` - DisableZjuDns *bool `toml:"disable_zju_dns"` - DisableMultiLine *bool `toml:"disable_multi_line"` - ProxyAll *bool `toml:"proxy_all"` - SocksBind *string `toml:"socks_bind"` - SocksUser *string `toml:"socks_user"` - SocksPasswd *string `toml:"socks_passwd"` - HttpBind *string `toml:"http_bind"` - TunMode *bool `toml:"tun_mode"` - AddRoute *bool `toml:"add_route"` - DnsTTL *uint64 `toml:"dns_ttl"` - DisableKeepAlive *bool `toml:"disable_keep_alive"` - ZjuDnsServer *string `toml:"zju_dns_server"` - SecondaryDnsServer *string `toml:"secondary_dns_server"` - DnsServerBind *string `toml:"dns_server_bind"` - TunDnsServer *string `toml:"tun_dns_server"` - DebugDump *bool `toml:"debug_dump"` - PortForwarding []SinglePortForwarding `toml:"port_forwarding"` - CustomDns []SingleCustomDns `toml:"custom_dns"` +var conf Config + +const zjuConnectVersion = "0.4.1" + +func main() { + log.Init() + + log.Println("Start ZJU Connect v" + zjuConnectVersion) + if conf.DebugDump { + log.EnableDebug() } - SinglePortForwarding struct { - NetworkType *string `toml:"network_type"` - BindAddress *string `toml:"bind_address"` - RemoteAddress *string `toml:"remote_address"` + vpnClient := client.NewEasyConnectClient( + conf.ServerAddress+":"+fmt.Sprintf("%d", conf.ServerPort), + conf.Username, + conf.Password, + conf.TwfID, + !conf.DisableMultiLine, + !conf.DisableServerConfig, + ) + err := vpnClient.Setup() + if err != nil { + log.Fatalf("EasyConnect client setup error: %s", err) } - SingleCustomDns struct { - HostName *string `toml:"host_name"` - IP *string `toml:"ip"` + log.Printf("EasyConnect client started") + + ipResource, err := vpnClient.IPResource() + if err != nil && !conf.DisableMultiLine { + log.Println("No IP resource") } -) -func getTomlVal[T int | uint64 | string | bool](valPointer *T, defaultVal T) T { - if valPointer == nil { - return defaultVal - } else { - return *valPointer + domainResource, err := vpnClient.DomainResource() + if err != nil && !conf.DisableMultiLine { + log.Println("No domain resource") } -} -func main() { - version := "0.4.1" - - // CLI args - host, port, username, password := "", 0, "", "" - disableServerConfig, disableZjuConfig, disableZjuDns, disableMultiLine, disableKeepAlive := false, false, false, false, false - twfId, configFile, tcpPortForwarding, udpPortForwarding, customDns := "", "", "", "", "" - showVersion := false - - flag.StringVar(&host, "server", "rvpn.zju.edu.cn", "EasyConnect server address") - flag.IntVar(&port, "port", 443, "EasyConnect port address") - flag.StringVar(&username, "username", "", "Your username") - flag.StringVar(&password, "password", "", "Your password") - flag.BoolVar(&disableServerConfig, "disable-server-config", false, "Don't parse server config") - flag.BoolVar(&disableZjuConfig, "disable-zju-config", false, "Don't use ZJU config") - flag.BoolVar(&disableZjuDns, "disable-zju-dns", false, "Use local DNS instead of ZJU DNS") - flag.BoolVar(&disableMultiLine, "disable-multi-line", false, "Disable multi line auto select") - flag.BoolVar(&core.ProxyAll, "proxy-all", false, "Proxy all IPv4 traffic") - flag.StringVar(&core.SocksBind, "socks-bind", ":1080", "The address SOCKS5 server listens on (e.g. 127.0.0.1:1080)") - flag.StringVar(&core.SocksUser, "socks-user", "", "SOCKS5 username, default is don't use auth") - flag.StringVar(&core.SocksPasswd, "socks-passwd", "", "SOCKS5 password, default is don't use auth") - flag.StringVar(&core.HttpBind, "http-bind", ":1081", "The address HTTP server listens on (e.g. 127.0.0.1:1081)") - flag.BoolVar(&core.TunMode, "tun-mode", false, "Enable TUN mode (experimental)") - flag.BoolVar(&core.AddRoute, "add-route", false, "Add route from rules for TUN interface") - flag.Uint64Var(&core.DnsTTL, "dns-ttl", 3600, "DNS record time to live, unit is second") - flag.BoolVar(&core.DebugDump, "debug-dump", false, "Enable traffic debug dump (only for debug usage)") - flag.StringVar(&tcpPortForwarding, "tcp-port-forwarding", "", "TCP port forwarding (e.g. 0.0.0.0:9898-10.10.98.98:80,127.0.0.1:9899-10.10.98.98:80)") - flag.StringVar(&udpPortForwarding, "udp-port-forwarding", "", "UDP port forwarding (e.g. 127.0.0.1:53-10.10.0.21:53)") - flag.StringVar(&customDns, "custom-dns", "", "Custom set dns lookup (e.g. www.cc98.org:10.10.98.98,appservice.zju.edu.cn:10.203.8.198)") - flag.BoolVar(&disableKeepAlive, "disable-keep-alive", false, "Disable keep alive") - flag.StringVar(&core.ZjuDnsServer, "zju-dns-server", "10.10.0.21", "ZJU DNS server address") - flag.StringVar(&core.SecondaryDnsServer, "secondary-dns-server", "114.114.114.114", "Secondary DNS server address. Leave empty to use system default DNS server") - flag.StringVar(&core.DnsServerBind, "dns-server-bind", "", "The address DNS server listens on (e.g. 127.0.0.1:53)") - flag.StringVar(&core.TunDnsServer, "tun-dns-server", "", "DNS Server address for TUN interface (e.g. 127.0.0.1). You should not specify the port") - flag.StringVar(&twfId, "twf-id", "", "Login using twfID captured (mostly for debug usage)") - flag.StringVar(&configFile, "config", "", "Config file") - flag.BoolVar(&showVersion, "version", false, "Show version") - - flag.Parse() - - if showVersion { - fmt.Printf("ZJU Connect v%s\n", version) - return + dnsResource, err := vpnClient.DNSResource() + if err != nil && !conf.DisableMultiLine { + log.Println("No DNS resource") } - if configFile != "" { - var conf Config - _, err := toml.DecodeFile(configFile, &conf) - if err != nil { - fmt.Println("ZJU Connect: error parsing the config file") - return + if !conf.DisableZJUConfig { + if domainResource != nil { + domainResource["zju.edu.cn"] = true + } else { + domainResource = map[string]bool{"zju.edu.cn": true} } - host = getTomlVal(conf.ServerAddress, "rvpn.zju.edu.cn") - port = getTomlVal(conf.ServerPort, 443) - username = getTomlVal(conf.Username, "") - password = getTomlVal(conf.Password, "") - core.ParseServConfig = !getTomlVal(conf.DisableServerConfig, false) - core.ParseZjuConfig = !getTomlVal(conf.DisableZjuConfig, false) - core.UseZjuDns = !getTomlVal(conf.DisableZjuDns, false) - core.TestMultiLine = !getTomlVal(conf.DisableMultiLine, false) - core.ProxyAll = getTomlVal(conf.ProxyAll, false) - core.SocksBind = getTomlVal(conf.SocksBind, ":1080") - core.SocksUser = getTomlVal(conf.SocksUser, "") - core.SocksPasswd = getTomlVal(conf.SocksPasswd, "") - core.HttpBind = getTomlVal(conf.HttpBind, ":1081") - core.TunMode = getTomlVal(conf.TunMode, false) - core.AddRoute = getTomlVal(conf.AddRoute, false) - core.DnsTTL = getTomlVal(conf.DnsTTL, uint64(3600)) - core.DebugDump = getTomlVal(conf.DebugDump, false) - core.EnableKeepAlive = !getTomlVal(conf.DisableKeepAlive, false) - core.ZjuDnsServer = getTomlVal(conf.ZjuDnsServer, "10.10.0.21") - core.SecondaryDnsServer = getTomlVal(conf.SecondaryDnsServer, "114.114.114.114") - core.DnsServerBind = getTomlVal(conf.DnsServerBind, "") - core.TunDnsServer = getTomlVal(conf.TunDnsServer, "") - - for _, singlePortForwarding := range conf.PortForwarding { - if singlePortForwarding.NetworkType == nil { - fmt.Println("ZJU Connect: network type is not set") - return - } - - if singlePortForwarding.BindAddress == nil { - fmt.Println("ZJU Connect: bind address is not set") - return - } - - if singlePortForwarding.RemoteAddress == nil { - fmt.Println("ZJU Connect: remote address is not set") - return - } - - core.ForwardingList = append(core.ForwardingList, core.Forwarding{ - NetworkType: *singlePortForwarding.NetworkType, - BindAddress: *singlePortForwarding.BindAddress, - RemoteAddress: *singlePortForwarding.RemoteAddress, - }) + ipSetBuilder := netaddr.IPSetBuilder{} + if ipResource != nil { + ipSetBuilder.AddSet(ipResource) } + ipSetBuilder.AddPrefix(netaddr.MustParseIPPrefix("10.0.0.0/8")) + ipResource, _ = ipSetBuilder.IPSet() + } - for _, singleCustomDns := range conf.CustomDns { - if singleCustomDns.HostName == nil { - fmt.Println("ZJU Connect: host_name is not set") - return - } + var vpnStack stack.Stack + if conf.TUNMode { + vpnTUNStack, err := tun.NewStack(vpnClient, conf.TUNDNSServer) + if err != nil { + log.Fatalf("gVisor stack setup error: %s", err) + } - if singleCustomDns.IP == nil { - fmt.Println("ZJU Connect: IP is not set") - return + if conf.AddRoute && ipResource != nil { + for _, prefix := range ipResource.Prefixes() { + log.Printf("Add route to %s", prefix.String()) + _ = vpnTUNStack.AddRoute(prefix.String()) } - - core.CustomDNSList = append(core.CustomDNSList, core.CustomDNS{ - HostName: *singleCustomDns.HostName, - IP: *singleCustomDns.IP, - }) } - if host == "" || (username == "" || password == "") { - fmt.Println("ZJU Connect: host, username and password can not be empty") - return - } + vpnStack = vpnTUNStack } else { - core.ParseServConfig = !disableServerConfig - core.ParseZjuConfig = !disableZjuConfig - core.UseZjuDns = !disableZjuDns - core.TestMultiLine = !disableMultiLine - core.EnableKeepAlive = !disableKeepAlive - - if tcpPortForwarding != "" { - forwardingStringList := strings.Split(tcpPortForwarding, ",") - for _, forwardingString := range forwardingStringList { - addressStringList := strings.Split(forwardingString, "-") - if len(addressStringList) != 2 { - fmt.Println("ZJU Connect: wrong tcp port forwarding format") - return - } - - core.ForwardingList = append(core.ForwardingList, core.Forwarding{ - NetworkType: "tcp", - BindAddress: addressStringList[0], - RemoteAddress: addressStringList[1], - }) - } + vpnStack, err = gvisor.NewStack(vpnClient) + if err != nil { + log.Fatalf("gVisor stack setup error: %s", err) } + } - if udpPortForwarding != "" { - forwardingStringList := strings.Split(udpPortForwarding, ",") - for _, forwardingString := range forwardingStringList { - addressStringList := strings.Split(forwardingString, "-") - if len(addressStringList) != 2 { - fmt.Println("ZJU Connect: wrong udp port forwarding format") - return - } - - core.ForwardingList = append(core.ForwardingList, core.Forwarding{ - NetworkType: "udp", - BindAddress: addressStringList[0], - RemoteAddress: addressStringList[1], - }) - } + go vpnStack.Run() + + vpnResolver := resolve.NewResolver( + vpnStack, + conf.ZJUDNSServer, + conf.SecondaryDNSServer, + conf.DNSTTL, + domainResource, + dnsResource, + !conf.DisableZJUDNS, + ) + + for _, customDns := range conf.CustomDNSList { + ipAddr := net.ParseIP(customDns.IP) + if ipAddr == nil { + log.Printf("Custom DNS for host name %s is invalid, SKIP", customDns.HostName) } + vpnResolver.SetPermanentDNS(customDns.HostName, ipAddr) + log.Printf("Add custom DNS: %s -> %s\n", customDns.HostName, customDns.IP) + } - if customDns != "" { - customDnsList := strings.Split(customDns, ",") - for _, singleCustomDns := range customDnsList { - singleCustomDnsSplit := strings.Split(singleCustomDns, ":") - if len(singleCustomDnsSplit) != 2 { - fmt.Println("ZJU Connect: wrong custom dns format") - return - } - - core.CustomDNSList = append(core.CustomDNSList, core.CustomDNS{ - HostName: singleCustomDnsSplit[0], - IP: singleCustomDnsSplit[1], - }) - } - } + vpnDialer := dial.NewDialer(vpnStack, vpnResolver, ipResource, conf.ProxyAll) - if host == "" || ((username == "" || password == "") && twfId == "") { - fmt.Println("ZJU Connect") - fmt.Println("Please see: https://github.com/mythologyli/zju-connect") - fmt.Printf("\nUsage: %s -username -password \n", os.Args[0]) - fmt.Println("\nFull usage:") - flag.PrintDefaults() + if conf.DNSServerBind != "" { + go service.ServeDNS(conf.DNSServerBind, vpnResolver) + } - return - } + if conf.SocksBind != "" { + go service.ServeSocks5(conf.SocksBind, vpnDialer, vpnResolver, conf.SocksUser, conf.SocksPasswd) + } + if conf.HTTPBind != "" { + go service.ServeHTTP(conf.HTTPBind, vpnDialer) } - log.Println("Start ZJU Connect v" + version) + for _, portForwarding := range conf.PortForwardingList { + if portForwarding.NetworkType == "tcp" { + go service.ServeTCPForwarding(vpnStack, portForwarding.BindAddress, portForwarding.RemoteAddress) + } else if portForwarding.NetworkType == "udp" { + go service.ServeUDPForwarding(vpnStack, portForwarding.BindAddress, portForwarding.RemoteAddress) + } else { + log.Printf("Port forwarding: unknown network type %s. Aborting", portForwarding.NetworkType) + } + } + + if !conf.DisableKeepAlive { + service.KeepAlive(vpnResolver) + } - core.StartClient(host, port, username, password, twfId) + select {} } diff --git a/parser/RulesParser.go b/parser/RulesParser.go deleted file mode 100644 index 8ad4acc..0000000 --- a/parser/RulesParser.go +++ /dev/null @@ -1,309 +0,0 @@ -package parser - -import ( - "fmt" - "github.com/mythologyli/zju-connect/core/config" - "log" - "math" - "net" - "net/url" - "regexp" - "runtime" - "strconv" - "strings" - - "github.com/dlclark/regexp2" -) - -var domainRegExp *regexp.Regexp - -func StringArrToIntArr(strArr []string) [4]int { - var intArr [4]int - - for index, str := range strArr { - intArr[index], _ = strconv.Atoi(str) - } - - return intArr -} - -func getMaskByIpRange(fromIp string, toIp string) (ones int, bits int) { - fromIpSplit := StringArrToIntArr(strings.Split(fromIp, ".")) - toIpSplit := StringArrToIntArr(strings.Split(toIp, ".")) - - fromIpSplit[3] = int(math.Max(float64(fromIpSplit[3]-1), 0)) - toIpSplit[3] = int(math.Min(float64(toIpSplit[3]+1), 255)) - - var mask [4]byte - for i := 3; i >= 0; i-- { - mask[i] = uint8(255 - toIpSplit[i] + fromIpSplit[i]) - } - - return net.IPv4Mask(mask[0], mask[1], mask[2], mask[3]).Size() -} - -// from https://github.com/dromara/hutool/blob/fc091b01a23271e02f3174c45942105048155c90/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java#L114 -func getIPsInRange(from, to string) *[]string { - ipf := StringArrToIntArr(strings.Split(from, ".")) - ipt := StringArrToIntArr(strings.Split(to, ".")) - - ips := make([]string, 4096) - - equ := func(condition bool, yes int, no int) int { - if condition { - return yes - } else { - return no - } - } - - var a, b, c int - - for a = ipf[0]; a <= ipt[0]; a++ { - for b = equ(a == ipf[0], ipf[1], 0); b <= equ(a == ipt[0], ipt[1], 255); b++ { - for c = equ(b == ipf[1], ipf[2], 0); c <= equ(b == ipt[1], ipt[2], 255); c++ { - for d := equ(c == ipf[2], ipf[3], 0); d <= equ(c == ipt[2], ipt[3], 255); d++ { - ips = append(ips, strconv.Itoa(a)+"."+strconv.Itoa(b)+"."+strconv.Itoa(c)+"."+strconv.Itoa(d)) - } - } - } - } - - return &ips -} - -func countByIpRange(from string, to string) int { - count := 1 - ipf := StringArrToIntArr(strings.Split(from, ".")) - ipt := StringArrToIntArr(strings.Split(to, ".")) - - for i := 3; i >= 0; i-- { - count += (ipt[i] - ipf[i]) * int(math.Pow(256, float64(3-i))) - } - - return count -} - -func processSingleIpRule(rule, port string, debug bool, waitChan *chan int) { - appendRule := func(value *string, isIPV4RangeRule bool, isCIDR bool) { - minValue := port - maxValue := port - - if strings.Contains(port, "~") { - minValue = strings.Split(port, "~")[0] - maxValue = strings.Split(port, "~")[1] - } - - minValueInt, err := strconv.Atoi(minValue) - if err != nil { - log.Printf("Cannot parse port value from string") - return - } - - maxValueInt, err := strconv.Atoi(maxValue) - if err != nil { - log.Printf("Cannot parse port value from string") - return - } - - if debug { - log.Printf("Appending Domain rule for: %s%v isIpv4RangeRule: %v isCIDR: %v", *value, []int{minValueInt, maxValueInt}, isIPV4RangeRule, isCIDR) - } - - if isIPV4RangeRule { - config.AppendSingleIpv4RangeRule(*value, []int{minValueInt, maxValueInt}, isCIDR, debug) - } else { - config.AppendSingleDomainRule(*value, []int{minValueInt, maxValueInt}, debug) - } - } - - if strings.Contains(rule, "~") { // ip range 1.1.1.7~1.1.7.9 - from := strings.Split(rule, "~")[0] - to := strings.Split(rule, "~")[1] - size := countByIpRange(from, to) - - mask, k := getMaskByIpRange(from, to) - - if debug { - log.Printf("Handling rule for: %s-%s mask: %v", from, to, mask) - } - - // mask == 0 -> cannot cover to cidr - if mask != 0 && mask <= 28 { - if debug { - log.Printf("using Cidr %s-%s mask: %v %v", from, to, mask, k) - } - - cidr := fmt.Sprintf("%s/%v", from, mask) - - appendRule(&cidr, true, true) - } else { - if size > 4096 { - log.Printf("Super large rule detected for: %s-%s mask: %v", from, to, mask) - - appendRule(&rule, true, false) - } else { - if size > 1024 { - log.Printf("Large rule detected for: %s-%s mask: %v", from, to, mask) - } - - for _, domain := range *getIPsInRange(from, to) { - appendRule(&domain, false, false) - } - } - } - } else { // http://domain.example.com/path/to&something=good#extra - appendRule(&rule, false, false) - - if domainRegExp == nil { - domainRegExp, _ = regexp.Compile("(?:\\w+\\.)+\\w+") - } - - pureDomain := domainRegExp.FindString(rule) - - appendRule(&pureDomain, false, false) //TODO::FIXME:: remove this when using Http(s) proxy - } - - *waitChan <- 1 -} - -func processDnsData(dnsData string, debug bool) { - for _, ent := range strings.Split(dnsData, ";") { - dnsEntry := strings.Split(ent, ":") - - if len(dnsEntry) >= 3 { - RcID := dnsEntry[0] - domain := dnsEntry[1] - ip := dnsEntry[2] - - if debug { - log.Printf("[%s] %s %s", RcID, domain, ip) - } - - if domain != "" && ip != "" { - config.AppendSingleDnsRule(domain, ip, debug) - } - } - } -} - -func processRcsData(rcsData config.Resource, debug bool, waitChan *chan int, cpuNumber *int) { - RcsLen := len(rcsData.Rcs.Rc) - for RcsIndex, ent := range rcsData.Rcs.Rc { - if debug { - log.Printf("[%s] %s %s", ent.Name, ent.Host, ent.Port) - } - - if ent.Host == "" || ent.Port == "" { - log.Printf("Found null entry when processing RcsData: [%s] %s %s", ent.Name, ent.Host, ent.Port) - continue - } - - domains := strings.Split(ent.Host, ";") - ports := strings.Split(ent.Port, ";") - - if len(domains) >= 1 && len(ports) >= 1 { - for index, domain := range domains { - portRange := ports[index] - - if *cpuNumber > 0 { - *cpuNumber-- - } else { - <-*waitChan - } - processSingleIpRule(domain, portRange, debug, waitChan) - } - } - - log.Printf("Progress: %v/100 (ResourceList.Rcs)", int(float32(RcsIndex)/float32(RcsLen)*100)) - } -} - -func ParseResourceLists(host, twfID string, debug bool) { - ResourceList := config.Resource{} - res, ok := ParseXml(&ResourceList, host, config.PathRlist, twfID) - - cpuNumber := runtime.NumCPU() - waitChan := make(chan int, cpuNumber) - - if !ok || ResourceList.Rcs.Rc == nil || len(ResourceList.Rcs.Rc) <= 0 || ResourceList.Dns.Data == "" { - if res != "" { - log.Printf("try parsing by regexp") - - escapeReplacementMap := map[string]string{ - " ": string(rune(160)), - "&": "&", - """: "\"", - "<": "<", - ">": ">", - } - - for from, to := range escapeReplacementMap { - res = strings.ReplaceAll(res, from, to) - } - - resUrlDecodedValue, err := url.QueryUnescape(res) - if err != nil { - log.Printf("Cannot do UrlDecode") - return - } - - ResourceListRegexp := regexp2.MustCompile("(?<=\" host=\").*?(?=\" enable_disguise=)", 0) - ResourceListMatches, _ := ResourceListRegexp.FindStringMatch(resUrlDecodedValue) - for ; ResourceListMatches != nil; ResourceListMatches, _ = ResourceListRegexp.FindNextMatch(ResourceListMatches) { - - if debug { - log.Printf("ResourceListMatch -> " + ResourceListMatches.String() + "\n") - } - - ResourceListData := ResourceListMatches.String() - - ResourceListDataHost := strings.Split(ResourceListData, "\" port=\"")[0] - ResourceListDataPort := strings.Split(ResourceListData, "\" port=\"")[1] - - entry := config.RcData{Host: ResourceListDataHost, Port: ResourceListDataPort} - ResourceList.Rcs.Rc = append(ResourceList.Rcs.Rc, entry) - } - - processRcsData(ResourceList, debug, &waitChan, &cpuNumber) - - log.Printf("Parsed %v domain rules", config.GetDomainRuleLen()) - log.Printf("Parsed %v IPv4 rules", config.GetIpv4RuleLen()) - - DnsDataRegexp := regexp2.MustCompile("(?<= %s", host, cachedIP.String()) + return ctx, cachedIP, nil + } + + if r.dnsResource != nil { + if ip, found := r.dnsResource[host]; found { + ctx = context.WithValue(ctx, "USE_VPN", true) + log.Printf("%s -> %s", host, ip.String()) + return ctx, ip, nil + } + } + + if r.useRemoteDNS { + r.lock.RLock() + useTCP := r.useTCP + r.lock.RUnlock() + + if !useTCP { + ips, err := r.remoteUDPResolver.LookupIP(context.Background(), "ip4", host) + if err != nil { + if ips, err = r.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { + // All remote DNS failed, so we keep do nothing but use secondary dns + log.Printf("Resolve IPv4 addr failed using ZJU UDP/TCP DNS: " + host + ", using secondary DNS instead") + return r.ResolveWithSecondaryDNS(ctx, host) + } else { + r.lock.Lock() + r.useTCP = true + if r.timer == nil { + r.timer = time.AfterFunc(10*time.Minute, func() { + r.lock.Lock() + r.useTCP = false + r.timer = nil + r.lock.Unlock() + }) + } + r.lock.Unlock() + } + } + // Set DNS cache if tcp or udp DNS success + r.setDNSCache(host, ips[0]) + log.Printf("%s -> %s", host, ips[0].String()) + return ctx, ips[0], nil + } else { + // Only try tcp and secondary DNS + if ips, err := r.remoteTCPResolver.LookupIP(context.Background(), "ip4", host); err != nil { + log.Printf("Resolve IPv4 addr failed using ZJU TCP DNS: " + host + ", using secondary DNS instead") + return r.ResolveWithSecondaryDNS(ctx, host) + } else { + r.setDNSCache(host, ips[0]) + log.Printf("%s -> %s", host, ips[0].String()) + return ctx, ips[0], nil + } + } + } else { + return r.ResolveWithSecondaryDNS(ctx, host) + } +} + +func (r *Resolver) RemoteUDPResolver() (*net.Resolver, error) { + if r.remoteUDPResolver != nil { + return r.remoteUDPResolver, nil + } else { + return nil, errors.New("remote UDP resolver is nil") + } +} + +func (r *Resolver) ResolveWithSecondaryDNS(ctx context.Context, host string) (context.Context, net.IP, error) { + if targets, err := r.secondaryResolver.LookupIP(ctx, "ip4", host); err != nil { + log.Printf("Resolve IPv4 addr failed using secondary DNS: " + host + ". Try IPv6 addr") + + if targets, err = r.secondaryResolver.LookupIP(ctx, "ip6", host); err != nil { + log.Printf("Resolve IPv6 addr failed using secondary DNS: " + host) + return ctx, nil, err + } else { + log.Printf("%s -> %s", host, targets[0].String()) + return ctx, targets[0], nil + } + } else { + log.Printf("%s -> %s", host, targets[0].String()) + return ctx, targets[0], nil + } +} + +func NewResolver(stack stack.Stack, remoteDNSServer, secondaryDNSServer string, ttl uint64, domainResource map[string]bool, dnsResource map[string]net.IP, useRemoteDNS bool) *Resolver { + resolver := &Resolver{ + remoteUDPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + return stack.DialUDP(&net.UDPAddr{ + IP: net.ParseIP(remoteDNSServer), + Port: 53, + }) + }, + }, + remoteTCPResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + return stack.DialTCP(&net.TCPAddr{ + IP: net.ParseIP(remoteDNSServer), + Port: 53, + }) + }, + }, + ttl: ttl, + domainResource: domainResource, + dnsResource: dnsResource, + dnsCache: cache.New(time.Duration(ttl)*time.Second, time.Duration(ttl)*2*time.Second), + useRemoteDNS: useRemoteDNS, + } + + if secondaryDNSServer != "" { + resolver.secondaryResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + return net.DialUDP(network, nil, &net.UDPAddr{ + IP: net.ParseIP(secondaryDNSServer), + Port: 53, + }) + }, + } + } else { + resolver.secondaryResolver = &net.Resolver{ + PreferGo: true, + } + } + + return resolver +} diff --git a/core/dns_server.go b/service/dns.go similarity index 63% rename from core/dns_server.go rename to service/dns.go index 5e4db47..ed002d6 100644 --- a/core/dns_server.go +++ b/service/dns.go @@ -1,17 +1,18 @@ -package core +package service import ( "context" "fmt" - "log" + "github.com/miekg/dns" + "github.com/mythologyli/zju-connect/log" + "github.com/mythologyli/zju-connect/resolve" ) -import "github.com/miekg/dns" -type DnsServer struct { - dnsResolve *DnsResolve +type DNSServer struct { + resolver *resolve.Resolver } -func (dnsServer DnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { +func (d DNSServer) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) m.SetReply(r) m.Compress = false @@ -26,7 +27,7 @@ func (dnsServer DnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { switch q.Qtype { case dns.TypeA: - if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), name); err == nil { + if _, ip, err := d.resolver.Resolve(context.Background(), name); err == nil { if ip.To4() != nil { rr, err := dns.NewRR(fmt.Sprintf("%s A %s", q.Name, ip)) if err == nil { @@ -35,7 +36,7 @@ func (dnsServer DnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { } } case dns.TypeAAAA: - if _, ip, err := dnsServer.dnsResolve.Resolve(context.Background(), name); err == nil { + if _, ip, err := d.resolver.Resolve(context.Background(), name); err == nil { if ip.To4() == nil { rr, err := dns.NewRR(fmt.Sprintf("%s AAAA %s", q.Name, ip)) if err == nil { @@ -50,11 +51,9 @@ func (dnsServer DnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { _ = w.WriteMsg(m) } -func ServeDns(bindAddr string, dnsResolve *DnsResolve) { - log.Printf("DNS server listening on " + bindAddr) - - dnsServer := &DnsServer{dnsResolve: dnsResolve} - dns.HandleFunc(".", dnsServer.handleDnsRequest) +func ServeDNS(bindAddr string, resolver *resolve.Resolver) { + dnsServer := &DNSServer{resolver: resolver} + dns.HandleFunc(".", dnsServer.handleDNSRequest) server := &dns.Server{Addr: bindAddr, Net: "udp"} log.Printf("Starting DNS server at %s", server.Addr) diff --git a/core/http.go b/service/http.go similarity index 75% rename from core/http.go rename to service/http.go index 4c1f69f..61d5e4f 100644 --- a/core/http.go +++ b/service/http.go @@ -1,9 +1,10 @@ -package core +package service import ( "context" + "github.com/mythologyli/zju-connect/dial" + "github.com/mythologyli/zju-connect/log" "io" - "log" "net" "net/http" ) @@ -29,13 +30,14 @@ import ( // 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. -func ServeHttp(bindAddr string, dialer Dialer, zjuDnsResolve *DnsResolve) { +func ServeHTTP(bindAddr string, dialer *dial.Dialer) { client := &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, net, addr string) (net.Conn, error) { - return dialer.Dial(ctx, zjuDnsResolve, "tcp", addr) + return dialer.Dial(ctx, net, addr) }, }, + // We must pass redirect response to browser CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, @@ -43,18 +45,18 @@ func ServeHttp(bindAddr string, dialer Dialer, zjuDnsResolve *DnsResolve) { handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.Method == "CONNECT" { - serverConn, err := dialer.Dial(context.Background(), zjuDnsResolve, "tcp", req.Host) + serverConn, err := dialer.Dial(context.Background(), "tcp", req.Host) if err != nil { w.WriteHeader(500) - w.Write([]byte(err.Error() + "\n")) + _, _ = w.Write([]byte(err.Error() + "\n")) return } hijacker, ok := w.(http.Hijacker) if !ok { - serverConn.Close() + _ = serverConn.Close() w.WriteHeader(500) - w.Write([]byte("Failed cast to Hijacker\n")) + _, _ = w.Write([]byte("Failed cast to hijacker\n")) return } @@ -63,21 +65,24 @@ func ServeHttp(bindAddr string, dialer Dialer, zjuDnsResolve *DnsResolve) { _, bio, err := hijacker.Hijack() if err != nil { w.WriteHeader(500) - w.Write([]byte(err.Error() + "\n")) - serverConn.Close() + _, _ = w.Write([]byte(err.Error() + "\n")) + _ = serverConn.Close() return } - go io.Copy(serverConn, bio) - go io.Copy(bio, serverConn) + go func() { + _, _ = io.Copy(serverConn, bio) + }() + go func() { + _, _ = io.Copy(bio, serverConn) + }() } else { - // Server-Only field; we get an error fi we pass this to `client.Do`. req.RequestURI = "" resp, err := client.Do(req) if err != nil { w.WriteHeader(500) - w.Write([]byte(err.Error() + "\n")) + _, _ = w.Write([]byte(err.Error() + "\n")) return } @@ -88,7 +93,7 @@ func ServeHttp(bindAddr string, dialer Dialer, zjuDnsResolve *DnsResolve) { w.WriteHeader(resp.StatusCode) - io.Copy(w, resp.Body) + _, _ = io.Copy(w, resp.Body) } }) diff --git a/service/keep_alive.go b/service/keep_alive.go new file mode 100644 index 0000000..50d44da --- /dev/null +++ b/service/keep_alive.go @@ -0,0 +1,27 @@ +package service + +import ( + "context" + "github.com/mythologyli/zju-connect/log" + "github.com/mythologyli/zju-connect/resolve" + "time" +) + +func KeepAlive(resolver *resolve.Resolver) { + remoteUDPResolver, err := resolver.RemoteUDPResolver() + if err != nil { + log.Printf("KeepAlive: %s", err) + panic("KeepAlive: " + err.Error()) + } + + for { + _, err := remoteUDPResolver.LookupIP(context.Background(), "ip4", "www.baidu.com") + if err != nil { + log.Printf("KeepAlive: %s", err) + } else { + log.Printf("KeepAlive: OK") + } + + time.Sleep(60 * time.Second) + } +} diff --git a/service/socks.go b/service/socks.go new file mode 100644 index 0000000..e1e0115 --- /dev/null +++ b/service/socks.go @@ -0,0 +1,35 @@ +package service + +import ( + "github.com/mythologyli/zju-connect/dial" + "github.com/mythologyli/zju-connect/log" + "github.com/mythologyli/zju-connect/resolve" + "github.com/things-go/go-socks5" +) + +func ServeSocks5(bindAddr string, dialer *dial.Dialer, resolver *resolve.Resolver, user string, password string) { + var authMethods []socks5.Authenticator + if user != "" && password != "" { + authMethods = append(authMethods, socks5.UserPassAuthenticator{ + Credentials: socks5.StaticCredentials{user: password}, + }) + + log.Println("Neither traffic nor credentials are encrypted in the SOCKS5 protocol!") + log.Println("DO NOT deploy it to the public network. All consequences and responsibilities have nothing to do with the developer") + } else { + authMethods = append(authMethods, socks5.NoAuthAuthenticator{}) + } + + server := socks5.NewServer( + socks5.WithAuthMethods(authMethods), + socks5.WithResolver(resolver), + socks5.WithDial(dialer.DialIPPort), + socks5.WithLogger(socks5.NewLogger(log.NewLogger("[SOCKS5] "))), + ) + + log.Printf("SOCKS5 server listening on " + bindAddr) + + if err := server.ListenAndServe("tcp", bindAddr); err != nil { + panic("SOCKS5 listen failed: " + err.Error()) + } +} diff --git a/service/tcp_forwarding.go b/service/tcp_forwarding.go new file mode 100644 index 0000000..dc29577 --- /dev/null +++ b/service/tcp_forwarding.go @@ -0,0 +1,60 @@ +package service + +import ( + "github.com/mythologyli/zju-connect/log" + "github.com/mythologyli/zju-connect/stack" + "io" + "net" + "strconv" + "strings" +) + +func handleRequest(stack stack.Stack, conn net.Conn, remoteAddress string) { + log.Printf("Port forwarding (TCP): %s -> %s -> %s", conn.RemoteAddr(), conn.LocalAddr(), remoteAddress) + + parts := strings.Split(remoteAddress, ":") + host := parts[0] + port, err := strconv.Atoi(parts[1]) + if err != nil { + panic(err) + } + + proxy, err := stack.DialTCP(&net.TCPAddr{ + IP: net.ParseIP(host), + Port: port, + }) + if err != nil { + panic(err) + } + + go copyIO(conn, proxy) + go copyIO(proxy, conn) +} + +func copyIO(src, dest net.Conn) { + defer func(src net.Conn) { + _ = src.Close() + }(src) + defer func(dest net.Conn) { + _ = dest.Close() + }(dest) + _, _ = io.Copy(src, dest) +} + +func ServeTCPForwarding(stack stack.Stack, bindAddress string, remoteAddress string) { + ln, err := net.Listen("tcp", bindAddress) + if err != nil { + panic(err) + } + + log.Printf("TCP port forwarding: %s -> %s", bindAddress, remoteAddress) + + for { + conn, err := ln.Accept() + if err != nil { + panic(err) + } + + go handleRequest(stack, conn, remoteAddress) + } +} diff --git a/service/udp_forwarding.go b/service/udp_forwarding.go new file mode 100644 index 0000000..5f56e4f --- /dev/null +++ b/service/udp_forwarding.go @@ -0,0 +1,218 @@ +package service + +import ( + "fmt" + "github.com/mythologyli/zju-connect/log" + "github.com/mythologyli/zju-connect/stack" + "net" + "strconv" + "sync" + "time" +) + +const BufferSize = 40960 +const DefaultTimeout = time.Minute * 5 + +type UDPForward struct { + src *net.UDPAddr + dest *net.UDPAddr + stack stack.Stack + client *net.UDPAddr + listenerConn *net.UDPConn + + connections map[string]*UDPConnection + connectionsMutex *sync.RWMutex + + connectCallback func(addr string) + disconnectCallback func(addr string) + + timeout time.Duration + + closed bool +} + +type UDPConnection struct { + available chan struct{} + udp net.Conn + lastActive time.Time +} + +func newUDPForward(stack stack.Stack, src, dest string) *UDPForward { + u := new(UDPForward) + u.stack = stack + u.connectCallback = func(addr string) {} + u.disconnectCallback = func(addr string) {} + u.connectionsMutex = new(sync.RWMutex) + u.connections = make(map[string]*UDPConnection) + u.timeout = DefaultTimeout + + var err error + u.src, err = net.ResolveUDPAddr("udp", src) + if err != nil { + panic(err) + } + + host, portStr, err := net.SplitHostPort(dest) + if err != nil { + panic(err) + } + + port, err := strconv.Atoi(portStr) + if err != nil { + panic(err) + } + + ip := net.ParseIP(host) + if ip == nil { + panic(fmt.Errorf("invalid host: %s", host)) + } + + u.dest = &net.UDPAddr{ + IP: ip, + Port: port, + } + + u.listenerConn, err = net.ListenUDP("udp", u.src) + if err != nil { + panic(err) + } + + return u +} + +func (u *UDPForward) startUDPForward() { + go u.janitor() + for { + buf := make([]byte, BufferSize) + n, addr, err := u.listenerConn.ReadFromUDP(buf) + if err != nil { + log.Println("UDP forward: failed to read, terminating:", err) + return + } + + log.Printf("Port forwarding (UDP): %s -> %s -> %s", addr.String(), u.src.String(), u.dest.String()) + go u.handle(buf[:n], addr) + } +} + +func (u *UDPForward) janitor() { + for !u.closed { + time.Sleep(u.timeout) + var keysToDelete []string + + u.connectionsMutex.RLock() + for k, conn := range u.connections { + if conn.lastActive.Before(time.Now().Add(-u.timeout)) { + keysToDelete = append(keysToDelete, k) + } + } + u.connectionsMutex.RUnlock() + + u.connectionsMutex.Lock() + for _, k := range keysToDelete { + u.connections[k].udp.Close() + delete(u.connections, k) + } + u.connectionsMutex.Unlock() + + for _, k := range keysToDelete { + u.disconnectCallback(k) + } + } +} + +func (u *UDPForward) handle(data []byte, addr *net.UDPAddr) { + u.connectionsMutex.Lock() + conn, found := u.connections[addr.String()] + if !found { + u.connections[addr.String()] = &UDPConnection{ + available: make(chan struct{}), + udp: nil, + lastActive: time.Now(), + } + } + u.connectionsMutex.Unlock() + + if !found { + var udpConn net.Conn + var err error + + udpConn, err = u.stack.DialUDP(&net.UDPAddr{ + IP: u.dest.IP, + Port: u.dest.Port, + }) + + if err != nil { + log.Println("UDP forward: failed to dial:", err) + delete(u.connections, addr.String()) + return + } + + u.connectionsMutex.Lock() + u.connections[addr.String()].udp = udpConn + u.connections[addr.String()].lastActive = time.Now() + close(u.connections[addr.String()].available) + u.connectionsMutex.Unlock() + + u.connectCallback(addr.String()) + + _, err = udpConn.Write(data) + if err != nil { + log.Println("UDP forward: error sending initial packet to client", err) + } + + for { + buf := make([]byte, BufferSize) + n, err := udpConn.Read(buf) + if err != nil { + u.connectionsMutex.Lock() + udpConn.Close() + delete(u.connections, addr.String()) + u.connectionsMutex.Unlock() + u.disconnectCallback(addr.String()) + log.Println("udp-forward: abnormal read, closing:", err) + return + } + + _, _, err = u.listenerConn.WriteMsgUDP(buf[:n], nil, addr) + if err != nil { + log.Println("UDP forward: error sending packet to client:", err) + } + } + } + + <-conn.available + + _, err := conn.udp.Write(data) + if err != nil { + log.Println("UDP forward: error sending packet to server:", err) + } + + shouldChangeTime := false + u.connectionsMutex.RLock() + if _, found := u.connections[addr.String()]; found { + if u.connections[addr.String()].lastActive.Before( + time.Now().Add(u.timeout / 4)) { + shouldChangeTime = true + } + } + u.connectionsMutex.RUnlock() + + if shouldChangeTime { + u.connectionsMutex.Lock() + + if _, found := u.connections[addr.String()]; found { + connWrapper := u.connections[addr.String()] + connWrapper.lastActive = time.Now() + u.connections[addr.String()] = connWrapper + } + u.connectionsMutex.Unlock() + } +} + +func ServeUDPForwarding(stack stack.Stack, bindAddress string, remoteAddress string) { + log.Printf("UDP port forwarding: %s -> %s", bindAddress, remoteAddress) + + udpForward := newUDPForward(stack, bindAddress, remoteAddress) + udpForward.startUDPForward() +} diff --git a/stack/gvisor/dial.go b/stack/gvisor/dial.go new file mode 100644 index 0000000..fe76d7d --- /dev/null +++ b/stack/gvisor/dial.go @@ -0,0 +1,24 @@ +package gvisor + +import ( + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/header" + "net" +) + +func (s *Stack) DialTCP(addr *net.TCPAddr) (net.Conn, error) { + return gonet.DialTCP(s.gvisorStack, tcpip.FullAddress{ + NIC: NICID, + Port: uint16(addr.Port), + Addr: tcpip.AddrFromSlice(addr.IP), + }, header.IPv4ProtocolNumber) +} + +func (s *Stack) DialUDP(addr *net.UDPAddr) (net.Conn, error) { + return gonet.DialUDP(s.gvisorStack, nil, &tcpip.FullAddress{ + NIC: NICID, + Port: uint16(addr.Port), + Addr: tcpip.AddrFromSlice(addr.IP), + }, header.IPv4ProtocolNumber) +} diff --git a/stack/gvisor/stack.go b/stack/gvisor/stack.go new file mode 100644 index 0000000..20c70cd --- /dev/null +++ b/stack/gvisor/stack.go @@ -0,0 +1,194 @@ +package gvisor + +import ( + "errors" + "github.com/mythologyli/zju-connect/client" + "github.com/mythologyli/zju-connect/log" + "github.com/refraction-networking/utls" + "gvisor.dev/gvisor/pkg/buffer" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" + "gvisor.dev/gvisor/pkg/tcpip/transport/udp" +) + +type Stack struct { + gvisorStack *stack.Stack + + endpoint *Endpoint +} + +const NICID tcpip.NICID = 1 +const MTU uint32 = 1400 + +type Endpoint struct { + easyConnectClient *client.EasyConnectClient + + sendConn *tls.UConn + recvConn *tls.UConn + sendErrCount int + recvErrCount int + + dispatcher stack.NetworkDispatcher +} + +func (ep *Endpoint) ParseHeader(stack.PacketBufferPtr) bool { + return true +} + +func (ep *Endpoint) MTU() uint32 { + return MTU +} + +func (ep *Endpoint) MaxHeaderLength() uint16 { + return 0 +} + +func (ep *Endpoint) LinkAddress() tcpip.LinkAddress { + return "" +} + +func (ep *Endpoint) Capabilities() stack.LinkEndpointCapabilities { + return stack.CapabilityNone +} + +func (ep *Endpoint) Attach(dispatcher stack.NetworkDispatcher) { + ep.dispatcher = dispatcher +} + +func (ep *Endpoint) IsAttached() bool { + return ep.dispatcher != nil +} + +func (ep *Endpoint) Wait() {} + +func (ep *Endpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareNone +} + +func (ep *Endpoint) AddHeader(stack.PacketBufferPtr) {} + +// WritePackets is called when get packets from gVisor stack. Then it sends them to VPN server +func (ep *Endpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { + for _, packetBuffer := range list.AsSlice() { + var buf []byte + for _, t := range packetBuffer.AsSlices() { + buf = append(buf, t...) + } + + if ep.sendConn != nil { + n, err := ep.sendConn.Write(buf) + if err != nil { + if ep.sendErrCount < 5 { + log.Printf("Error occurred while sending, retrying: %v", err) + + // Do handshake again and create a new sendConn + ep.sendConn, err = ep.easyConnectClient.SendConn() + if err != nil { + panic(err) + } + } else { + panic("send retry limit exceeded.") + } + + ep.sendErrCount++ + } else { + log.DebugPrintf("Send: wrote %d bytes", n) + log.DebugDumpHex(buf[:n]) + } + } + } + + return list.Len(), nil +} + +func NewStack(easyConnectClient *client.EasyConnectClient) (*Stack, error) { + s := &Stack{} + + s.gvisorStack = stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol}, + TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol}, + HandleLocal: true, + }) + + s.endpoint = &Endpoint{ + easyConnectClient: easyConnectClient, + } + + tcpipErr := s.gvisorStack.CreateNIC(NICID, s.endpoint) + if tcpipErr != nil { + return nil, errors.New(tcpipErr.String()) + } + + ip, err := easyConnectClient.IP() + if err != nil { + return nil, err + } + + addr := tcpip.AddrFromSlice(ip) + protoAddr := tcpip.ProtocolAddress{ + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: addr, + PrefixLen: 32, + }, + Protocol: ipv4.ProtocolNumber, + } + + tcpipErr = s.gvisorStack.AddProtocolAddress(NICID, protoAddr, stack.AddressProperties{}) + if tcpipErr != nil { + return nil, errors.New(tcpipErr.String()) + } + + sOpt := tcpip.TCPSACKEnabled(true) + s.gvisorStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt) + cOpt := tcpip.CongestionControlOption("cubic") + s.gvisorStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt) + s.gvisorStack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: NICID}) + + return s, nil +} + +func (s *Stack) Run() { + var err error + s.endpoint.sendConn, err = s.endpoint.easyConnectClient.SendConn() + if err != nil { + panic(err) + } + + s.endpoint.recvConn, err = s.endpoint.easyConnectClient.RecvConn() + if err != nil { + panic(err) + } + + // Read from VPN server and send to gVisor stack + for { + buf := make([]byte, 1500) + n, err := s.endpoint.recvConn.Read(buf) + if err != nil { + if s.endpoint.recvErrCount < 5 { + log.Printf("Error occurred while receiving, retrying: %v", err) + + // Do handshake again and create a new recvConn + s.endpoint.recvConn, err = s.endpoint.easyConnectClient.RecvConn() + if err != nil { + panic(err) + } + } else { + panic("recv retry limit exceeded.") + } + + s.endpoint.recvErrCount++ + } else { + log.DebugPrintf("Recv: read %d bytes", n) + log.DebugDumpHex(buf[:n]) + + packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(buf), + }) + s.endpoint.dispatcher.DeliverNetworkPacket(header.IPv4ProtocolNumber, packetBuffer) + packetBuffer.DecRef() + } + } +} diff --git a/stack/stack.go b/stack/stack.go new file mode 100644 index 0000000..361880f --- /dev/null +++ b/stack/stack.go @@ -0,0 +1,9 @@ +package stack + +import "net" + +type Stack interface { + Run() + DialTCP(addr *net.TCPAddr) (net.Conn, error) + DialUDP(addr *net.UDPAddr) (net.Conn, error) +} diff --git a/stack/tun/dial_darwin.go b/stack/tun/dial_darwin.go new file mode 100644 index 0000000..e0c339a --- /dev/null +++ b/stack/tun/dial_darwin.go @@ -0,0 +1,13 @@ +package tun + +import ( + "net" +) + +func (s *Stack) DialTCP(addr *net.TCPAddr) (net.Conn, error) { + return s.endpoint.dialer.Dial("tcp4", addr.String()) +} + +func (s *Stack) DialUDP(addr *net.UDPAddr) (net.Conn, error) { + return s.endpoint.dialer.Dial("udp4", addr.String()) +} diff --git a/stack/tun/dial_linux.go b/stack/tun/dial_linux.go new file mode 100644 index 0000000..e0c339a --- /dev/null +++ b/stack/tun/dial_linux.go @@ -0,0 +1,13 @@ +package tun + +import ( + "net" +) + +func (s *Stack) DialTCP(addr *net.TCPAddr) (net.Conn, error) { + return s.endpoint.dialer.Dial("tcp4", addr.String()) +} + +func (s *Stack) DialUDP(addr *net.UDPAddr) (net.Conn, error) { + return s.endpoint.dialer.Dial("udp4", addr.String()) +} diff --git a/stack/tun/dial_windows.go b/stack/tun/dial_windows.go new file mode 100644 index 0000000..fc356d5 --- /dev/null +++ b/stack/tun/dial_windows.go @@ -0,0 +1,19 @@ +package tun + +import ( + "net" +) + +func (s *Stack) DialTCP(addr *net.TCPAddr) (net.Conn, error) { + return net.DialTCP("tcp4", &net.TCPAddr{ + IP: s.endpoint.ip, + Port: 0, + }, addr) +} + +func (s *Stack) DialUDP(addr *net.UDPAddr) (net.Conn, error) { + return net.DialUDP("udp4", &net.UDPAddr{ + IP: s.endpoint.ip, + Port: 0, + }, addr) +} diff --git a/stack/tun/stack.go b/stack/tun/stack.go new file mode 100644 index 0000000..192afd9 --- /dev/null +++ b/stack/tun/stack.go @@ -0,0 +1,99 @@ +package tun + +import ( + "github.com/mythologyli/zju-connect/log" + "golang.org/x/net/ipv4" +) + +const tcpProtocolNumber = 6 +const udpProtocolNumber = 17 + +type Stack struct { + endpoint *Endpoint +} + +func (s *Stack) Run() { + sendConn, err := s.endpoint.easyConnectClient.SendConn() + if err != nil { + panic(err) + } + + recvConn, err := s.endpoint.easyConnectClient.RecvConn() + if err != nil { + panic(err) + } + + sendErrCount := 0 + recvErrCount := 0 + + // Read from VPN server and send to TUN stack + go func() { + for { + buf := make([]byte, 1500) + n, err := recvConn.Read(buf) + if err != nil { + if recvErrCount < 5 { + log.Printf("Error occurred while receiving, retrying: %v", err) + + // Do handshake again and create a new recvConn + recvConn, err = s.endpoint.easyConnectClient.RecvConn() + if err != nil { + panic(err) + } + } else { + panic("recv retry limit exceeded.") + } + + recvErrCount++ + } else { + log.DebugPrintf("Recv: read %d bytes", n) + log.DebugDumpHex(buf[:n]) + + err := s.endpoint.Write(buf[:n]) + if err != nil { + return + } + } + } + }() + + // Read from TUN stack and send to VPN server + for { + buf := make([]byte, 1500) + n, err := s.endpoint.Read(buf) + + header, err := ipv4.ParseHeader(buf[:n]) + if err != nil { + continue + } + + // Filter out non-TCP/UDP packets otherwise error may occur + if header.Protocol != tcpProtocolNumber && header.Protocol != udpProtocolNumber { + continue + } + + if err != nil { + if sendErrCount < 5 { + log.Printf("Error occurred while sending, retrying: %v", err) + + // Do handshake again and create a new sendConn + sendConn, err = s.endpoint.easyConnectClient.SendConn() + if err != nil { + panic(err) + } + } else { + panic("send retry limit exceeded.") + } + + sendErrCount++ + } else { + log.DebugPrintf("Send: wrote %d bytes", n) + log.DebugDumpHex(buf[:n]) + + _, err := sendConn.Write(buf[:n]) + if err != nil { + return + } + } + } +} diff --git a/stack/tun/stack_darwin.go b/stack/tun/stack_darwin.go new file mode 100644 index 0000000..f542c14 --- /dev/null +++ b/stack/tun/stack_darwin.go @@ -0,0 +1,105 @@ +package tun + +import ( + "github.com/mythologyli/zju-connect/client" + "github.com/mythologyli/zju-connect/log" + "github.com/songgao/water" + "golang.org/x/sys/unix" + "net" + "os/exec" + "syscall" +) + +type Endpoint struct { + easyConnectClient *client.EasyConnectClient + + ifce *water.Interface + ip net.IP + + dialer *net.Dialer +} + +func (ep *Endpoint) Write(buf []byte) error { + _, err := ep.ifce.Write(buf) + return err +} + +func (ep *Endpoint) Read(buf []byte) (int, error) { + return ep.ifce.Read(buf) +} + +func (s *Stack) AddRoute(target string) error { + command := exec.Command("route", "-n", "add", "-net", target, "-interface", s.endpoint.ifce.Name()) + err := command.Run() + if err != nil { + return err + } + + return nil +} + +func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*Stack, error) { + s := &Stack{} + + ifce, err := water.New(water.Config{ + DeviceType: water.TUN, + }) + if err != nil { + return nil, err + } + + log.Printf("Interface Name: %s\n", ifce.Name()) + + s.endpoint = &Endpoint{ + easyConnectClient: easyConnectClient, + } + + s.endpoint.ifce = ifce + + // Get index of TUN interface + netIfce, err := net.InterfaceByName(ifce.Name()) + if err != nil { + return nil, err + } + + ifceIndex := netIfce.Index + + s.endpoint.ip, err = easyConnectClient.IP() + if err != nil { + return nil, err + } + + // We need this dialer to bind to device otherwise packets will not be sent via TUN + s.endpoint.dialer = &net.Dialer{ + LocalAddr: &net.TCPAddr{ + IP: s.endpoint.ip, + Port: 0, + }, + Control: func(network, address string, c syscall.RawConn) error { // By ChenXuzheng + return c.Control(func(fd uintptr) { + if err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVIF, ifceIndex); err != nil { + log.Println("Warning: failed to bind to interface", s.endpoint.ifce.Name()) + } + }) + }, + } + + cmd := exec.Command("ifconfig", ifce.Name(), s.endpoint.ip.String(), "255.0.0.0", s.endpoint.ip.String()) + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) + } + + if err = s.endpoint.AddRoute("10.0.0.0/8"); err != nil { + log.Printf("Run AddRoute 10.0.0.0/8 failed: %v", err) + } + + // Set MTU to 1400 otherwise error may occur when packets are large + cmd = exec.Command("ifconfig", ifce.Name(), "mtu", "1400", "up") + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) + } + + return s, nil +} diff --git a/stack/tun/stack_linux.go b/stack/tun/stack_linux.go new file mode 100644 index 0000000..dcdb6df --- /dev/null +++ b/stack/tun/stack_linux.go @@ -0,0 +1,98 @@ +package tun + +import ( + "github.com/mythologyli/zju-connect/client" + "github.com/mythologyli/zju-connect/log" + "github.com/songgao/water" + "net" + "os/exec" + "syscall" +) + +type Endpoint struct { + easyConnectClient *client.EasyConnectClient + + ifce *water.Interface + ip net.IP + + dialer *net.Dialer +} + +func (ep *Endpoint) Write(buf []byte) error { + _, err := ep.ifce.Write(buf) + return err +} + +func (ep *Endpoint) Read(buf []byte) (int, error) { + return ep.ifce.Read(buf) +} + +func (s *Stack) AddRoute(target string) error { + command := exec.Command("ip", "route", "add", target, "dev", s.endpoint.ifce.Name()) + err := command.Run() + if err != nil { + return err + } + + return nil +} + +func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*Stack, error) { + s := &Stack{} + + ifce, err := water.New(water.Config{ + DeviceType: water.TUN, + }) + if err != nil { + return nil, err + } + + log.Printf("Interface Name: %s\n", ifce.Name()) + + s.endpoint = &Endpoint{ + easyConnectClient: easyConnectClient, + } + + s.endpoint.ifce = ifce + + s.endpoint.ip, err = easyConnectClient.IP() + if err != nil { + return nil, err + } + + // We need this dialer to bind to device otherwise packets will not be sent via TUN + s.endpoint.dialer = &net.Dialer{ + LocalAddr: &net.TCPAddr{ + IP: s.endpoint.ip, + Port: 0, + }, + Control: func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + if err := syscall.BindToDevice(int(fd), s.endpoint.ifce.Name()); err != nil { + log.Println("Warning: failed to bind to interface", s.endpoint.ifce.Name()) + } + }) + }, + } + + cmd := exec.Command("ip", "link", "set", ifce.Name(), "up") + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) + } + + // Set MTU to 1400 otherwise error may occur when packets are large + cmd = exec.Command("ip", "link", "set", "dev", ifce.Name(), "mtu", "1400") + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) + } + + cmd = exec.Command("ip", "addr", "add", s.endpoint.ip.String()+"/8", "dev", ifce.Name()) + err = cmd.Run() + if err != nil { + log.Printf("Run %s failed: %v", cmd.String(), err) + } + + return s, nil +} diff --git a/core/tun_stack_windows.go b/stack/tun/stack_windows.go similarity index 50% rename from core/tun_stack_windows.go rename to stack/tun/stack_windows.go index 348f26f..99728d1 100644 --- a/core/tun_stack_windows.go +++ b/stack/tun/stack_windows.go @@ -1,22 +1,28 @@ -package core +package tun import ( "fmt" + "github.com/mythologyli/zju-connect/client" + "github.com/mythologyli/zju-connect/log" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" - "log" "net" "net/netip" "os/exec" ) -type EasyConnectTunEndpoint struct { - dev tun.Device - selfIp string +const guid = "{4F5CDE94-D2A3-4AA5-A4A3-0FE6CB909E83}" +const interfaceName = "ZJU Connect" + +type Endpoint struct { + easyConnectClient *client.EasyConnectClient + + dev tun.Device + ip net.IP } -func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { +func (ep *Endpoint) Write(buf []byte) error { bufs := [][]byte{buf} _, err := ep.dev.Write(bufs, 0) @@ -27,7 +33,7 @@ func (ep *EasyConnectTunEndpoint) Write(buf []byte) error { return nil } -func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { +func (ep *Endpoint) Read(buf []byte) (int, error) { bufs := [][]byte{buf} sizes := []int{1} @@ -39,7 +45,7 @@ func (ep *EasyConnectTunEndpoint) Read(buf []byte) (int, error) { return sizes[0], nil } -func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { +func (s *Stack) AddRoute(target string) error { ipaddr, ipv4Net, err := net.ParseCIDR(target) if err != nil { return err @@ -50,7 +56,7 @@ func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { return fmt.Errorf("not a valid IPv4 address") } - command := exec.Command("route", "add", ip.String(), "mask", net.IP(ipv4Net.Mask).String(), ep.selfIp, "metric", "1") + command := exec.Command("route", "add", ip.String(), "mask", net.IP(ipv4Net.Mask).String(), s.endpoint.ip.String(), "metric", "1") err = command.Run() if err != nil { return err @@ -59,30 +65,37 @@ func (ep *EasyConnectTunEndpoint) AddRoute(target string) error { return nil } -func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { - guid, err := windows.GUIDFromString("{4F5CDE94-D2A3-4AA5-A4A3-0FE6CB909E83}") +func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*Stack, error) { + s := &Stack{} + + guid, err := windows.GUIDFromString(guid) if err != nil { - panic(err) + return nil, err } - dev, err := tun.CreateTUNWithRequestedGUID("ZJU Connect", &guid, 1400) + dev, err := tun.CreateTUNWithRequestedGUID(interfaceName, &guid, 1400) if err != nil { - panic(err) + return nil, err + } + + s.endpoint = &Endpoint{ + easyConnectClient: easyConnectClient, } - endpoint.dev = dev + s.endpoint.dev = dev nativeTunDevice := dev.(*tun.NativeTun) link := winipcfg.LUID(nativeTunDevice.LUID()) - ipStr := fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) - - endpoint.selfIp = ipStr + s.endpoint.ip, err = easyConnectClient.IP() + if err != nil { + return nil, err + } - prefix, err := netip.ParsePrefix(ipStr + "/8") + prefix, err := netip.ParsePrefix(s.endpoint.ip.String() + "/8") if err != nil { - log.Printf("Parse prefix failed: %v", err) + log.Printf("Parse prefix failed: %v", err) // Fail to set TUN IP is not a fatal problem, so we don't return an error } err = link.SetIPAddresses([]netip.Prefix{prefix}) @@ -90,26 +103,30 @@ func SetupTunStack(ip []byte, endpoint *EasyConnectTunEndpoint) { log.Printf("Set IP address failed: %v", err) } - command := exec.Command("netsh", "interface", "ipv4", "set", "subinterface", "ZJU Connect", "mtu=1400", "store=persistent") + // Set MTU to 1400 otherwise error may occur when packets are large + command := exec.Command("netsh", "interface", "ipv4", "set", "subinterface", interfaceName, "mtu=1400", "store=persistent") err = command.Run() if err != nil { log.Printf("Run %s failed: %v", command.String(), err) } - command = exec.Command("route", "add", "0.0.0.0", "mask", "0.0.0.0", ipStr, "metric", "9999") + // We must add a route to 0.0.0.0/0, otherwise Windows will refuse to send packets to public network via TUN interface + // Set metric to 9999 to make sure normal traffic not go through TUN interface + command = exec.Command("route", "add", "0.0.0.0", "mask", "0.0.0.0", s.endpoint.ip.String(), "metric", "9999") err = command.Run() if err != nil { log.Printf("Run %s failed: %v", command.String(), err) } - if TunDnsServer != "" { - command = exec.Command("netsh", "interface", "ipv4", "add", "dnsservers", "ZJU Connect", TunDnsServer) + if dnsServer != "" { + command = exec.Command("netsh", "interface", "ipv4", "add", "dnsservers", "ZJU Connect", dnsServer) } else { command = exec.Command("netsh", "interface", "ipv4", "delete", "dnsservers", "ZJU Connect", "all") } - err = command.Run() if err != nil { log.Printf("Run %s failed: %v", command.String(), err) } + + return s, nil } From 4b9699e9da2442b967f6518b17882b0886dfbca2 Mon Sep 17 00:00:00 2001 From: Myth Date: Wed, 25 Oct 2023 23:38:20 +0800 Subject: [PATCH 21/28] fix: add route in darwin --- stack/tun/stack_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stack/tun/stack_darwin.go b/stack/tun/stack_darwin.go index f542c14..42e9d90 100644 --- a/stack/tun/stack_darwin.go +++ b/stack/tun/stack_darwin.go @@ -90,7 +90,7 @@ func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*S log.Printf("Run %s failed: %v", cmd.String(), err) } - if err = s.endpoint.AddRoute("10.0.0.0/8"); err != nil { + if err = s.AddRoute("10.0.0.0/8"); err != nil { log.Printf("Run AddRoute 10.0.0.0/8 failed: %v", err) } From ceac623bf9d112390ba30c6b23c58ed630e54f68 Mon Sep 17 00:00:00 2001 From: Myth Date: Wed, 25 Oct 2023 23:50:11 +0800 Subject: [PATCH 22/28] fix: don't exit in init on error --- init.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/init.go b/init.go index f4a5c2e..76f09c6 100644 --- a/init.go +++ b/init.go @@ -205,14 +205,14 @@ func init() { if showVersion { fmt.Printf("ZJU Connect v%s\n", zjuConnectVersion) - return + os.Exit(0) } if configFile != "" { err := parseTOMLConfig(configFile, &conf) if err != nil { fmt.Println(err) - return + os.Exit(1) } } else { if tcpPortForwarding != "" { @@ -221,7 +221,7 @@ func init() { addressStringList := strings.Split(forwardingString, "-") if len(addressStringList) != 2 { fmt.Println("ZJU Connect: wrong tcp port forwarding format") - return + os.Exit(1) } conf.PortForwardingList = append(conf.PortForwardingList, SinglePortForwarding{ @@ -238,7 +238,7 @@ func init() { addressStringList := strings.Split(forwardingString, "-") if len(addressStringList) != 2 { fmt.Println("ZJU Connect: wrong udp port forwarding format") - return + os.Exit(1) } conf.PortForwardingList = append(conf.PortForwardingList, SinglePortForwarding{ @@ -255,7 +255,7 @@ func init() { dnsStringSplit := strings.Split(dnsString, ":") if len(dnsStringSplit) != 2 { fmt.Println("ZJU Connect: wrong custom dns format") - return + os.Exit(1) } conf.CustomDNSList = append(conf.CustomDNSList, SingleCustomDNS{ @@ -273,6 +273,6 @@ func init() { fmt.Println("\nFull usage:") flag.PrintDefaults() - return + os.Exit(1) } } From 312c87098cd8ed7812a790b33f4a6891121d9bc2 Mon Sep 17 00:00:00 2001 From: ChenXuzheng <1092889706@qq.com> Date: Thu, 26 Oct 2023 00:31:22 +0800 Subject: [PATCH 23/28] chore: close conn before new connection --- stack/gvisor/stack.go | 2 ++ stack/tun/stack.go | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stack/gvisor/stack.go b/stack/gvisor/stack.go index 20c70cd..9d17ec0 100644 --- a/stack/gvisor/stack.go +++ b/stack/gvisor/stack.go @@ -85,6 +85,7 @@ func (ep *Endpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) log.Printf("Error occurred while sending, retrying: %v", err) // Do handshake again and create a new sendConn + ep.sendConn.Close() ep.sendConn, err = ep.easyConnectClient.SendConn() if err != nil { panic(err) @@ -171,6 +172,7 @@ func (s *Stack) Run() { log.Printf("Error occurred while receiving, retrying: %v", err) // Do handshake again and create a new recvConn + s.endpoint.recvConn.Close() s.endpoint.recvConn, err = s.endpoint.easyConnectClient.RecvConn() if err != nil { panic(err) diff --git a/stack/tun/stack.go b/stack/tun/stack.go index 192afd9..e45b340 100644 --- a/stack/tun/stack.go +++ b/stack/tun/stack.go @@ -3,11 +3,9 @@ package tun import ( "github.com/mythologyli/zju-connect/log" "golang.org/x/net/ipv4" + "syscall" ) -const tcpProtocolNumber = 6 -const udpProtocolNumber = 17 - type Stack struct { endpoint *Endpoint } @@ -36,6 +34,7 @@ func (s *Stack) Run() { log.Printf("Error occurred while receiving, retrying: %v", err) // Do handshake again and create a new recvConn + recvConn.Close() recvConn, err = s.endpoint.easyConnectClient.RecvConn() if err != nil { panic(err) @@ -68,7 +67,7 @@ func (s *Stack) Run() { } // Filter out non-TCP/UDP packets otherwise error may occur - if header.Protocol != tcpProtocolNumber && header.Protocol != udpProtocolNumber { + if header.Protocol != syscall.IPPROTO_TCP && header.Protocol != syscall.IPPROTO_UDP { continue } @@ -77,6 +76,7 @@ func (s *Stack) Run() { log.Printf("Error occurred while sending, retrying: %v", err) // Do handshake again and create a new sendConn + sendConn.Close() sendConn, err = s.endpoint.easyConnectClient.SendConn() if err != nil { panic(err) From 302b70098f043f1800f9e627972136f724a9cb66 Mon Sep 17 00:00:00 2001 From: ChenXuzheng <1092889706@qq.com> Date: Thu, 26 Oct 2023 02:36:57 +0800 Subject: [PATCH 24/28] bugfix: use different dial for tcp/udp --- stack/tun/dial_darwin.go | 4 ++-- stack/tun/dial_linux.go | 4 ++-- stack/tun/stack_darwin.go | 19 +++++++++++++++++-- stack/tun/stack_linux.go | 19 +++++++++++++++++-- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/stack/tun/dial_darwin.go b/stack/tun/dial_darwin.go index e0c339a..4e0a63e 100644 --- a/stack/tun/dial_darwin.go +++ b/stack/tun/dial_darwin.go @@ -5,9 +5,9 @@ import ( ) func (s *Stack) DialTCP(addr *net.TCPAddr) (net.Conn, error) { - return s.endpoint.dialer.Dial("tcp4", addr.String()) + return s.endpoint.tcpDialer.Dial("tcp4", addr.String()) } func (s *Stack) DialUDP(addr *net.UDPAddr) (net.Conn, error) { - return s.endpoint.dialer.Dial("udp4", addr.String()) + return s.endpoint.udpDialer.Dial("udp4", addr.String()) } diff --git a/stack/tun/dial_linux.go b/stack/tun/dial_linux.go index e0c339a..4e0a63e 100644 --- a/stack/tun/dial_linux.go +++ b/stack/tun/dial_linux.go @@ -5,9 +5,9 @@ import ( ) func (s *Stack) DialTCP(addr *net.TCPAddr) (net.Conn, error) { - return s.endpoint.dialer.Dial("tcp4", addr.String()) + return s.endpoint.tcpDialer.Dial("tcp4", addr.String()) } func (s *Stack) DialUDP(addr *net.UDPAddr) (net.Conn, error) { - return s.endpoint.dialer.Dial("udp4", addr.String()) + return s.endpoint.udpDialer.Dial("udp4", addr.String()) } diff --git a/stack/tun/stack_darwin.go b/stack/tun/stack_darwin.go index 42e9d90..172f9b4 100644 --- a/stack/tun/stack_darwin.go +++ b/stack/tun/stack_darwin.go @@ -16,7 +16,8 @@ type Endpoint struct { ifce *water.Interface ip net.IP - dialer *net.Dialer + tcpDialer *net.Dialer + udpDialer *net.Dialer } func (ep *Endpoint) Write(buf []byte) error { @@ -70,7 +71,7 @@ func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*S } // We need this dialer to bind to device otherwise packets will not be sent via TUN - s.endpoint.dialer = &net.Dialer{ + s.endpoint.tcpDialer = &net.Dialer{ LocalAddr: &net.TCPAddr{ IP: s.endpoint.ip, Port: 0, @@ -84,6 +85,20 @@ func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*S }, } + s.endpoint.udpDialer = &net.Dialer{ + LocalAddr: &net.UDPAddr{ + IP: s.endpoint.ip, + Port: 0, + }, + Control: func(network, address string, c syscall.RawConn) error { // By ChenXuzheng + return c.Control(func(fd uintptr) { + if err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVIF, ifceIndex); err != nil { + log.Println("Warning: failed to bind to interface", s.endpoint.ifce.Name()) + } + }) + }, + } + cmd := exec.Command("ifconfig", ifce.Name(), s.endpoint.ip.String(), "255.0.0.0", s.endpoint.ip.String()) err = cmd.Run() if err != nil { diff --git a/stack/tun/stack_linux.go b/stack/tun/stack_linux.go index dcdb6df..b24d2b6 100644 --- a/stack/tun/stack_linux.go +++ b/stack/tun/stack_linux.go @@ -15,7 +15,8 @@ type Endpoint struct { ifce *water.Interface ip net.IP - dialer *net.Dialer + tcpDialer *net.Dialer + udpDialer *net.Dialer } func (ep *Endpoint) Write(buf []byte) error { @@ -61,7 +62,7 @@ func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*S } // We need this dialer to bind to device otherwise packets will not be sent via TUN - s.endpoint.dialer = &net.Dialer{ + s.endpoint.tcpDialer = &net.Dialer{ LocalAddr: &net.TCPAddr{ IP: s.endpoint.ip, Port: 0, @@ -75,6 +76,20 @@ func NewStack(easyConnectClient *client.EasyConnectClient, dnsServer string) (*S }, } + s.endpoint.udpDialer = &net.Dialer{ + LocalAddr: &net.UDPAddr{ + IP: s.endpoint.ip, + Port: 0, + }, + Control: func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + if err := syscall.BindToDevice(int(fd), s.endpoint.ifce.Name()); err != nil { + log.Println("Warning: failed to bind to interface", s.endpoint.ifce.Name()) + } + }) + }, + } + cmd := exec.Command("ip", "link", "set", ifce.Name(), "up") err = cmd.Run() if err != nil { From 1fe2f6aca62597c13960e7cd05a0c9cfb06cabd4 Mon Sep 17 00:00:00 2001 From: ChenXuzheng <1092889706@qq.com> Date: Sat, 28 Oct 2023 01:04:51 +0800 Subject: [PATCH 25/28] fix: change tun stack retry logic --- main.go | 2 +- stack/tun/stack.go | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index 1066f92..94a2ba8 100644 --- a/main.go +++ b/main.go @@ -75,7 +75,7 @@ func main() { if conf.TUNMode { vpnTUNStack, err := tun.NewStack(vpnClient, conf.TUNDNSServer) if err != nil { - log.Fatalf("gVisor stack setup error: %s", err) + log.Fatalf("Tun stack setup error: %s", err) } if conf.AddRoute && ipResource != nil { diff --git a/stack/tun/stack.go b/stack/tun/stack.go index e45b340..6f390f8 100644 --- a/stack/tun/stack.go +++ b/stack/tun/stack.go @@ -37,6 +37,7 @@ func (s *Stack) Run() { recvConn.Close() recvConn, err = s.endpoint.easyConnectClient.RecvConn() if err != nil { + // TODO graceful shutdown panic(err) } } else { @@ -50,7 +51,8 @@ func (s *Stack) Run() { err := s.endpoint.Write(buf[:n]) if err != nil { - return + log.Printf("Error occurred while writing to TUN stack: %v", err) + panic(err) } } } @@ -60,6 +62,15 @@ func (s *Stack) Run() { for { buf := make([]byte, 1500) n, err := s.endpoint.Read(buf) + if err != nil { + log.Printf("Error occurred while reading from TUN stack: %v", err) + // TODO graceful shutdown + panic(err) + } + + if n < 20 { + continue + } header, err := ipv4.ParseHeader(buf[:n]) if err != nil { @@ -71,7 +82,7 @@ func (s *Stack) Run() { continue } - if err != nil { + if _, err = sendConn.Write(buf[:n]); err != nil { if sendErrCount < 5 { log.Printf("Error occurred while sending, retrying: %v", err) @@ -84,16 +95,10 @@ func (s *Stack) Run() { } else { panic("send retry limit exceeded.") } - sendErrCount++ } else { log.DebugPrintf("Send: wrote %d bytes", n) log.DebugDumpHex(buf[:n]) - - _, err := sendConn.Write(buf[:n]) - if err != nil { - return - } } } } From 396d89fe081cd18e677f12cf819644c35e14dec6 Mon Sep 17 00:00:00 2001 From: ChenXuzheng <1092889706@qq.com> Date: Sat, 28 Oct 2023 01:57:19 +0800 Subject: [PATCH 26/28] feat: use rvpn_conn to unified connection with rvpn server --- client/rvpn_conn.go | 88 +++++++++++++++++++++++++++++++++++++++++++ stack/gvisor/stack.go | 77 +++++++++---------------------------- stack/tun/stack.go | 67 +++++++------------------------- 3 files changed, 119 insertions(+), 113 deletions(-) create mode 100644 client/rvpn_conn.go diff --git a/client/rvpn_conn.go b/client/rvpn_conn.go new file mode 100644 index 0000000..a23aaae --- /dev/null +++ b/client/rvpn_conn.go @@ -0,0 +1,88 @@ +package client + +import ( + "github.com/mythologyli/zju-connect/log" + "io" +) + +type RvpnConn struct { + easyConnectClient *EasyConnectClient + + sendConn io.WriteCloser + sendErrCount int + + recvConn io.ReadCloser + recvErrCount int +} + +// always success or panic +func (r *RvpnConn) Read(p []byte) (n int, err error) { + for n, err = r.recvConn.Read(p); err != nil && r.recvErrCount < 5; { + log.Printf("Error occurred while receiving, retrying: %v", err) + + // Do handshake again and create a new recvConn + r.recvConn.Close() + r.recvConn, err = r.easyConnectClient.RecvConn() + if err != nil { + // TODO graceful shutdown + panic(err) + } + r.recvErrCount++ + if r.recvErrCount >= 5 { + panic("recv retry limit exceeded.") + } + } + return +} + +// always success or panic +func (r *RvpnConn) Write(p []byte) (n int, err error) { + for n, err = r.sendConn.Write(p); err != nil && r.sendErrCount < 5; { + log.Printf("Error occurred while sending, retrying: %v", err) + + // Do handshake again and create a new sendConn + r.sendConn.Close() + r.sendConn, err = r.easyConnectClient.SendConn() + if err != nil { + // TODO graceful shutdown + panic(err) + } + r.sendErrCount++ + if r.sendErrCount >= 5 { + panic("send retry limit exceeded.") + } + } + return +} + +func (r *RvpnConn) Close() error { + if r.sendConn != nil { + r.sendConn.Close() + } + if r.recvConn != nil { + r.recvConn.Close() + } + return nil +} + +func NewRvpnConn(ec *EasyConnectClient) (*RvpnConn, error) { + c := &RvpnConn{ + easyConnectClient: ec, + sendErrCount: 0, + recvErrCount: 0, + } + + var err error + c.sendConn, err = ec.SendConn() + if err != nil { + log.Printf("Error occurred while creating sendConn: %v", err) + panic(err) + } + + c.recvConn, err = ec.RecvConn() + if err != nil { + log.Printf("Error occurred while creating recvConn: %v", err) + panic(err) + } + return c, nil +} diff --git a/stack/gvisor/stack.go b/stack/gvisor/stack.go index 9d17ec0..077e553 100644 --- a/stack/gvisor/stack.go +++ b/stack/gvisor/stack.go @@ -4,7 +4,6 @@ import ( "errors" "github.com/mythologyli/zju-connect/client" "github.com/mythologyli/zju-connect/log" - "github.com/refraction-networking/utls" "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" @@ -12,6 +11,7 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" "gvisor.dev/gvisor/pkg/tcpip/transport/udp" + "io" ) type Stack struct { @@ -26,10 +26,7 @@ const MTU uint32 = 1400 type Endpoint struct { easyConnectClient *client.EasyConnectClient - sendConn *tls.UConn - recvConn *tls.UConn - sendErrCount int - recvErrCount int + rvpnConn io.ReadWriteCloser dispatcher stack.NetworkDispatcher } @@ -78,27 +75,11 @@ func (ep *Endpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) buf = append(buf, t...) } - if ep.sendConn != nil { - n, err := ep.sendConn.Write(buf) - if err != nil { - if ep.sendErrCount < 5 { - log.Printf("Error occurred while sending, retrying: %v", err) - - // Do handshake again and create a new sendConn - ep.sendConn.Close() - ep.sendConn, err = ep.easyConnectClient.SendConn() - if err != nil { - panic(err) - } - } else { - panic("send retry limit exceeded.") - } - - ep.sendErrCount++ - } else { - log.DebugPrintf("Send: wrote %d bytes", n) - log.DebugDumpHex(buf[:n]) - } + if ep.rvpnConn != nil { + n, _ := ep.rvpnConn.Write(buf) + + log.DebugPrintf("Send: wrote %d bytes", n) + log.DebugDumpHex(buf[:n]) } } @@ -152,45 +133,21 @@ func NewStack(easyConnectClient *client.EasyConnectClient) (*Stack, error) { } func (s *Stack) Run() { - var err error - s.endpoint.sendConn, err = s.endpoint.easyConnectClient.SendConn() - if err != nil { - panic(err) - } - s.endpoint.recvConn, err = s.endpoint.easyConnectClient.RecvConn() - if err != nil { - panic(err) - } + s.endpoint.rvpnConn, _ = client.NewRvpnConn(s.endpoint.easyConnectClient) // Read from VPN server and send to gVisor stack for { buf := make([]byte, 1500) - n, err := s.endpoint.recvConn.Read(buf) - if err != nil { - if s.endpoint.recvErrCount < 5 { - log.Printf("Error occurred while receiving, retrying: %v", err) - - // Do handshake again and create a new recvConn - s.endpoint.recvConn.Close() - s.endpoint.recvConn, err = s.endpoint.easyConnectClient.RecvConn() - if err != nil { - panic(err) - } - } else { - panic("recv retry limit exceeded.") - } - - s.endpoint.recvErrCount++ - } else { - log.DebugPrintf("Recv: read %d bytes", n) - log.DebugDumpHex(buf[:n]) + n, _ := s.endpoint.rvpnConn.Read(buf) - packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Payload: buffer.MakeWithData(buf), - }) - s.endpoint.dispatcher.DeliverNetworkPacket(header.IPv4ProtocolNumber, packetBuffer) - packetBuffer.DecRef() - } + log.DebugPrintf("Recv: read %d bytes", n) + log.DebugDumpHex(buf[:n]) + + packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(buf), + }) + s.endpoint.dispatcher.DeliverNetworkPacket(header.IPv4ProtocolNumber, packetBuffer) + packetBuffer.DecRef() } } diff --git a/stack/tun/stack.go b/stack/tun/stack.go index 6f390f8..d2c9287 100644 --- a/stack/tun/stack.go +++ b/stack/tun/stack.go @@ -1,59 +1,34 @@ package tun import ( + "github.com/mythologyli/zju-connect/client" "github.com/mythologyli/zju-connect/log" "golang.org/x/net/ipv4" + "io" "syscall" ) type Stack struct { endpoint *Endpoint + rvpnConn io.ReadWriteCloser } func (s *Stack) Run() { - sendConn, err := s.endpoint.easyConnectClient.SendConn() - if err != nil { - panic(err) - } - - recvConn, err := s.endpoint.easyConnectClient.RecvConn() - if err != nil { - panic(err) - } - - sendErrCount := 0 - recvErrCount := 0 + s.rvpnConn, _ = client.NewRvpnConn(s.endpoint.easyConnectClient) // Read from VPN server and send to TUN stack go func() { for { buf := make([]byte, 1500) - n, err := recvConn.Read(buf) - if err != nil { - if recvErrCount < 5 { - log.Printf("Error occurred while receiving, retrying: %v", err) - - // Do handshake again and create a new recvConn - recvConn.Close() - recvConn, err = s.endpoint.easyConnectClient.RecvConn() - if err != nil { - // TODO graceful shutdown - panic(err) - } - } else { - panic("recv retry limit exceeded.") - } + n, _ := s.rvpnConn.Read(buf) - recvErrCount++ - } else { - log.DebugPrintf("Recv: read %d bytes", n) - log.DebugDumpHex(buf[:n]) + log.DebugPrintf("Recv: read %d bytes", n) + log.DebugDumpHex(buf[:n]) - err := s.endpoint.Write(buf[:n]) - if err != nil { - log.Printf("Error occurred while writing to TUN stack: %v", err) - panic(err) - } + err := s.endpoint.Write(buf[:n]) + if err != nil { + log.Printf("Error occurred while writing to TUN stack: %v", err) + panic(err) } } }() @@ -82,23 +57,9 @@ func (s *Stack) Run() { continue } - if _, err = sendConn.Write(buf[:n]); err != nil { - if sendErrCount < 5 { - log.Printf("Error occurred while sending, retrying: %v", err) + s.rvpnConn.Write(buf[:n]) - // Do handshake again and create a new sendConn - sendConn.Close() - sendConn, err = s.endpoint.easyConnectClient.SendConn() - if err != nil { - panic(err) - } - } else { - panic("send retry limit exceeded.") - } - sendErrCount++ - } else { - log.DebugPrintf("Send: wrote %d bytes", n) - log.DebugDumpHex(buf[:n]) - } + log.DebugPrintf("Send: wrote %d bytes", n) + log.DebugDumpHex(buf[:n]) } } From c9fbfb13b8fd375b843d82e4ca0fe499105e2400 Mon Sep 17 00:00:00 2001 From: Myth Date: Wed, 1 Nov 2023 18:59:06 +0800 Subject: [PATCH 27/28] chore: ignore errors explicitly --- client/rvpn_conn.go | 8 ++++---- stack/tun/stack.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/rvpn_conn.go b/client/rvpn_conn.go index a23aaae..57346b1 100644 --- a/client/rvpn_conn.go +++ b/client/rvpn_conn.go @@ -21,7 +21,7 @@ func (r *RvpnConn) Read(p []byte) (n int, err error) { log.Printf("Error occurred while receiving, retrying: %v", err) // Do handshake again and create a new recvConn - r.recvConn.Close() + _ = r.recvConn.Close() r.recvConn, err = r.easyConnectClient.RecvConn() if err != nil { // TODO graceful shutdown @@ -41,7 +41,7 @@ func (r *RvpnConn) Write(p []byte) (n int, err error) { log.Printf("Error occurred while sending, retrying: %v", err) // Do handshake again and create a new sendConn - r.sendConn.Close() + _ = r.sendConn.Close() r.sendConn, err = r.easyConnectClient.SendConn() if err != nil { // TODO graceful shutdown @@ -57,10 +57,10 @@ func (r *RvpnConn) Write(p []byte) (n int, err error) { func (r *RvpnConn) Close() error { if r.sendConn != nil { - r.sendConn.Close() + _ = r.sendConn.Close() } if r.recvConn != nil { - r.recvConn.Close() + _ = r.recvConn.Close() } return nil } diff --git a/stack/tun/stack.go b/stack/tun/stack.go index d2c9287..9f6ba8c 100644 --- a/stack/tun/stack.go +++ b/stack/tun/stack.go @@ -57,7 +57,7 @@ func (s *Stack) Run() { continue } - s.rvpnConn.Write(buf[:n]) + _, _ = s.rvpnConn.Write(buf[:n]) log.DebugPrintf("Send: wrote %d bytes", n) log.DebugDumpHex(buf[:n]) From 0687d44829a046d8dc73b242cb8bcbad0d84422c Mon Sep 17 00:00:00 2001 From: Myth Date: Wed, 1 Nov 2023 19:22:14 +0800 Subject: [PATCH 28/28] docs: update README.md --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce1649c..3bcc194 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,9 @@ $ docker compose up -d + `http-bind`: HTTP 代理监听地址,默认为 `:1081`。为 `""` 时不启用 HTTP 代理 -+ `tun-mode`: TUN 模式(实验性) ++ `tun-mode`: TUN 模式(实验性)。请阅读后文中的 TUN 模式注意事项 + ++ `add_route`: 启用 TUN 模式时根据服务端下发配置添加路由 + `dns-ttl`: DNS 缓存时间,默认为 `3600` 秒 @@ -228,6 +230,12 @@ $ docker compose up -d + `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`。多个转发用 `,` 分隔 @@ -240,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` 外的地址 + ### 计划表 #### 已完成 @@ -257,10 +275,12 @@ $ docker compose up -d - [x] UDP 端口转发功能 - [x] 通过配置文件启动 - [x] 定时保活 +- [x] TUN 模式 #### To Do -- [ ] TUN 模式 +- [ ] 自动劫持 DNS +- [ ] Fake IP 模式 ### 贡献者