Previously, I implemented a proxy service. At that time, I only wanted to support SOCKS5, because I often used Chrome to work with switchysharp, and the experience was great. However, I hate Chrome a little now. It is too large and occupies too many resources. In addition, I am used to locking web pages. As soon as I open chrome, there will be more than a dozen or even more processes, which makes me very uncomfortable. But I have to say that Chrome's security design is still very good. Then I tried Firefox. Well, I think it is similar to IE. then I gave up. Then I checked that I had reached 11, and I felt very good when I used it. So I want to support the IE proxy.
The proxy mechanism of IE is relatively small. For example, it only supports socks4, does not support SOCKS5, and is then divided into HTTP proxy, HTTPS proxy, and FTP Proxy. There is no powerful proxy plug-in mechanism like chrome. Although IE provides the PAC mechanism, I have to say that this mechanism is also very weak, and there is no real-time increase or decrease rule like switchysharp. For the above reasons, I have added several proxies based on the original code, but FTP proxies are not supported.
Socks4 proxy
The socks4 protocol is relatively simple. You can refer to the Wiki and OpenSSH documents. There is also a SOCKS4A Protocol later, but this SOCKS4A has never been used. The CONNECT Command Format of the socks4 protocol is very simple, that is, a request package and a response package. The first field in the request package is the version number, which occupies 1 byte, namely 0x04. The second field is of the command type and occupies 1 byte. 0x01 indicates the connect command, that is, the IP address of the request link: Port and 0x02 is bind. It is generally used in FTP scenarios and I have not implemented it. The third field is the peer port, which occupies 2 bytes and the network byte order. The fourth field is the peer IP address, which occupies 4 bytes and the network byte order; the fifth field is userid, with a variable length ending with 0x00. Note that in ie11, userid is the current user name and is not empty. Therefore, read the complete userid and the last 0x00.
The first field in the response packet occupies one byte, and the data is 0. The second field occupies one byte, indicating the status, 0x5a indicating the success, and 0x5b indicating the rejection or failure; the third byte and the fourth field have a total of 6 bytes, which will be ignored. Enter 0 directly.
The entire protocol is much simpler than SOCKS5, but it is not powerful. Because socks4 only supports the IP: Port mode, it means that when iefq is used, the local DNS will be taken first, and then the socks proxy will be taken after the address is obtained. The problem here is that if DNS is contaminated, it means FQ fails. Therefore, you must use the following HTTP proxy and HTTP tunnel.
HTTP Tunnel)
HTTP tunnel is relatively simple. The client connects to the server through the HTTP protocol and requests the server to link a port of a domain name or IP address. The Protocol is very simple, that is, the client sends the connect domain: Port HTTP/1.0 \ r \ n. After receiving the request, the server will link the specified domain name and port. After the connection is successful, the client will reply to HTTP/1.0 200 connection established \ r \ n after the client receives the response, it will start to forward the data through the proxy. In this case, the proxy is a blind switch, which is the same as the SOCKS protocol.
It is relatively simple to implement with go, and can be completed through the net/http package. Implement a servehttp method by yourself, and then find that the connection hijacked is dropped for the Connect Method Request. The Code is as follows:
hj, ok := response.(http.Hijacker)if !ok { http.Error(response, "Hijacker failed", http.StatusInternalServerError) return}conn, _, err := hj.Hijack()if err != nil { http.Error(response, err.Error(), http.StatusInternalServerError) return}defer conn.Close()
It should be noted that, after hijack, if you want to reply data in the HTTP format, you have to perform the operation on your own. There is no way to use the methods provided by net/HTTP. responsewriter. Fortunately, the FMT package of Go provides the fprint/fprintf function, so the operation is still simple.
Another point is that this HTTP tunnel allows additional data to be carried in the body when the CONNECT is initiated for optimization purposes. Therefore, after establishing a remote link, check whether there is still body data. If so, send the data.
HTTP Proxy
I originally thought that since the proxy mechanism in the HTTP tunnel mode is available, this setting is used. As a result, ie is not the case. The HTTP tunnel only uses the HTTPS URL, the common http url adopts the common HTTP proxy mechanism. HTTP common requests are similar to the following: Get/XXX/YYYY/zzzz.html HTTP/1.0, while HTTP proxy is get http://www.qqqq.com/xxx/yyy/zzz.html HTTP/1.0, then an additional HTTP header proxy-connection will be added. This is the first Google app. When processing the HTTP proxy request sent from the client, I replaced the URL with a normal relative Uri and checked whether there is a proxy-connection. If yes, the corresponding value is obtained, delete the header and add the connection header. Its value is the value corresponding to the original proxy-connection. Then, it is forwarded to the peer server. Go provides a package of net/HTTP/httputil, which encapsulates the implementation of a reverse proxy. You only need to provide the function for establishing the link and the function for processing HTTP. Request. The Code is as follows:
func NewHTTPProxy(remoteSocks, cryptoMethod string, password []byte) *HTTPProxy { return &HTTPProxy{ ReverseProxy: &httputil.ReverseProxy{ Director: director, Transport: &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { return dial(network, addr, remoteSocks, cryptoMethod, password) }, }, }, }}func dial(network, addr, remoteSocks, cryptoMethod string, password []byte) (net.Conn, error) { tcpAddr, err := net.ResolveTCPAddr(network, addr) if err != nil { return nil, err } remoteSvr, err := NewRemoteSocks(remoteSocks, cryptoMethod, password) if err != nil { return nil, err } // version(1) + cmd(1) + reserved(1) + addrType(1) + domainLength(1) + maxDomainLength(256) + port(2) req := []byte{0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} copy(req[4:8], []byte(tcpAddr.IP.To4())) binary.BigEndian.PutUint16(req[8:10], uint16(tcpAddr.Port)) err = remoteSvr.Handshake(req) if err != nil { remoteSvr.Close() return nil, err } conn := &HTTPProxyConn{ RemoteSocks: remoteSvr, } return conn, nil}func director(request *http.Request) { u, err := url.Parse(request.RequestURI) if err != nil { return } request.RequestURI = u.RequestURI() v := request.Header.Get("Proxy-Connection") if v != "" { request.Header.Del("Proxy-Connection") request.Header.Del("Connection") request.Header.Add("Connection", v) }}
Summary:
In essence, HTTP proxy and HTTP tunnel can be implemented through the same port, but I didn't do this, because I felt that code separation is more convenient for testing and modification. This saves a lot of trouble. However, the same port can be reused through a simple combination, and I will try to modify it later. HTTP proxy and tunnel are now implemented on the same port, and corresponding functions are implemented through a simple combination. Socks4 and SOCKS5 can also use the same port, but considering the issue of version judgment in the code, I think it is better to implement it separately.
We can also consider porting the switchysharp proxy policy to this proxy service, and then writing an Internet Explorer plug-in to implement functions similar to switchysharp, which is much more convenient. By the way, ie is actually doing well now.
Proxy Service Extension