├── .gitignore ├── config.simple.json ├── go.mod ├── README.md ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | *.iml 3 | -------------------------------------------------------------------------------- /config.simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen": ":443", 3 | "bypassAddr": "127.0.0.1:8080", 4 | "certificates": [ 5 | { 6 | "certificateFile": "/path/to/certificate", 7 | "keyFile": "/path/to/key" 8 | } 9 | ], 10 | "methodsAddrs": [ 11 | { 12 | "methodName": "V2RAY", 13 | "addr": "unix:/path/to/ds/file" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liberal-boy/v2ray-http-header-bypass 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect 7 | github.com/go-acme/lego v2.7.2+incompatible // indirect 8 | github.com/klauspost/cpuid v1.2.1 // indirect 9 | github.com/mholt/certmagic v0.6.2 10 | github.com/miekg/dns v1.1.16 // indirect 11 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c 12 | golang.org/x/crypto v0.0.0-20190907121410-71b5226ff739 // indirect 13 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect 14 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect 15 | golang.org/x/text v0.3.2 // indirect 16 | gopkg.in/square/go-jose.v2 v2.3.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v2ray-http-header-bypass 2 | 利用 v2ray 的 tcp 头部伪装实现将伪装流量与正常 http 流量分流,程序将指定`method`为的请求发给目标服务器 (v2ray),为其他的请求发给绕行服务器(caddy 等 web 服务器)。 3 | # example 4 | Web 服务器监听 http://127.0.0.1:8080 5 | outbound (client side) 6 | ```json 7 | { 8 | "protocol": "vmess", 9 | "settings": { 10 | "vnext": [ 11 | { 12 | "address": "example.com", 13 | "port": 443, 14 | "users": [ 15 | { 16 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 17 | } 18 | ] 19 | } 20 | ] 21 | }, 22 | "streamSettings": { 23 | "security": "tls", 24 | "network": "tcp", 25 | "tcpSettings": { 26 | "header": { 27 | "type": "http", 28 | "request": { 29 | "method": "V2RAY", 30 | "version": " ", 31 | "path": [""], 32 | "headers": { 33 | "": "" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | ``` 41 | ## tcp 42 | 43 | `v2ray-http-header-bypass -l :443 -d 127.0.0.1:8000 -b 127.0.0.1:8080 -cert /path/to/tls/cert -key /path/to/tls/key -method V2RAY` 44 | inbound (server side) 45 | ```json 46 | { 47 | "port": 8000, 48 | "protocol": "vmess", 49 | "settings": { 50 | "clients": [ 51 | { 52 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 53 | } 54 | ] 55 | }, 56 | "streamSettings": { 57 | "network": "tcp" 58 | } 59 | } 60 | ``` 61 | ## domain socket 62 | 63 | `v2ray-http-header-bypass -l :443 -d unix:/path/to/ds/file -b 127.0.0.1:8080 -cert /path/to/tls/cert -key /path/to/tls/key -method V2RAY` 64 | inbound (server side) 65 | ```json 66 | { 67 | "port": 8000, 68 | "protocol": "vmess", 69 | "settings": { 70 | "clients": [ 71 | { 72 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 73 | } 74 | ] 75 | }, 76 | "streamSettings": { 77 | "network": "ds", 78 | "dsSettings": { 79 | "path": "/path/to/ds/file" 80 | } 81 | } 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= 2 | github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 3 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= 4 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 5 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s= 8 | github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= 9 | github.com/go-acme/lego v2.7.2+incompatible h1:ThhpPBgf6oa9X/vRd0kEmWOsX7+vmYdckmGZSb+FEp0= 10 | github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= 11 | github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= 12 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 13 | github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= 14 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 15 | github.com/mholt/certmagic v0.6.2 h1:yy9cKm3rtxdh12SW4E51lzG3Eo6N59LEOfBQ0CTnMms= 16 | github.com/mholt/certmagic v0.6.2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= 17 | github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM= 18 | github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 19 | github.com/miekg/dns v1.1.16 h1:iMEQ/IVHxPTtx2Q07JP/k4CKRvSjiAZjZ0hnhgYEDmE= 20 | github.com/miekg/dns v1.1.16/go.mod h1:YNV562EiewvSmpCB6/W4c6yqjK7Z+M/aIS1JHsIVeg8= 21 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= 22 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 27 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 28 | golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 29 | golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b h1:Elez2XeF2p9uyVj0yEUDqQ56NFcDtcBNkYP7yv8YbUE= 30 | golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 31 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 32 | golang.org/x/crypto v0.0.0-20190907121410-71b5226ff739 h1:Gc7JIyxvWgD6m+QmVryY0MstDORNYididDGxgZ6Tnpk= 33 | golang.org/x/crypto v0.0.0-20190907121410-71b5226ff739/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 34 | golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 35 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= 36 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 37 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 38 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= 39 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 40 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= 42 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20190124100055-b90733256f2e h1:3GIlrlVLfkoipSReOMNAgApI0ajnalyLa/EZHHca/XI= 45 | golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 46 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 47 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= 49 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 51 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 52 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 53 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 54 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 55 | gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= 56 | gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 57 | gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= 58 | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 59 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "errors" 8 | "flag" 9 | "github.com/mholt/certmagic" 10 | "github.com/oxtoacart/bpool" 11 | "io" 12 | "log" 13 | "net" 14 | "os" 15 | "strings" 16 | "sync" 17 | ) 18 | 19 | var ENDING = []byte("\r\n\r\n") 20 | 21 | var ErrHeaderToLong = errors.New("header too long") 22 | 23 | type Config struct { 24 | Listen string `json:"listen"` 25 | Certificates []certificate `json:"certificates"` 26 | AutoAuthorityCertificates []string `json:"autoAuthorityCertificates"` 27 | BypassAddr string `json:"bypassAddr"` 28 | MethodAddrs []*methodAddr `json:"methodsAddrs"` 29 | BufSize int `json:"bufSize"` 30 | MaxHeaderSize int `json:"maxHeaderSize"` 31 | } 32 | 33 | type certificate struct { 34 | CertificateFile string `json:"certificateFile"` 35 | KeyFile string `json:"keyFile"` 36 | } 37 | 38 | type methodAddr struct { 39 | MethodName string `json:"methodName"` 40 | MethodBuf []byte `json:"_"` 41 | Addr string `json:"addr"` 42 | } 43 | 44 | var config = Config{ 45 | BufSize: 128, 46 | MaxHeaderSize: 128, 47 | } 48 | 49 | var methodBufPool *bpool.BytePool 50 | var headerBufPool *bpool.BytePool 51 | 52 | func main() { 53 | initConfig() 54 | 55 | server() 56 | } 57 | 58 | func initConfig() { 59 | var method, destination, cert, key, configPath string 60 | 61 | flag.StringVar(&config.Listen, "l", ":443", "listen address") 62 | flag.StringVar(&destination, "d", "127.0.0.1:8000", "v2ray server address") 63 | flag.StringVar(&config.BypassAddr, "b", "127.0.0.1:80", "bypass server address") 64 | flag.StringVar(&cert, "cert", "", "path to certificate file, blank to disable TLS") 65 | flag.StringVar(&key, "key", "", "path to key file, blank to disable TLS") 66 | flag.StringVar(&method, "method", "V2RAY", "method name of v2ray http header") 67 | flag.StringVar(&configPath, "config", "", "path to config file") 68 | flag.Parse() 69 | 70 | if configPath == "" { 71 | config.MethodAddrs = []*methodAddr{{ 72 | MethodName: method, 73 | Addr: destination, 74 | }} 75 | if cert != "" && key != "" { 76 | certs := strings.Split(cert, ",") 77 | keys := strings.Split(key, ",") 78 | config.Certificates = make([]certificate, len(certs)) 79 | for i := 0; i < len(config.Certificates); i++ { 80 | config.Certificates[i] = certificate{ 81 | CertificateFile: certs[i], 82 | KeyFile: keys[i], 83 | } 84 | } 85 | } 86 | } else { 87 | configFile, err := os.Open(configPath) 88 | if err != nil { 89 | log.Fatalf("fail to open file %s: %v", configPath, err) 90 | } 91 | defer func() { _ = configFile.Close() }() 92 | err = json.NewDecoder(configFile).Decode(&config) 93 | if err != nil { 94 | log.Fatalf("fail to parse config: %v", err) 95 | } 96 | } 97 | 98 | var methodBufLen int 99 | 100 | for _, ma := range config.MethodAddrs { 101 | ma.MethodBuf = []byte(ma.MethodName) 102 | mbl := len(ma.MethodBuf) + 1 103 | if mbl > methodBufLen { 104 | methodBufLen = mbl 105 | } 106 | } 107 | 108 | methodBufPool = bpool.NewBytePool(config.BufSize, methodBufLen) 109 | headerBufPool = bpool.NewBytePool(config.BufSize, config.MaxHeaderSize) 110 | } 111 | 112 | func listenTls() (ln net.Listener, err error) { 113 | magic := certmagic.NewDefault() 114 | magic.Agreed = true 115 | for _, cert := range config.Certificates { 116 | err = magic.CacheUnmanagedCertificatePEMFile(cert.CertificateFile, cert.KeyFile, nil) 117 | if err != nil { 118 | return 119 | } 120 | } 121 | 122 | err = magic.Manage(config.AutoAuthorityCertificates) 123 | if err != nil { 124 | return 125 | } 126 | 127 | tlsConfig := &tls.Config{ 128 | GetCertificate: magic.GetCertificate, 129 | MinVersion: tls.VersionTLS12, 130 | CipherSuites: []uint16{ 131 | tls.TLS_AES_128_GCM_SHA256, 132 | tls.TLS_AES_256_GCM_SHA384, 133 | tls.TLS_CHACHA20_POLY1305_SHA256, 134 | 135 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 136 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 137 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 138 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 139 | tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, 140 | tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 141 | }, 142 | } 143 | 144 | ln, err = tls.Listen("tcp", config.Listen, tlsConfig) 145 | return 146 | } 147 | 148 | func listenTcp() (ln net.Listener, err error) { 149 | ln, err = net.Listen("tcp", config.Listen) 150 | return 151 | } 152 | 153 | func server() { 154 | var ln net.Listener 155 | var err error 156 | 157 | if len(config.Certificates) != 0 { 158 | ln, err = listenTls() 159 | } else { 160 | ln, err = listenTcp() 161 | } 162 | 163 | if err != nil { 164 | log.Fatalf("failed to listen on %s: %v", config.Listen, err) 165 | } 166 | 167 | defer func() { _ = ln.Close() }() 168 | for { 169 | conn, err := ln.Accept() 170 | if err != nil { 171 | log.Printf("fail to establish conn: %v\n", err) 172 | continue 173 | } 174 | go handle(conn) 175 | } 176 | } 177 | 178 | func handle(srcConn net.Conn) { 179 | defer func() { _ = srcConn.Close() }() 180 | 181 | buf := methodBufPool.Get() 182 | var once sync.Once 183 | putBuf := func() { 184 | once.Do(func() { 185 | methodBufPool.Put(buf) 186 | }) 187 | } 188 | defer putBuf() 189 | _, err := srcConn.Read(buf) 190 | if err != nil && err != io.EOF { 191 | log.Printf("fail to read method:%v\n", err) 192 | return 193 | } 194 | var isSpecifiedMethod bool 195 | var addr string 196 | var leftOver []byte 197 | for i, b := range buf { 198 | if b == ' ' { 199 | for _, ma := range config.MethodAddrs { 200 | if bytes.Compare(buf[0:i], ma.MethodBuf) == 0 { 201 | isSpecifiedMethod = true 202 | putBuf() 203 | addr = ma.Addr 204 | leftOver, err = removeHttpHeader(srcConn) 205 | if err != nil && err != io.EOF { 206 | log.Printf("fail to remove http header:%v\n", err) 207 | return 208 | } 209 | break 210 | } 211 | } 212 | break 213 | } 214 | } 215 | 216 | if addr == "" { 217 | addr = config.BypassAddr 218 | } 219 | 220 | var dstConn net.Conn 221 | if strings.HasPrefix(addr, "unix:") { 222 | dstConn, err = net.Dial("unix", addr[5:]) 223 | } else { 224 | dstConn, err = net.Dial("tcp", addr) 225 | } 226 | 227 | if err != nil { 228 | log.Printf("fail to connect to %s :%v\n", addr, err) 229 | return 230 | } 231 | 232 | defer func() { _ = dstConn.Close() }() 233 | 234 | var wg sync.WaitGroup 235 | wg.Add(2) 236 | 237 | go func(srcConn net.Conn, dstConn net.Conn) { 238 | if !isSpecifiedMethod { 239 | _, _ = dstConn.Write(buf) 240 | putBuf() 241 | } else { 242 | _, _ = dstConn.Write(leftOver) 243 | } 244 | _, err := io.Copy(dstConn, srcConn) 245 | if err != nil && err != io.EOF { 246 | log.Printf("failed to send to %s:%v\n", addr, err) 247 | } 248 | wg.Done() 249 | }(srcConn, dstConn) 250 | go func(srcConn net.Conn, dstConn net.Conn) { 251 | if isSpecifiedMethod { 252 | _, _ = srcConn.Write(ENDING) 253 | } 254 | _, err := io.Copy(srcConn, dstConn) 255 | if err != nil && err != io.EOF { 256 | log.Printf("failed to read from %s: %v\n", addr, err) 257 | } 258 | wg.Done() 259 | }(srcConn, dstConn) 260 | 261 | wg.Wait() 262 | } 263 | 264 | func removeHttpHeader(reader io.Reader) (leftOver []byte, err error) { 265 | buf := headerBufPool.Get() 266 | defer headerBufPool.Put(buf) 267 | n, err := reader.Read(buf) 268 | if err != nil && err != io.EOF { 269 | return 270 | } 271 | idxEnding := bytes.Index(buf[0:n], ENDING) 272 | if idxEnding < 0 { 273 | err = ErrHeaderToLong 274 | return 275 | } 276 | leftOver = buf[idxEnding+len(ENDING) : n] 277 | return 278 | } 279 | --------------------------------------------------------------------------------