Skip to content

Commit

Permalink
feat: use http proxy when socks5 proxy disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
Mythologyli committed Oct 21, 2023
1 parent f49cb5a commit ed52b1d
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 333 deletions.
64 changes: 47 additions & 17 deletions core/EasyConnectClient.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
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"
Expand Down Expand Up @@ -152,16 +156,54 @@ 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.Address(net.ParseIP(ZjuDnsServer).To4()),
}

bind := tcpip.FullAddress{
NIC: defaultNIC,
Addr: tcpip.Address(client.clientIp),
}

return gonet.DialUDP(client.ipStack, &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)
},
},
useTCP: false,
timer: nil,
}

dialer := Dialer{
selfIp: client.clientIp,
ipStack: client.ipStack,
}

if SocksBind != "" {
go client.ServeSocks5(SocksBind, ZjuDnsServer)
go ServeSocks5(SocksBind, dialer, &dnsResolve)
}

if HttpBind != "" {
go client.ServeHttp(HttpBind, SocksBind, SocksUser, SocksPasswd)
}
if HttpBind != "" {
go ServeHttp(HttpBind, dialer, &dnsResolve)
}

if EnableKeepAlive {
go client.KeepAlive(ZjuDnsServer)
go KeepAlive(ZjuDnsServer, client.ipStack, client.clientIp)
}

for {
Expand Down Expand Up @@ -249,14 +291,6 @@ func (client *EasyConnectClient) GetClientIp() ([]byte, error) {
return client.clientIp, nil
}

func (client *EasyConnectClient) ServeSocks5(socksBind string, dnsServer string) {
ServeSocks5(client.ipStack, client.clientIp, socksBind, dnsServer)
}

func (client *EasyConnectClient) ServeHttp(httpBind string, socksBind string, socksUser string, socksPasswd string) {
ServeHttp(httpBind, socksBind, socksUser, socksPasswd)
}

func (client *EasyConnectClient) ServeForwarding(networkType string, bindAddress string, remoteAddress string) {
if networkType == "tcp" {
log.Printf("Port forwarding (tcp): %s <- %s", bindAddress, remoteAddress)
Expand All @@ -270,7 +304,3 @@ func (client *EasyConnectClient) ServeForwarding(networkType string, bindAddress
log.Println("Only TCP/UDP forwarding is supported yet. Aborting.")
}
}

func (client *EasyConnectClient) KeepAlive(dnsServer string) {
KeepAlive(dnsServer, client.ipStack, client.clientIp)
}
167 changes: 167 additions & 0 deletions core/dialer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
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"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"log"
"net"
"strconv"
"strings"
)

type Dialer struct {
selfIp []byte

ipStack *stack.Stack
}

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.IsIpv4RuleAvailable() {
if DebugDump {
log.Printf("IPv4 rule 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
}
}
}
}

if useProxy {
addrTarget := tcpip.FullAddress{
NIC: defaultNIC,
Port: uint16(port),
Addr: tcpip.Address(target.IP),
}

bind := tcpip.FullAddress{
NIC: defaultNIC,
Addr: tcpip.Address(dialer.selfIp),
}

if network == "tcp" {
log.Printf("%s -> PROXY", addr)
return gonet.DialTCPWithBind(context.Background(), dialer.ipStack, bind, addrTarget, header.IPv4ProtocolNumber)
} else if network == "udp" {
log.Printf("%s -> PROXY", addr)
return gonet.DialUDP(dialer.ipStack, &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)
}
112 changes: 112 additions & 0 deletions core/dns_resolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package core

import (
"github.com/mythologyli/zju-connect/core/config"
"golang.org/x/net/context"
"log"
"net"
"sync"
"time"
)

type DnsResolve struct {
remoteUDPResolver *net.Resolver
remoteTCPResolver *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.")

if target, err = net.ResolveIPAddr("ip6", host); err != nil {
log.Printf("Resolve IPv6 addr failed using local DNS: " + host + ". Reject connection.")
return ctx, nil, err
} else {
log.Printf("%s -> %s", host, target.IP.String())
return ctx, target.IP, nil
}
} else {
log.Printf("%s -> %s", host, target.IP.String())
return ctx, target.IP, nil
}
}

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 {
useProxy = true
}
}
if !useProxy && config.IsDomainRuleAvailable() {
if _, found := config.GetSingleDomainRule(host); found {
useProxy = true
}
}

ctx = context.WithValue(ctx, "USE_PROXY", useProxy)

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 local 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)
} 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 local 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)
} 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.ResolveWithLocal(ctx, host)
}
}
Loading

0 comments on commit ed52b1d

Please sign in to comment.