Files
ProxyClient/proxyclient.go
2020-01-21 19:25:41 +08:00

223 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package proxyclient
import (
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
)
// Conn 用来表示连接
type Conn interface {
net.Conn
}
// TCPConn 用来表示 TCP 连接
// 提供 net.TcpConn 结构的全部方法
// 但是部分方法由于代理协议的限制可能不能获得正确的结果。例如LocalAddr 、RemoteAddr 方法不被很多代理协议支持。
type TCPConn interface {
Conn
/*
SetLinger设定当连接中仍有数据等待发送或接受时的Close方法的行为。
如果sec < 0默认Close方法立即返回操作系统停止后台数据发送如果 sec == 0Close立刻返回操作系统丢弃任何未发送或未接收的数据如果sec > 0Close方法阻塞最多sec秒等待数据发送或者接收在一些操作系统中在超时后任何未发送的数据会被丢弃。
*/
SetLinger(sec int) error
// SetNoDelay设定操作系统是否应该延迟数据包传递以便发送更少的数据包Nagle's算法。默认为真即数据应该在Write方法后立刻发送。
SetNoDelay(noDelay bool) error
//SetReadBuffer设置该连接的系统接收缓冲
SetReadBuffer(bytes int) error
//SetWriteBuffer设置该连接的系统发送缓冲
SetWriteBuffer(bytes int) error
}
// ProxyTCPConn 用来表示通过代理访问的TCP连接
type ProxyTCPConn interface {
TCPConn
ProxyClient() ProxyClient // 获得所属的代理
}
// UDPConn 表示 UDP 连接
type UDPConn interface {
Conn
}
// ProxyUDPConn 用来表示通过代理访问的TCP连接
type ProxyUDPConn interface {
UDPConn
ProxyClient() ProxyClient // 获得所属的代理
}
// ProxyClient 仿 net 库接口的代理客户端
// 支持级联代理功能,可以通过 SetUpProxy 设置上级代理。
type ProxyClient interface {
// 返回本代理的上层级联代理
UpProxy() ProxyClient
// 设置本代理的上层代理
SetUpProxy(upProxy ProxyClient) error
// Dial 在网络network上连接地址address并返回一个Conn接口。可用的网络类型有
// "tcp"、"tcp4"、"tcp6"、"udp"、"udp4"、"udp6"
// 对TCP和UDP网络地址格式是host:port或[host]:port参见函数JoinHostPort和SplitHostPort。
// 如果代理服务器支持远端DNS解析那么会使用远端DNS解析。
Dial(network, address string) (net.Conn, error)
DialTimeout(network, address string, timeout time.Duration) (net.Conn, error)
// DialTCP在网络协议net上连接本地地址laddr和远端地址raddr。net必须是"tcp"、"tcp4"、"tcp6"如果laddr不是nil将使用它作为本地地址否则自动选择一个本地地址。
// 由于 net.TCPAddr 内部保存的是IP地址及端口所以使用本函数无法使用远端DNS解析要想使用远端DNS解析请使用 Dial 或 DialTCPSAddr 函数。
DialTCP(net string, laddr, raddr *net.TCPAddr) (net.Conn, error)
// DialTCPSAddr 同 DialTCP 函数主要区别是如果代理支持远端dns解析那么会使用远端dns解析。
DialTCPSAddr(network string, raddr string) (ProxyTCPConn, error)
// DialTCPSAddrTimeout 同 DialTCPSAddr 函数,增加了超时功能
DialTCPSAddrTimeout(network string, raddr string, timeour time.Duration) (ProxyTCPConn, error)
//ListenTCP在本地TCP地址laddr上声明并返回一个*TCPListenernet参数必须是"tcp"、"tcp4"、"tcp6"如果laddr的端口字段为0函数将选择一个当前可用的端口可以用Listener的Addr方法获得该端口。
//ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error)
//DialTCP在网络协议net上连接本地地址laddr和远端地址raddr。net必须是"udp"、"udp4"、"udp6"如果laddr不是nil将使用它作为本地地址否则自动选择一个本地地址。
DialUDP(net string, laddr, raddr *net.UDPAddr) (net.Conn, error)
// 获得 Proxy 代理地址的 Query
// 为了大小写兼容key全部是转换成小写的。
GetProxyAddrQuery() map[string][]string
}
// NewProxyClient 用来创建代理客户端
//
// 参数格式:允许使用 ?参数名1=参数值1&参数名2=参数值2指定参数
// 例如https://123.123.123.123:8088?insecureskipverify=true
// 全体协议可选参数: upProxy=http://145.2.1.3:8080 用于指定代理的上层代理即代理嵌套。默认值direct://0.0.0.0:0000
//
// http 代理 http://123.123.123.123:8088
// 可选功能: 用户认证功能。格式http://user:password@123.123.123:8080
// 可选参数standardheader=false true表示 CONNNET 请求包含标准的 Accept、Accept-Encoding、Accept-Language、User-Agent等头。默认值false
//
// https 代理 https://123.123.123.123:8088
// 可选功能: 用户认证功能,同 http 代理。
// 可选参数standardheader=false 同上 http 代理
// 可选参数insecureskipverify=false true表示跳过 https 证书验证。默认false。
// 可选参数domain=域名 指定https验证证书时使用的域名默认为 host:port
//
// socks4 代理 socks4://123.123.123.123:5050
// 注意socks4 协议不支持远端 dns 解析
//
// socks4a 代理 socks4a://123.123.123.123:5050
//
// socks5 代理 socks5://123.123.123.123:5050
// 可选功能:用户认证功能。支持无认证、用户名密码认证,格式同 http 代理。
//
// ss 代理 ss://method:passowd@123.123.123:5050
//
// 直连 direct://0.0.0.0:0000
// 可选参数: LocalAddr=0.0.0.0:0 表示tcp连接绑定的本地ip及端口默认值 0.0.0.0:0。
// 可选参数: SplitHttp=false true 表示拆分 http 请求(分多个tcp包发送),可以解决简单的运营商 http 劫持。默认值false 。
// 原理是:当发现目标地址为 80 端口,发送的内容包含 GET、POST、HTTP、HOST 等关键字时,会将关键字拆分到两个包在发送出去。
// 注意: Web 防火墙类软件、设备可能会重组 HTTP 包,造成拆分无效。目前已知 ESET Smart Security 会造成这个功能无效,即使暂停防火墙也一样无效。
// G|ET /pa|th H|TTTP/1.0
// HO|ST:www.aa|dd.com
// 可选参数: sleep=0 建立连接后延迟多少毫秒发送数据,配合 ttl 反劫持系统时建议设置为10置50。默认值 0 .
func NewProxyClient(addr string) (ProxyClient, error) {
u, err := url.Parse(addr)
if err != nil {
return nil, errors.New("addr 错误的格式")
}
// 将 query key 转换成为小写
_query := u.Query()
query := make(map[string][]string, len(_query))
for k, v := range _query {
query[strings.ToLower(k)] = v
}
queryGet := func(key string) string {
if query == nil {
return ""
}
v, ok := query[key]
if !ok || len(v) == 0 {
return ""
}
return v[0]
}
scheme := strings.ToLower(strings.TrimSpace(u.Scheme))
var upProxy ProxyClient
if up, ok := query["upproxy"]; ok == true {
if upProxy, err = NewProxyClient(up[0]); err != nil {
return nil, fmt.Errorf("upProxy 创建失败:%v", err)
}
}
switch scheme {
case "direct":
localAddr := queryGet("localaddr")
if localAddr == "" {
localAddr = ":0"
}
splitHttp := false
if strings.ToLower(queryGet("splithttp")) == "true" {
splitHttp = true
}
sleep := 0 * time.Millisecond
if queryGet("sleep") != "" {
if s, err := strconv.Atoi(queryGet("sleep")); err != nil {
return nil, fmt.Errorf("sleep 参数错误:%v", err)
} else {
sleep = time.Duration(s) * time.Millisecond
}
}
return newDriectProxyClient(localAddr, splitHttp, sleep, query)
case "socks4", "socks4a", "socks5":
username := ""
password := ""
if u.User != nil {
username = u.User.Username()
password, _ = u.User.Password()
}
return newSocksProxyClient(scheme, u.Host, username, password, upProxy, query)
case "http", "https":
auth := ""
if u.User != nil {
password,_:=u.User.Password()
// 必须自己重新生成,因为 User.String() 会将密码内的 ! 等特殊符号编码,造成鉴定失败
auth =fmt.Sprintf("%v:%v", u.User.Username(),password)
}
standardHeader := false
if strings.ToLower(queryGet("standardheader")) == "true" {
standardHeader = true
}
insecureSkipVerify := false
if strings.ToLower(queryGet("insecureskipverify")) == "true" {
insecureSkipVerify = true
}
domain := queryGet("domain")
return newHTTPProxyClient(scheme, u.Host, domain, auth, insecureSkipVerify, standardHeader, upProxy, query)
case "ss":
password, ok := u.User.Password()
if ok == false {
return nil, fmt.Errorf("ss 代理 method, password 格式错误。")
}
return newSsProxyClient(u.Host, u.User.Username(), password, upProxy, query)
default:
return nil, fmt.Errorf("未识别的代理类型:%v", scheme)
}
}