-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: use http proxy when socks5 proxy disabled
- Loading branch information
1 parent
f49cb5a
commit ed52b1d
Showing
6 changed files
with
336 additions
and
333 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.