Files
ddns-go/util/http_client_util.go

243 lines
7.0 KiB
Go

package util
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"time"
)
func getLocalAddrFromInterfaceByNetwork(ifaceName, network string) (string, error) {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return "", fmt.Errorf("interface %s not found: %v", ifaceName, err)
}
addrs, err := iface.Addrs()
if err != nil {
return "", fmt.Errorf("get interface %s addresses failed: %v", ifaceName, err)
}
hasGlobalUnicast := false
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || !ipNet.IP.IsGlobalUnicast() {
continue
}
hasGlobalUnicast = true
if isIPMatchedNetwork(ipNet.IP, network) {
return ipNet.IP.String(), nil
}
}
if hasGlobalUnicast && (network == "tcp4" || network == "tcp6") {
return "", fmt.Errorf("interface %s has no usable %s address", ifaceName, network)
}
return "", fmt.Errorf("interface %s has no usable global-unicast address", ifaceName)
}
func isIPMatchedNetwork(ip net.IP, network string) bool {
switch network {
case "tcp4":
return ip.To4() != nil
case "tcp6":
return ip.To16() != nil && ip.To4() == nil
default:
return true
}
}
func localTCPAddr(ip net.IP, network, ifaceName string) *net.TCPAddr {
addr := &net.TCPAddr{IP: ip}
if network == "tcp6" && ifaceName != "" {
addr.Zone = ifaceName
}
return addr
}
var dialer = &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
// newHTTPDialer creates a dialer that preserves the custom DNS resolver set by SetDNS.
func newHTTPDialer(localAddr *net.TCPAddr) *net.Dialer {
return &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
LocalAddr: localAddr,
Resolver: dialer.Resolver,
}
}
var defaultTransport = &http.Transport{
// from http.DefaultTransport
Proxy: http.ProxyFromEnvironment,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
// insecureSkipVerify 全局TLS验证跳过标志
var insecureSkipVerify bool
// CreateHTTPClient Create Default HTTP Client
func CreateHTTPClient() *http.Client {
return &http.Client{
Timeout: 30 * time.Second,
Transport: defaultTransport,
}
}
// GetLocalAddrFromInterface 根据网卡名称获取本地IP地址
func GetLocalAddrFromInterface(ifaceName string) (string, error) {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return "", fmt.Errorf("找不到网卡 %s: %v", ifaceName, err)
}
addrs, err := iface.Addrs()
if err != nil {
return "", fmt.Errorf("获取网卡 %s 地址失败: %v", ifaceName, err)
}
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && ipNet.IP.IsGlobalUnicast() {
return ipNet.IP.String(), nil
}
}
return "", fmt.Errorf("网卡 %s 没有可用的单播地址", ifaceName)
}
// CreateHTTPClientWithInterface 创建绑定指定网卡的HTTP客户端
func CreateHTTPClientWithInterface(ifaceName string) *http.Client {
if ifaceName == "" {
return CreateHTTPClient()
}
localIP, err := GetLocalAddrFromInterface(ifaceName)
if err != nil {
Log("绑定网卡失败, 将使用默认网卡. 网卡: %s, 错误: %v", ifaceName, err)
return CreateHTTPClient()
}
localAddr := &net.TCPAddr{IP: net.ParseIP(localIP)}
boundDialer := newHTTPDialer(localAddr)
setLinuxBindToDevice(boundDialer, ifaceName)
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return boundDialer.DialContext(ctx, network, address)
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if insecureSkipVerify {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
return &http.Client{
Timeout: 30 * time.Second,
Transport: transport,
}
}
// CreateBoundNoProxyHTTPClient 创建无代理且绑定指定网卡的HTTP客户端
func CreateBoundNoProxyHTTPClient(network, ifaceName string) *http.Client {
if ifaceName == "" {
return CreateNoProxyHTTPClient(network)
}
localIP, err := getLocalAddrFromInterfaceByNetwork(ifaceName, network)
if err != nil {
Log("绑定网卡失败, 将使用默认网卡. 网卡: %s, 错误: %v", ifaceName, err)
return CreateNoProxyHTTPClient(network)
}
localAddrIP := net.ParseIP(localIP)
if localAddrIP == nil {
Log("绑定网卡失败, 将使用默认网卡. 网卡: %s, 网络: %s, 错误: 本地IP无效: %s", ifaceName, network, localIP)
return CreateNoProxyHTTPClient(network)
}
localAddr := localTCPAddr(localAddrIP, network, ifaceName)
boundDialer := newHTTPDialer(localAddr)
setLinuxBindToDevice(boundDialer, ifaceName)
transport := &http.Transport{
// no proxy
DisableKeepAlives: true,
DialContext: func(ctx context.Context, _, address string) (net.Conn, error) {
return boundDialer.DialContext(ctx, network, address)
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if insecureSkipVerify {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
return &http.Client{
Timeout: 30 * time.Second,
Transport: transport,
}
}
var noProxyTcp4Transport = &http.Transport{
// no proxy
// DisableKeepAlives
DisableKeepAlives: true,
// tcp4
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp4", address)
},
// from http.DefaultTransport
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
var noProxyTcp6Transport = &http.Transport{
// no proxy
// DisableKeepAlives
DisableKeepAlives: true,
// tcp6
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp6", address)
},
// from http.DefaultTransport
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
// CreateNoProxyHTTPClient Create NoProxy HTTP Client
func CreateNoProxyHTTPClient(network string) *http.Client {
if network == "tcp6" {
return &http.Client{
Timeout: 30 * time.Second,
Transport: noProxyTcp6Transport,
}
}
return &http.Client{
Timeout: 30 * time.Second,
Transport: noProxyTcp4Transport,
}
}
// SetInsecureSkipVerify 将所有 http.Transport 的 InsecureSkipVerify 设置为 true
func SetInsecureSkipVerify() {
insecureSkipVerify = true
transports := []*http.Transport{defaultTransport, noProxyTcp4Transport, noProxyTcp6Transport}
for _, transport := range transports {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
}