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 兄弟,可以了,真的是强!还有一点,访问国内网站也会直接走代理是吧?
@ethsonliu 嗯,这么120 行代码的简单的东西,肯定没有区分国内外,不过我用的版本有区分,还有其他更丰富的功能,我可能会写个博客讲如何实现。你可以继续关注,半个月内吧应该。
或者你用 switchyomega 插件来管理哪些都走代理,哪些不走代理。(我没用这个,我的实现比它还要更方便,简单,全面
@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
我修改这个参数默认为 true 了,可重新用这最新代码再编译一次了,或者手动指定为 true.