Skip to content

Instantly share code, notes, and snippets.

@fanpei91
Last active June 20, 2023 09:08
Show Gist options
  • Save fanpei91/b1c4daf375fd84d7fca079e16e5cf836 to your computer and use it in GitHub Desktop.
Save fanpei91/b1c4daf375fd84d7fca079e16e5cf836 to your computer and use it in GitHub Desktop.
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
Copy link
Author

那看起来是 local proxy 没有指定 -secure=true 了。

@fanpei91
Copy link
Author

fanpei91 commented Mar 27, 2020

我修改这个参数默认为 true 了,可重新用这最新代码再编译一次了,或者手动指定为 true.

@ethsonliu
Copy link

ethsonliu commented Mar 27, 2020

@fanpei91 兄弟,可以了,真的是强!还有一点,访问国内网站也会直接走代理是吧?

@fanpei91
Copy link
Author

@ethsonliu 嗯,这么120 行代码的简单的东西,肯定没有区分国内外,不过我用的版本有区分,还有其他更丰富的功能,我可能会写个博客讲如何实现。你可以继续关注,半个月内吧应该。

@fanpei91
Copy link
Author

或者你用 switchyomega 插件来管理哪些都走代理,哪些不走代理。(我没用这个,我的实现比它还要更方便,简单,全面

@ethsonliu
Copy link

@fanpei91 必须关注!

@ethsonliu
Copy link

ethsonliu commented Jun 7, 2021

学习的时候遇到一个问题,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