Last active
June 20, 2023 09:08
-
-
Save fanpei91/b1c4daf375fd84d7fca079e16e5cf836 to your computer and use it in GitHub Desktop.
博客:https://fanpei91.com/posts/implement-double-proxies-to-cross-firewall-by-using-https/ 增强版:https://github.com/fanpei91/sandwich
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
package main | |
import ( | |
"crypto/tls" | |
"flag" | |
"fmt" | |
"io" | |
"log" | |
"net" | |
"net/http" | |
"net/http/httputil" | |
"net/url" | |
"strings" | |
) | |
const headerSecretKey = "Misha-Secret" | |
const reversedWebsite = "http://mirrors.codec-cluster.org/" | |
type localProxy struct { | |
remoteProxyAddr string | |
secure bool | |
secret string | |
} | |
func (l *localProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | |
hijacker, _ := rw.(http.Hijacker) | |
client, _, _ := hijacker.Hijack() | |
var remoteProxy net.Conn | |
var err error | |
if l.secure { | |
remoteProxy, err = tls.Dial("tcp", l.remoteProxyAddr, nil) | |
} else { | |
remoteProxy, err = net.Dial("tcp", l.remoteProxyAddr) | |
} | |
if err != nil { | |
return | |
} | |
req.Header.Set(headerSecretKey, l.secret) | |
req.Write(remoteProxy) | |
go transfer(remoteProxy, client) | |
transfer(client, remoteProxy) | |
} | |
type remoteProxy struct { | |
secret string | |
} | |
func (s *remoteProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | |
secretValue := req.Header.Get(headerSecretKey) | |
req.Header.Del(headerSecretKey) | |
if secretValue == s.secret { | |
s.crossWall(rw, req) | |
return | |
} | |
s.reverseProxy(rw, req) | |
} | |
func (s *remoteProxy) crossWall(rw http.ResponseWriter, req *http.Request) { | |
if strings.Index(req.Host, ":") < 0 || strings.HasSuffix(req.Host, "]") { | |
req.Host += ":80" | |
} | |
hijacker, _ := rw.(http.Hijacker) | |
localProxy, _, _ := hijacker.Hijack() | |
target, err := net.Dial("tcp", req.Host) | |
if err != nil { | |
return | |
} | |
if req.Method == http.MethodConnect { | |
localProxy.Write([]byte(fmt.Sprintf("%s 200 OK\r\n\r\n", req.Proto))) | |
} else { | |
if v := req.Header.Get("Proxy-Connection"); v != "" { | |
req.Header.Del("Proxy-Connection") | |
req.Header.Set("Connection", v) | |
} | |
req.Write(target) | |
} | |
go transfer(localProxy, target) | |
transfer(target, localProxy) | |
} | |
func (s *remoteProxy) reverseProxy(rw http.ResponseWriter, req *http.Request) { | |
u, _ := url.Parse(reversedWebsite) | |
req.URL.Host = u.Host | |
req.URL.Scheme = u.Scheme | |
req.Host = "" | |
httputil.NewSingleHostReverseProxy(u).ServeHTTP(rw, req) | |
} | |
func transfer(dst io.WriteCloser, src io.ReadCloser) { | |
defer dst.Close() | |
defer src.Close() | |
io.Copy(dst, src) | |
} | |
type options struct { | |
typo string | |
remoteProxyAddr string | |
listenAddr string | |
certFile string | |
keyFile string | |
secure bool | |
secret string | |
} | |
func main() { | |
var o options | |
flag.StringVar(&o.typo, "typo", "local", "start local or remote proxy. [local, remote]") | |
flag.StringVar(&o.remoteProxyAddr, "remote-proxy-addr", "yourdomain.com:443", "the remote proxy address to connect to") | |
flag.StringVar(&o.listenAddr, "addr", "127.0.0.1:8080", "listen on given address") | |
flag.StringVar(&o.certFile, "cert", "", "cert file path") | |
flag.StringVar(&o.keyFile, "key", "", "key file path") | |
flag.BoolVar(&o.secure, "secure", true, "secure mode") | |
flag.StringVar(&o.secret, "secret", "4db0f78bd1d9f723a1e8daf97cfb73d5af0777e5", "secret value") | |
flag.Parse() | |
var listener net.Listener | |
var err error | |
if listener, err = net.Listen("tcp", o.listenAddr); err != nil { | |
log.Panic(err) | |
} | |
if o.typo == "local" { | |
err = startLocalProxy(o, listener) | |
} else { | |
err = startRemoteProxy(o, listener) | |
} | |
if err != nil { | |
log.Panic(err) | |
} | |
} | |
func startLocalProxy(o options, listener net.Listener) error { | |
return http.Serve(listener, &localProxy{remoteProxyAddr: o.remoteProxyAddr, secure: o.secure, secret: o.secret}) | |
} | |
func startRemoteProxy(o options, listener net.Listener) error { | |
var err error | |
if o.certFile != "" && o.keyFile != "" { | |
err = http.ServeTLS(listener, &remoteProxy{secret: o.secret}, o.certFile, o.keyFile) | |
} else { | |
err = http.Serve(listener, &remoteProxy{secret: o.secret}) | |
} | |
return err | |
} |
@fanpei91 必须关注!
学习的时候遇到一个问题,https://gist.github.com/fanpei91/b1c4daf375fd84d7fca079e16e5cf836#file-misha-proxy-go-L44 这行代码为什么要加上,上一句 req.Write(remoteProxy)
不是已经发送了么?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
或者你用 switchyomega 插件来管理哪些都走代理,哪些不走代理。(我没用这个,我的实现比它还要更方便,简单,全面