diff --git a/README.md b/README.md index 031eb1b..fc8e813 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ gost - GO Simple Tunnel ------ * 可同时监听多端口 * 可设置转发代理,支持多级转发(代理链) -* 支持标准HTTP/HTTPS/SOCKS5代理协议 +* 支持标准HTTP/HTTPS/SOCKS4(A)/SOCKS5代理协议 * SOCKS5代理支持TLS协商加密 * Tunnel UDP over TCP * 支持Shadowsocks协议 (OTA: 2.2+,UDP: 2.4+) @@ -36,7 +36,7 @@ Google讨论组: https://groups.google.com/d/forum/go-gost ``` scheme分为两部分: protocol+transport -protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic, kcp), 二者可以任意组合,或单独使用: +protocol: 代理协议类型(http, socks4(a), socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic, kcp, pht), 二者可以任意组合,或单独使用: > http - HTTP代理: http://:8080 @@ -44,11 +44,13 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > http2 - HTTP2代理并向下兼容HTTPS代理: http2://:443 -> socks - 标准SOCKS5代理(支持tls协商加密): socks://:1080 +> socks4(a) - 标准SOCKS4(A)代理: socks4://:1080或socks4a://:1080 + +> socks - 标准SOCKS5代理(支持TLS协商加密): socks://:1080 > socks+wss - SOCKS5代理,使用websocket传输数据: socks+wss://:1080 -> tls - HTTPS/SOCKS5代理,使用tls传输数据: tls://:443 +> tls - HTTPS/SOCKS5代理,使用TLS传输数据: tls://:443 > ss - Shadowsocks代理,ss://chacha20:123456@:8338 @@ -56,7 +58,9 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > quic - QUIC代理,quic://:6121 -> kcp - KCP代理,kcp://:8388或kcp://aes:123456@:8388 +> kcp - KCP通道,kcp://:8388或kcp://aes:123456@:8388 + +> pht - 普通HTTP通道,pht://:8080 > redirect - 透明代理,redirect://:12345 diff --git a/README_en.md b/README_en.md index b7c0e7f..7a37a3f 100644 --- a/README_en.md +++ b/README_en.md @@ -7,7 +7,7 @@ Features ------ * Listening on multiple ports * Multi-level forward proxy - proxy chain -* Standard HTTP/HTTPS/SOCKS5 proxy protocols support +* Standard HTTP/HTTPS/SOCKS4(A)/SOCKS5 proxy protocols support * TLS encryption via negotiation support for SOCKS5 proxy * Tunnel UDP over TCP * Shadowsocks protocol support (OTA: 2.2+, UDP: 2.4+) @@ -35,8 +35,8 @@ Effective for the -L and -F parameters ``` scheme can be divided into two parts: protocol+transport -protocol: proxy protocol types (http, socks5, shadowsocks), -transport: data transmission mode (ws, wss, tls, http2, quic, kcp), may be used in any combination or individually: +protocol: proxy protocol types (http, socks4(a), socks5, shadowsocks), +transport: data transmission mode (ws, wss, tls, http2, quic, kcp, pht), may be used in any combination or individually: > http - standard HTTP proxy: http://:8080 @@ -44,11 +44,13 @@ transport: data transmission mode (ws, wss, tls, http2, quic, kcp), may be used > http2 - HTTP2 proxy and backwards-compatible with HTTPS proxy: http2://:443 +> socks4(a) - standard SOCKS4(A) proxy: socks4://:1080 or socks4a://:1080 + > socks - standard SOCKS5 proxy: socks://:1080 > socks+wss - SOCKS5 over websocket: socks+wss://:1080 -> tls - HTTPS/SOCKS5 over tls: tls://:443 +> tls - HTTPS/SOCKS5 over TLS: tls://:443 > ss - standard shadowsocks proxy, ss://chacha20:123456@:8338 @@ -58,6 +60,8 @@ transport: data transmission mode (ws, wss, tls, http2, quic, kcp), may be used > kcp - standard KCP tunnel,kcp://:8388 or kcp://aes:123456@:8388 +> pht - plain HTTP tunnel, pht://:8080 + > redirect - transparent proxy,redirect://:12345 #### Port forwarding diff --git a/chain.go b/chain.go index ae57704..de8ad23 100644 --- a/chain.go +++ b/chain.go @@ -7,6 +7,7 @@ import ( "errors" "github.com/ginuerzh/pht" "github.com/golang/glog" + "github.com/lucas-clemente/quic-go/h2quic" "golang.org/x/net/http2" "io" "net" @@ -30,7 +31,8 @@ type ProxyChain struct { kcpConfig *KCPConfig kcpSession *KCPSession kcpMutex sync.Mutex - phttpClient *pht.Client + phtClient *pht.Client + quicClient *http.Client } func NewProxyChain(nodes ...ProxyNode) *ProxyChain { @@ -121,9 +123,21 @@ func (c *ProxyChain) Init() { return } + if c.nodes[0].Transport == "quic" { + glog.V(LINFO).Infoln("QUIC is enabled") + c.quicClient = &http.Client{ + Transport: &h2quic.QuicRoundTripper{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: c.nodes[0].insecureSkipVerify(), + ServerName: c.nodes[0].serverName, + }, + }, + } + } + if c.nodes[0].Transport == "pht" { glog.V(LINFO).Infoln("Pure HTTP mode is enabled") - c.phttpClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key")) + c.phtClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key")) } } @@ -249,6 +263,12 @@ func (c *ProxyChain) dialWithNodes(withHttp2 bool, addr string, nodes ...ProxyNo return c.http2Connect(addr) } } + + if nodes[0].Transport == "quic" { + glog.V(LINFO).Infoln("Dial with QUIC") + return c.quicConnect(addr) + } + pc, err := c.travelNodes(withHttp2, nodes...) if err != nil { return @@ -277,7 +297,7 @@ func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *Prox } else if node.Transport == "kcp" { cc, err = c.getKCPConn() } else if node.Transport == "pht" { - cc, err = c.phttpClient.Dial() + cc, err = c.phtClient.Dial() } else { cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout) } @@ -388,3 +408,63 @@ func (c *ProxyChain) http2Connect(addr string) (net.Conn, error) { } return c.getHttp2Conn(header) } + +func (c *ProxyChain) quicConnect(addr string) (net.Conn, error) { + quicNode := c.nodes[0] + header := make(http.Header) + header.Set("Gost-Target", addr) // Flag header to indicate the address that server connected to + if quicNode.Users != nil { + header.Set("Proxy-Authorization", + "Basic "+base64.StdEncoding.EncodeToString([]byte(quicNode.Users[0].String()))) + } + return c.getQuicConn(header) +} + +func (c *ProxyChain) getQuicConn(header http.Header) (net.Conn, error) { + quicNode := c.nodes[0] + pr, pw := io.Pipe() + + if header == nil { + header = make(http.Header) + } + + /* + req := http.Request{ + Method: http.MethodGet, + URL: &url.URL{Scheme: "https", Host: quicNode.Addr}, + Header: header, + Proto: "HTTP/2.0", + ProtoMajor: 2, + ProtoMinor: 0, + Body: pr, + Host: quicNode.Addr, + ContentLength: -1, + } + */ + req, err := http.NewRequest(http.MethodPost, "https://"+quicNode.Addr, pr) + if err != nil { + return nil, err + } + req.ContentLength = -1 + req.Header = header + + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } + resp, err := c.quicClient.Do(req) + if err != nil { + return nil, err + } + if glog.V(LDEBUG) { + dump, _ := httputil.DumpResponse(resp, false) + glog.Infoln(string(dump)) + } + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, errors.New(resp.Status) + } + conn := &http2Conn{r: resp.Body, w: pw} + conn.remoteAddr, _ = net.ResolveUDPAddr("udp", quicNode.Addr) + return conn, nil +} diff --git a/cmd/gost/vendor/github.com/ginuerzh/gosocks4/LICENSE b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/LICENSE new file mode 100644 index 0000000..de5e3f4 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 ginuerzh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER 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. diff --git a/cmd/gost/vendor/github.com/ginuerzh/gosocks4/README.md b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/README.md new file mode 100644 index 0000000..ddb0528 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/README.md @@ -0,0 +1,2 @@ +# gosocks4 +golang and SOCKS4(a) diff --git a/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4.go b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4.go new file mode 100644 index 0000000..52b32e4 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4.go @@ -0,0 +1,252 @@ +// SOCKS Protocol Version 4(a) +// https://www.openssh.com/txt/socks4.protocol +// https://www.openssh.com/txt/socks4a.protocol +package gosocks4 + +import ( + "bufio" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "strconv" +) + +const ( + Ver4 = 4 +) + +const ( + CmdConnect uint8 = 1 + CmdBind = 2 +) + +const ( + AddrIPv4 = 0 + AddrDomain = 1 +) + +const ( + Granted = 90 + Failed = 91 + Rejected = 92 + RejectedUserid = 93 +) + +var ( + ErrBadVersion = errors.New("Bad version") + ErrBadFormat = errors.New("Bad format") + ErrBadAddrType = errors.New("Bad address type") + ErrShortDataLength = errors.New("Short data length") + ErrBadCmd = errors.New("Bad Command") +) + +type Addr struct { + Type int + Host string + Port uint16 +} + +func (addr *Addr) Decode(b []byte) error { + if len(b) < 6 { + return ErrShortDataLength + } + + addr.Port = binary.BigEndian.Uint16(b[0:2]) + addr.Host = net.IP(b[2 : 2+net.IPv4len]).String() + + if b[2]|b[3]|b[4] == 0 { + addr.Type = AddrDomain + } + + return nil +} + +func (addr *Addr) Encode(b []byte) error { + if len(b) < 6 { + return ErrShortDataLength + } + + binary.BigEndian.PutUint16(b[0:2], addr.Port) + + switch addr.Type { + case AddrIPv4: + ip4 := net.ParseIP(addr.Host).To4() + if ip4 == nil { + return ErrBadAddrType + } + copy(b[2:], ip4) + case AddrDomain: + ip4 := net.IPv4(0, 0, 0, 1) + copy(b[2:], ip4) + default: + return ErrBadAddrType + } + + return nil +} + +func (addr *Addr) String() string { + return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port))) +} + +/* + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + 1 1 2 4 variable 1 +*/ +type Request struct { + Cmd uint8 + Addr *Addr + Userid []byte +} + +func NewRequest(cmd uint8, addr *Addr, userid []byte) *Request { + return &Request{ + Cmd: cmd, + Addr: addr, + Userid: userid, + } +} + +func ReadRequest(r io.Reader) (*Request, error) { + br := bufio.NewReader(r) + b, err := br.Peek(8) + if err != nil { + return nil, err + } + + if b[0] != Ver4 { + return nil, ErrBadVersion + } + + request := &Request{ + Cmd: b[1], + } + + addr := &Addr{} + if err := addr.Decode(b[2:8]); err != nil { + return nil, err + } + request.Addr = addr + + if _, err := br.Discard(8); err != nil { + return nil, err + } + b, err = br.ReadBytes(0) + if err != nil { + return nil, err + } + request.Userid = b[:len(b)-1] + + if request.Addr.Type == AddrDomain { + b, err = br.ReadBytes(0) + if err != nil { + return nil, err + } + request.Addr.Host = string(b[:len(b)-1]) + } + + return request, nil +} + +func (r *Request) Write(w io.Writer) (err error) { + bw := bufio.NewWriter(w) + bw.Write([]byte{Ver4, r.Cmd}) + + if r.Addr == nil { + return ErrBadAddrType + } + + var b [6]byte + if err = r.Addr.Encode(b[:]); err != nil { + return + } + bw.Write(b[:]) + + if len(r.Userid) > 0 { + bw.Write(r.Userid) + } + bw.WriteByte(0) + + if r.Addr.Type == AddrDomain { + bw.WriteString(r.Addr.Host) + bw.WriteByte(0) + } + + return bw.Flush() +} + +func (r *Request) String() string { + addr := r.Addr + if addr == nil { + addr = &Addr{} + } + return fmt.Sprintf("%d %d %s", Ver4, r.Cmd, addr.String()) +} + +/* + +----+----+----+----+----+----+----+----+ + | VN | CD | DSTPORT | DSTIP | + +----+----+----+----+----+----+----+----+ + 1 1 2 4 +*/ +type Reply struct { + Code uint8 + Addr *Addr +} + +func NewReply(code uint8, addr *Addr) *Reply { + return &Reply{ + Code: code, + Addr: addr, + } +} + +func ReadReply(r io.Reader) (*Reply, error) { + var b [8]byte + + _, err := io.ReadFull(r, b[:]) + if err != nil { + return nil, err + } + + if b[0] != 0 { + return nil, ErrBadVersion + } + + reply := &Reply{ + Code: b[1], + } + + reply.Addr = &Addr{} + if err := reply.Addr.Decode(b[2:]); err != nil { + return nil, err + } + + return reply, nil +} + +func (r *Reply) Write(w io.Writer) (err error) { + var b [8]byte + + b[1] = r.Code + if r.Addr != nil { + if err = r.Addr.Encode(b[2:]); err != nil { + return + } + } + + _, err = w.Write(b[:]) + return +} + +func (r *Reply) String() string { + addr := r.Addr + if addr == nil { + addr = &Addr{} + } + return fmt.Sprintf("0 %d %s", r.Code, addr.String()) +} diff --git a/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4.protocol.txt b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4.protocol.txt new file mode 100644 index 0000000..6bc0837 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4.protocol.txt @@ -0,0 +1,152 @@ + SOCKS: A protocol for TCP proxy across firewalls + + Ying-Da Lee + Principal Member Technical Staff + NEC Systems Laboratory, CSTC + ylee@syl.dl.nec.com + +SOCKS was originally developed by David Koblas and subsequently modified +and extended by me to its current running version -- version 4. It is a +protocol that relays TCP sessions at a firewall host to allow application +users transparent access across the firewall. Because the protocol is +independent of application protocols, it can be (and has been) used for +many different services, such as telnet, ftp, finger, whois, gopher, WWW, +etc. Access control can be applied at the beginning of each TCP session; +thereafter the server simply relays the data between the client and the +application server, incurring minimum processing overhead. Since SOCKS +never has to know anything about the application protocol, it should also +be easy for it to accommodate applications which use encryption to protect +their traffic from nosey snoopers. + +Two operations are defined: CONNECT and BIND. + +1) CONNECT + +The client connects to the SOCKS server and sends a CONNECT request when +it wants to establish a connection to an application server. The client +includes in the request packet the IP address and the port number of the +destination host, and userid, in the following format. + + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + # of bytes: 1 1 2 4 variable 1 + +VN is the SOCKS protocol version number and should be 4. CD is the +SOCKS command code and should be 1 for CONNECT request. NULL is a byte +of all zero bits. + +The SOCKS server checks to see whether such a request should be granted +based on any combination of source IP address, destination IP address, +destination port number, the userid, and information it may obtain by +consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS +server makes a connection to the specified port of the destination host. +A reply packet is sent to the client when this connection is established, +or when the request is rejected or the operation fails. + + +----+----+----+----+----+----+----+----+ + | VN | CD | DSTPORT | DSTIP | + +----+----+----+----+----+----+----+----+ + # of bytes: 1 1 2 4 + +VN is the version of the reply code and should be 0. CD is the result +code with one of the following values: + + 90: request granted + 91: request rejected or failed + 92: request rejected becasue SOCKS server cannot connect to + identd on the client + 93: request rejected because the client program and identd + report different user-ids + +The remaining fields are ignored. + +The SOCKS server closes its connection immediately after notifying +the client of a failed or rejected request. For a successful request, +the SOCKS server gets ready to relay traffic on both directions. This +enables the client to do I/O on its connection as if it were directly +connected to the application server. + + +2) BIND + +The client connects to the SOCKS server and sends a BIND request when +it wants to prepare for an inbound connection from an application server. +This should only happen after a primary connection to the application +server has been established with a CONNECT. Typically, this is part of +the sequence of actions: + +-bind(): obtain a socket +-getsockname(): get the IP address and port number of the socket +-listen(): ready to accept call from the application server +-use the primary connection to inform the application server of + the IP address and the port number that it should connect to. +-accept(): accept a connection from the application server + +The purpose of SOCKS BIND operation is to support such a sequence +but using a socket on the SOCKS server rather than on the client. + +The client includes in the request packet the IP address of the +application server, the destination port used in the primary connection, +and the userid. + + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + # of bytes: 1 1 2 4 variable 1 + +VN is again 4 for the SOCKS protocol version number. CD must be 2 to +indicate BIND request. + +The SOCKS server uses the client information to decide whether the +request is to be granted. The reply it sends back to the client has +the same format as the reply for CONNECT request, i.e., + + +----+----+----+----+----+----+----+----+ + | VN | CD | DSTPORT | DSTIP | + +----+----+----+----+----+----+----+----+ + # of bytes: 1 1 2 4 + +VN is the version of the reply code and should be 0. CD is the result +code with one of the following values: + + 90: request granted + 91: request rejected or failed + 92: request rejected becasue SOCKS server cannot connect to + identd on the client + 93: request rejected because the client program and identd + report different user-ids. + +However, for a granted request (CD is 90), the DSTPORT and DSTIP fields +are meaningful. In that case, the SOCKS server obtains a socket to wait +for an incoming connection and sends the port number and the IP address +of that socket to the client in DSTPORT and DSTIP, respectively. If the +DSTIP in the reply is 0 (the value of constant INADDR_ANY), then the +client should replace it by the IP address of the SOCKS server to which +the cleint is connected. (This happens if the SOCKS server is not a +multi-homed host.) In the typical scenario, these two numbers are +made available to the application client prgram via the result of the +subsequent getsockname() call. The application protocol must provide a +way for these two pieces of information to be sent from the client to +the application server so that it can initiate the connection, which +connects it to the SOCKS server rather than directly to the application +client as it normally would. + +The SOCKS server sends a second reply packet to the client when the +anticipated connection from the application server is established. +The SOCKS server checks the IP address of the originating host against +the value of DSTIP specified in the client's BIND request. If a mismatch +is found, the CD field in the second reply is set to 91 and the SOCKS +server closes both connections. If the two match, CD in the second +reply is set to 90 and the SOCKS server gets ready to relay the traffic +on its two connections. From then on the client does I/O on its connection +to the SOCKS server as if it were directly connected to the application +server. + + + +For both CONNECT and BIND operations, the server sets a time limit +(2 minutes in current CSTC implementation) for the establishment of its +connection with the application server. If the connection is still not +establiched when the time limit expires, the server closes its connection +to the client and gives up. diff --git a/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4a.protocol.txt b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4a.protocol.txt new file mode 100644 index 0000000..f944218 --- /dev/null +++ b/cmd/gost/vendor/github.com/ginuerzh/gosocks4/socks4a.protocol.txt @@ -0,0 +1,39 @@ + SOCKS 4A: A Simple Extension to SOCKS 4 Protocol + + Ying-Da Lee + yingda@best.com or yingda@esd.sgi.com + +Please read SOCKS4.protocol first for an description of the version 4 +protocol. This extension is intended to allow the use of SOCKS on hosts +which are not capable of resolving all domain names. + +In version 4, the client sends the following packet to the SOCKS server +to request a CONNECT or a BIND operation: + + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + # of bytes: 1 1 2 4 variable 1 + +VN is the SOCKS protocol version number and should be 4. CD is the +SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL +is a byte of all zero bits. + +For version 4A, if the client cannot resolve the destination host's +domain name to find its IP address, it should set the first three bytes +of DSTIP to NULL and the last byte to a non-zero value. (This corresponds +to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The +Internet Assigned Numbers Authority -- such an address is inadmissible +as a destination IP address and thus should never occur if the client +can resolve the domain name.) Following the NULL byte terminating +USERID, the client must sends the destination domain name and termiantes +it with another NULL byte. This is used for both CONNECT and BIND requests. + +A server using protocol 4A must check the DSTIP in the request packet. +If it represent address 0.0.0.x with nonzero x, the server must read +in the domain name that the client sends in the packet. The server +should resolve the domain name and make connection to the destination +host if it can. + +SOCKSified sockd may pass domain names that it cannot resolve to +the next-hop SOCKS server. diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/README.md b/cmd/gost/vendor/github.com/ginuerzh/gost/README.md index 031eb1b..fc8e813 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/README.md +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/README.md @@ -9,7 +9,7 @@ gost - GO Simple Tunnel ------ * 可同时监听多端口 * 可设置转发代理,支持多级转发(代理链) -* 支持标准HTTP/HTTPS/SOCKS5代理协议 +* 支持标准HTTP/HTTPS/SOCKS4(A)/SOCKS5代理协议 * SOCKS5代理支持TLS协商加密 * Tunnel UDP over TCP * 支持Shadowsocks协议 (OTA: 2.2+,UDP: 2.4+) @@ -36,7 +36,7 @@ Google讨论组: https://groups.google.com/d/forum/go-gost ``` scheme分为两部分: protocol+transport -protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic, kcp), 二者可以任意组合,或单独使用: +protocol: 代理协议类型(http, socks4(a), socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic, kcp, pht), 二者可以任意组合,或单独使用: > http - HTTP代理: http://:8080 @@ -44,11 +44,13 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > http2 - HTTP2代理并向下兼容HTTPS代理: http2://:443 -> socks - 标准SOCKS5代理(支持tls协商加密): socks://:1080 +> socks4(a) - 标准SOCKS4(A)代理: socks4://:1080或socks4a://:1080 + +> socks - 标准SOCKS5代理(支持TLS协商加密): socks://:1080 > socks+wss - SOCKS5代理,使用websocket传输数据: socks+wss://:1080 -> tls - HTTPS/SOCKS5代理,使用tls传输数据: tls://:443 +> tls - HTTPS/SOCKS5代理,使用TLS传输数据: tls://:443 > ss - Shadowsocks代理,ss://chacha20:123456@:8338 @@ -56,7 +58,9 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > quic - QUIC代理,quic://:6121 -> kcp - KCP代理,kcp://:8388或kcp://aes:123456@:8388 +> kcp - KCP通道,kcp://:8388或kcp://aes:123456@:8388 + +> pht - 普通HTTP通道,pht://:8080 > redirect - 透明代理,redirect://:12345 diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/README_en.md b/cmd/gost/vendor/github.com/ginuerzh/gost/README_en.md index b7c0e7f..7a37a3f 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/README_en.md +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/README_en.md @@ -7,7 +7,7 @@ Features ------ * Listening on multiple ports * Multi-level forward proxy - proxy chain -* Standard HTTP/HTTPS/SOCKS5 proxy protocols support +* Standard HTTP/HTTPS/SOCKS4(A)/SOCKS5 proxy protocols support * TLS encryption via negotiation support for SOCKS5 proxy * Tunnel UDP over TCP * Shadowsocks protocol support (OTA: 2.2+, UDP: 2.4+) @@ -35,8 +35,8 @@ Effective for the -L and -F parameters ``` scheme can be divided into two parts: protocol+transport -protocol: proxy protocol types (http, socks5, shadowsocks), -transport: data transmission mode (ws, wss, tls, http2, quic, kcp), may be used in any combination or individually: +protocol: proxy protocol types (http, socks4(a), socks5, shadowsocks), +transport: data transmission mode (ws, wss, tls, http2, quic, kcp, pht), may be used in any combination or individually: > http - standard HTTP proxy: http://:8080 @@ -44,11 +44,13 @@ transport: data transmission mode (ws, wss, tls, http2, quic, kcp), may be used > http2 - HTTP2 proxy and backwards-compatible with HTTPS proxy: http2://:443 +> socks4(a) - standard SOCKS4(A) proxy: socks4://:1080 or socks4a://:1080 + > socks - standard SOCKS5 proxy: socks://:1080 > socks+wss - SOCKS5 over websocket: socks+wss://:1080 -> tls - HTTPS/SOCKS5 over tls: tls://:443 +> tls - HTTPS/SOCKS5 over TLS: tls://:443 > ss - standard shadowsocks proxy, ss://chacha20:123456@:8338 @@ -58,6 +60,8 @@ transport: data transmission mode (ws, wss, tls, http2, quic, kcp), may be used > kcp - standard KCP tunnel,kcp://:8388 or kcp://aes:123456@:8388 +> pht - plain HTTP tunnel, pht://:8080 + > redirect - transparent proxy,redirect://:12345 #### Port forwarding diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go b/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go index ae57704..de8ad23 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/chain.go @@ -7,6 +7,7 @@ import ( "errors" "github.com/ginuerzh/pht" "github.com/golang/glog" + "github.com/lucas-clemente/quic-go/h2quic" "golang.org/x/net/http2" "io" "net" @@ -30,7 +31,8 @@ type ProxyChain struct { kcpConfig *KCPConfig kcpSession *KCPSession kcpMutex sync.Mutex - phttpClient *pht.Client + phtClient *pht.Client + quicClient *http.Client } func NewProxyChain(nodes ...ProxyNode) *ProxyChain { @@ -121,9 +123,21 @@ func (c *ProxyChain) Init() { return } + if c.nodes[0].Transport == "quic" { + glog.V(LINFO).Infoln("QUIC is enabled") + c.quicClient = &http.Client{ + Transport: &h2quic.QuicRoundTripper{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: c.nodes[0].insecureSkipVerify(), + ServerName: c.nodes[0].serverName, + }, + }, + } + } + if c.nodes[0].Transport == "pht" { glog.V(LINFO).Infoln("Pure HTTP mode is enabled") - c.phttpClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key")) + c.phtClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key")) } } @@ -249,6 +263,12 @@ func (c *ProxyChain) dialWithNodes(withHttp2 bool, addr string, nodes ...ProxyNo return c.http2Connect(addr) } } + + if nodes[0].Transport == "quic" { + glog.V(LINFO).Infoln("Dial with QUIC") + return c.quicConnect(addr) + } + pc, err := c.travelNodes(withHttp2, nodes...) if err != nil { return @@ -277,7 +297,7 @@ func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *Prox } else if node.Transport == "kcp" { cc, err = c.getKCPConn() } else if node.Transport == "pht" { - cc, err = c.phttpClient.Dial() + cc, err = c.phtClient.Dial() } else { cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout) } @@ -388,3 +408,63 @@ func (c *ProxyChain) http2Connect(addr string) (net.Conn, error) { } return c.getHttp2Conn(header) } + +func (c *ProxyChain) quicConnect(addr string) (net.Conn, error) { + quicNode := c.nodes[0] + header := make(http.Header) + header.Set("Gost-Target", addr) // Flag header to indicate the address that server connected to + if quicNode.Users != nil { + header.Set("Proxy-Authorization", + "Basic "+base64.StdEncoding.EncodeToString([]byte(quicNode.Users[0].String()))) + } + return c.getQuicConn(header) +} + +func (c *ProxyChain) getQuicConn(header http.Header) (net.Conn, error) { + quicNode := c.nodes[0] + pr, pw := io.Pipe() + + if header == nil { + header = make(http.Header) + } + + /* + req := http.Request{ + Method: http.MethodGet, + URL: &url.URL{Scheme: "https", Host: quicNode.Addr}, + Header: header, + Proto: "HTTP/2.0", + ProtoMajor: 2, + ProtoMinor: 0, + Body: pr, + Host: quicNode.Addr, + ContentLength: -1, + } + */ + req, err := http.NewRequest(http.MethodPost, "https://"+quicNode.Addr, pr) + if err != nil { + return nil, err + } + req.ContentLength = -1 + req.Header = header + + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } + resp, err := c.quicClient.Do(req) + if err != nil { + return nil, err + } + if glog.V(LDEBUG) { + dump, _ := httputil.DumpResponse(resp, false) + glog.Infoln(string(dump)) + } + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, errors.New(resp.Status) + } + conn := &http2Conn{r: resp.Body, w: pw} + conn.remoteAddr, _ = net.ResolveUDPAddr("udp", quicNode.Addr) + return conn, nil +} diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/conn.go b/cmd/gost/vendor/github.com/ginuerzh/gost/conn.go index a46a285..3a6259a 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/conn.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/conn.go @@ -5,6 +5,8 @@ import ( "crypto/tls" "encoding/base64" "errors" + "fmt" + "github.com/ginuerzh/gosocks4" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" @@ -179,6 +181,39 @@ func (c *ProxyConn) Connect(addr string) error { if reply.Rep != gosocks5.Succeeded { return errors.New("Service unavailable") } + case "socks4", "socks4a": + atype := gosocks4.AddrDomain + host, port, err := net.SplitHostPort(addr) + if err != nil { + return err + } + p, _ := strconv.Atoi(port) + + if c.Node.Protocol == "socks4" { + taddr, err := net.ResolveTCPAddr("tcp4", addr) + if err != nil { + return err + } + host = taddr.IP.String() + p = taddr.Port + atype = gosocks4.AddrIPv4 + } + req := gosocks4.NewRequest(gosocks4.CmdConnect, + &gosocks4.Addr{Type: atype, Host: host, Port: uint16(p)}, nil) + if err := req.Write(c); err != nil { + return err + } + glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, req) + + reply, err := gosocks4.ReadReply(c) + if err != nil { + return err + } + glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, reply) + + if reply.Code != gosocks4.Granted { + return errors.New(fmt.Sprintf("%s: code=%d", c.Node.Protocol, reply.Code)) + } case "http": fallthrough default: diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/node.go b/cmd/gost/vendor/github.com/ginuerzh/gost/node.go index 4f8b020..66b84be 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/node.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/node.go @@ -84,7 +84,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { } switch node.Protocol { - case "http", "http2", "socks", "socks5", "ss": + case "http", "http2", "socks", "socks4", "socks4a", "socks5", "ss": default: node.Protocol = "" } diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/quic.go b/cmd/gost/vendor/github.com/ginuerzh/gost/quic.go index 0d97061..446acf2 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/quic.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/quic.go @@ -29,7 +29,8 @@ func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error { }, } if server.Handler == nil { - server.Handler = http.HandlerFunc(s.HandleRequest) + // server.Handler = http.HandlerFunc(s.HandleRequest) + server.Handler = http.HandlerFunc(NewHttp2Server(s.Base).HandleRequest) } return server.ListenAndServe() } diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/server.go b/cmd/gost/vendor/github.com/ginuerzh/gost/server.go index 4bafc91..3e25556 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/server.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/server.go @@ -3,6 +3,7 @@ package gost import ( "bufio" "crypto/tls" + "github.com/ginuerzh/gosocks4" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" @@ -173,6 +174,14 @@ func (s *ProxyServer) handleConn(conn net.Conn) { } NewSocks5Server(conn, s).HandleRequest(req) return + case "socks4", "socks4a": + req, err := gosocks4.ReadRequest(conn) + if err != nil { + glog.V(LWARNING).Infoln("[socks4]", err) + return + } + NewSocks4Server(conn, s).HandleRequest(req) + return } // http or socks5 diff --git a/cmd/gost/vendor/github.com/ginuerzh/gost/socks.go b/cmd/gost/vendor/github.com/ginuerzh/gost/socks.go index 59d801d..7727286 100644 --- a/cmd/gost/vendor/github.com/ginuerzh/gost/socks.go +++ b/cmd/gost/vendor/github.com/ginuerzh/gost/socks.go @@ -3,12 +3,9 @@ package gost import ( "bytes" "crypto/tls" - //"errors" + "github.com/ginuerzh/gosocks4" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" - //"os/exec" - //"io" - //"io/ioutil" "net" "net/url" "strconv" @@ -672,3 +669,78 @@ func ToSocksAddr(addr net.Addr) *gosocks5.Addr { Port: uint16(port), } } + +type Socks4Server struct { + conn net.Conn + Base *ProxyServer +} + +func NewSocks4Server(conn net.Conn, base *ProxyServer) *Socks4Server { + return &Socks4Server{conn: conn, Base: base} +} + +func (s *Socks4Server) HandleRequest(req *gosocks4.Request) { + glog.V(LDEBUG).Infof("[socks4] %s -> %s\n%s", s.conn.RemoteAddr(), req.Addr, req) + + switch req.Cmd { + case gosocks4.CmdConnect: + glog.V(LINFO).Infof("[socks4-connect] %s -> %s", s.conn.RemoteAddr(), req.Addr) + s.handleConnect(req) + + case gosocks4.CmdBind: + glog.V(LINFO).Infof("[socks4-bind] %s - %s", s.conn.RemoteAddr(), req.Addr) + s.handleBind(req) + + default: + glog.V(LWARNING).Infoln("[socks4] Unrecognized request:", req.Cmd) + } +} + +func (s *Socks4Server) handleConnect(req *gosocks4.Request) { + cc, err := s.Base.Chain.Dial(req.Addr.String()) + if err != nil { + glog.V(LWARNING).Infof("[socks4-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + rep := gosocks4.NewReply(gosocks4.Failed, nil) + rep.Write(s.conn) + glog.V(LDEBUG).Infof("[socks4-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) + return + } + defer cc.Close() + + rep := gosocks4.NewReply(gosocks4.Granted, nil) + if err := rep.Write(s.conn); err != nil { + glog.V(LWARNING).Infof("[socks4-connect] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + glog.V(LDEBUG).Infof("[socks4-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) + + glog.V(LINFO).Infof("[socks4-connect] %s <-> %s", s.conn.RemoteAddr(), req.Addr) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[socks4-connect] %s >-< %s", s.conn.RemoteAddr(), req.Addr) +} + +func (s *Socks4Server) handleBind(req *gosocks4.Request) { + cc, err := s.Base.Chain.GetConn() + + // connection error + if err != nil && err != ErrEmptyChain { + glog.V(LWARNING).Infof("[socks4-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + reply := gosocks4.NewReply(gosocks4.Failed, nil) + reply.Write(s.conn) + glog.V(LDEBUG).Infof("[socks4-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) + return + } + // TODO: serve socks4 bind + if err == ErrEmptyChain { + //s.bindOn(req.Addr.String()) + return + } + + defer cc.Close() + // forward request + req.Write(cc) + + glog.V(LINFO).Infof("[socks4-bind] %s <-> %s", s.conn.RemoteAddr(), cc.RemoteAddr()) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[socks4-bind] %s >-< %s", s.conn.RemoteAddr(), cc.RemoteAddr()) +} diff --git a/conn.go b/conn.go index a46a285..3a6259a 100644 --- a/conn.go +++ b/conn.go @@ -5,6 +5,8 @@ import ( "crypto/tls" "encoding/base64" "errors" + "fmt" + "github.com/ginuerzh/gosocks4" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" @@ -179,6 +181,39 @@ func (c *ProxyConn) Connect(addr string) error { if reply.Rep != gosocks5.Succeeded { return errors.New("Service unavailable") } + case "socks4", "socks4a": + atype := gosocks4.AddrDomain + host, port, err := net.SplitHostPort(addr) + if err != nil { + return err + } + p, _ := strconv.Atoi(port) + + if c.Node.Protocol == "socks4" { + taddr, err := net.ResolveTCPAddr("tcp4", addr) + if err != nil { + return err + } + host = taddr.IP.String() + p = taddr.Port + atype = gosocks4.AddrIPv4 + } + req := gosocks4.NewRequest(gosocks4.CmdConnect, + &gosocks4.Addr{Type: atype, Host: host, Port: uint16(p)}, nil) + if err := req.Write(c); err != nil { + return err + } + glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, req) + + reply, err := gosocks4.ReadReply(c) + if err != nil { + return err + } + glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, reply) + + if reply.Code != gosocks4.Granted { + return errors.New(fmt.Sprintf("%s: code=%d", c.Node.Protocol, reply.Code)) + } case "http": fallthrough default: diff --git a/node.go b/node.go index 4f8b020..66b84be 100644 --- a/node.go +++ b/node.go @@ -84,7 +84,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { } switch node.Protocol { - case "http", "http2", "socks", "socks5", "ss": + case "http", "http2", "socks", "socks4", "socks4a", "socks5", "ss": default: node.Protocol = "" } diff --git a/quic.go b/quic.go index 0d97061..446acf2 100644 --- a/quic.go +++ b/quic.go @@ -29,7 +29,8 @@ func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error { }, } if server.Handler == nil { - server.Handler = http.HandlerFunc(s.HandleRequest) + // server.Handler = http.HandlerFunc(s.HandleRequest) + server.Handler = http.HandlerFunc(NewHttp2Server(s.Base).HandleRequest) } return server.ListenAndServe() } diff --git a/server.go b/server.go index 4bafc91..3e25556 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,7 @@ package gost import ( "bufio" "crypto/tls" + "github.com/ginuerzh/gosocks4" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" @@ -173,6 +174,14 @@ func (s *ProxyServer) handleConn(conn net.Conn) { } NewSocks5Server(conn, s).HandleRequest(req) return + case "socks4", "socks4a": + req, err := gosocks4.ReadRequest(conn) + if err != nil { + glog.V(LWARNING).Infoln("[socks4]", err) + return + } + NewSocks4Server(conn, s).HandleRequest(req) + return } // http or socks5 diff --git a/socks.go b/socks.go index 59d801d..7727286 100644 --- a/socks.go +++ b/socks.go @@ -3,12 +3,9 @@ package gost import ( "bytes" "crypto/tls" - //"errors" + "github.com/ginuerzh/gosocks4" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" - //"os/exec" - //"io" - //"io/ioutil" "net" "net/url" "strconv" @@ -672,3 +669,78 @@ func ToSocksAddr(addr net.Addr) *gosocks5.Addr { Port: uint16(port), } } + +type Socks4Server struct { + conn net.Conn + Base *ProxyServer +} + +func NewSocks4Server(conn net.Conn, base *ProxyServer) *Socks4Server { + return &Socks4Server{conn: conn, Base: base} +} + +func (s *Socks4Server) HandleRequest(req *gosocks4.Request) { + glog.V(LDEBUG).Infof("[socks4] %s -> %s\n%s", s.conn.RemoteAddr(), req.Addr, req) + + switch req.Cmd { + case gosocks4.CmdConnect: + glog.V(LINFO).Infof("[socks4-connect] %s -> %s", s.conn.RemoteAddr(), req.Addr) + s.handleConnect(req) + + case gosocks4.CmdBind: + glog.V(LINFO).Infof("[socks4-bind] %s - %s", s.conn.RemoteAddr(), req.Addr) + s.handleBind(req) + + default: + glog.V(LWARNING).Infoln("[socks4] Unrecognized request:", req.Cmd) + } +} + +func (s *Socks4Server) handleConnect(req *gosocks4.Request) { + cc, err := s.Base.Chain.Dial(req.Addr.String()) + if err != nil { + glog.V(LWARNING).Infof("[socks4-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + rep := gosocks4.NewReply(gosocks4.Failed, nil) + rep.Write(s.conn) + glog.V(LDEBUG).Infof("[socks4-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) + return + } + defer cc.Close() + + rep := gosocks4.NewReply(gosocks4.Granted, nil) + if err := rep.Write(s.conn); err != nil { + glog.V(LWARNING).Infof("[socks4-connect] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + glog.V(LDEBUG).Infof("[socks4-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) + + glog.V(LINFO).Infof("[socks4-connect] %s <-> %s", s.conn.RemoteAddr(), req.Addr) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[socks4-connect] %s >-< %s", s.conn.RemoteAddr(), req.Addr) +} + +func (s *Socks4Server) handleBind(req *gosocks4.Request) { + cc, err := s.Base.Chain.GetConn() + + // connection error + if err != nil && err != ErrEmptyChain { + glog.V(LWARNING).Infof("[socks4-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + reply := gosocks4.NewReply(gosocks4.Failed, nil) + reply.Write(s.conn) + glog.V(LDEBUG).Infof("[socks4-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) + return + } + // TODO: serve socks4 bind + if err == ErrEmptyChain { + //s.bindOn(req.Addr.String()) + return + } + + defer cc.Close() + // forward request + req.Write(cc) + + glog.V(LINFO).Infof("[socks4-bind] %s <-> %s", s.conn.RemoteAddr(), cc.RemoteAddr()) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[socks4-bind] %s >-< %s", s.conn.RemoteAddr(), cc.RemoteAddr()) +}