├── .gitignore ├── .misc ├── android.jpg ├── console.png ├── logo.png ├── speed.png ├── speed_gfw.PNG └── speed_ssr.PNG ├── LICENSE ├── client.go ├── cmd ├── goflyway │ └── main.go └── mitm-relay │ └── index.py ├── fd ├── fd.go ├── fd_go19.go ├── fd_non_go19.go ├── fd_unix.go └── fd_windows.go ├── io.go ├── readme.md ├── server.go ├── ssvpn_interop.go ├── ssvpn_windows.go ├── toh ├── client_conn.go ├── conn_test.go ├── dnshelper │ ├── dns.go │ └── dns_test.go ├── frame.go ├── frame_test.go ├── listendial.go ├── option.go ├── orch.go ├── proxy_test.go ├── read_conn.go ├── server_conn.go ├── util.go └── websocket.go ├── util.go └── v ├── errno.go └── vprint.go /.gitignore: -------------------------------------------------------------------------------- 1 | top-1m.csv 2 | debug 3 | launch.json 4 | *.exe 5 | *.dll 6 | *.a 7 | *.csv.* 8 | .vscode 9 | build/ 10 | .DS_Store 11 | ca.pem 12 | key.pem 13 | gfw.conf 14 | 15 | ## Ignore Visual Studio temporary files, build results, and 16 | ## files generated by popular Visual Studio add-ons. 17 | ## 18 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 19 | 20 | # User-specific files 21 | *.suo 22 | *.user 23 | *.userosscache 24 | *.sln.docstates 25 | 26 | # User-specific files (MonoDevelop/Xamarin Studio) 27 | *.userprefs 28 | 29 | # Build results 30 | [Dd]ebug/ 31 | [Dd]ebugPublic/ 32 | [Rr]elease/ 33 | [Rr]eleases/ 34 | x64/ 35 | x86/ 36 | bld/ 37 | [Bb]in/ 38 | [Oo]bj/ 39 | [Ll]og/ 40 | 41 | # Visual Studio 2015 cache/options directory 42 | .vs/ 43 | # Uncomment if you have tasks that create the project's static files in wwwroot 44 | #wwwroot/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUNIT 51 | *.VisualState.xml 52 | TestResult.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | **/Properties/launchSettings.json 67 | 68 | *_i.c 69 | *_p.c 70 | *_i.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.pch 75 | *.pdb 76 | *.pgc 77 | *.pgd 78 | *.rsp 79 | *.sbr 80 | *.tlb 81 | *.tli 82 | *.tlh 83 | *.tmp 84 | *.tmp_proj 85 | *.log 86 | *.vspscc 87 | *.vssscc 88 | .builds 89 | *.pidb 90 | *.svclog 91 | *.scc 92 | 93 | # Chutzpah Test files 94 | _Chutzpah* 95 | 96 | # Visual C++ cache files 97 | ipch/ 98 | *.aps 99 | *.ncb 100 | *.opendb 101 | *.opensdf 102 | *.sdf 103 | *.cachefile 104 | *.VC.db 105 | *.VC.VC.opendb 106 | 107 | # Visual Studio profiler 108 | *.psess 109 | *.vsp 110 | *.vspx 111 | *.sap 112 | 113 | # Visual Studio Trace Files 114 | *.e2e 115 | 116 | # TFS 2012 Local Workspace 117 | $tf/ 118 | 119 | # Guidance Automation Toolkit 120 | *.gpState 121 | 122 | # ReSharper is a .NET coding add-in 123 | _ReSharper*/ 124 | *.[Rr]e[Ss]harper 125 | *.DotSettings.user 126 | 127 | # JustCode is a .NET coding add-in 128 | .JustCode 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # The packages folder can be ignored because of Package Restore 188 | **/packages/* 189 | # except build/, which is used as an MSBuild target. 190 | !**/packages/build/ 191 | # Uncomment if necessary however generally it will be regenerated when needed 192 | #!**/packages/repositories.config 193 | # NuGet v3's project.json files produces more ignorable files 194 | *.nuget.props 195 | *.nuget.targets 196 | 197 | # Microsoft Azure Build Output 198 | csx/ 199 | *.build.csdef 200 | 201 | # Microsoft Azure Emulator 202 | ecf/ 203 | rcf/ 204 | 205 | # Windows Store app package directories and files 206 | AppPackages/ 207 | BundleArtifacts/ 208 | Package.StoreAssociation.xml 209 | _pkginfo.txt 210 | *.appx 211 | 212 | # Visual Studio cache files 213 | # files ending in .cache can be ignored 214 | *.[Cc]ache 215 | # but keep track of directories ending in .cache 216 | !*.[Cc]ache/ 217 | 218 | # Others 219 | ClientBin/ 220 | ~$* 221 | *~ 222 | *.dbmdl 223 | *.dbproj.schemaview 224 | *.jfm 225 | *.pfx 226 | *.publishsettings 227 | orleans.codegen.cs 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | 244 | # SQL Server files 245 | *.mdf 246 | *.ldf 247 | *.ndf 248 | 249 | # Business Intelligence projects 250 | *.rdl.data 251 | *.bim.layout 252 | *.bim_*.settings 253 | 254 | # Microsoft Fakes 255 | FakesAssemblies/ 256 | 257 | # GhostDoc plugin setting file 258 | *.GhostDoc.xml 259 | 260 | # Node.js Tools for Visual Studio 261 | .ntvs_analysis.dat 262 | node_modules/ 263 | 264 | # Typescript v1 declaration files 265 | typings/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush 296 | .cr/ 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | heap.txt 321 | *.keystore 322 | cmd/goflyway/lib 323 | shadowsocks* 324 | -------------------------------------------------------------------------------- /.misc/android.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coyove/goflyway/dfffaed0d1031c343ce6827ee1efbeefac1311ec/.misc/android.jpg -------------------------------------------------------------------------------- /.misc/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coyove/goflyway/dfffaed0d1031c343ce6827ee1efbeefac1311ec/.misc/console.png -------------------------------------------------------------------------------- /.misc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coyove/goflyway/dfffaed0d1031c343ce6827ee1efbeefac1311ec/.misc/logo.png -------------------------------------------------------------------------------- /.misc/speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coyove/goflyway/dfffaed0d1031c343ce6827ee1efbeefac1311ec/.misc/speed.png -------------------------------------------------------------------------------- /.misc/speed_gfw.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coyove/goflyway/dfffaed0d1031c343ce6827ee1efbeefac1311ec/.misc/speed_gfw.PNG -------------------------------------------------------------------------------- /.misc/speed_ssr.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coyove/goflyway/dfffaed0d1031c343ce6827ee1efbeefac1311ec/.misc/speed_ssr.PNG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 coyove 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package goflyway 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/coyove/goflyway/toh" 13 | "github.com/coyove/goflyway/v" 14 | 15 | "net" 16 | ) 17 | 18 | type ClientConfig struct { 19 | commonConfig 20 | Upstream string 21 | Bind string 22 | URLHeader string 23 | PathPattern string 24 | WebSocket bool 25 | VPN bool 26 | Dynamic bool 27 | } 28 | 29 | func NewClient(localaddr string, config *ClientConfig) error { 30 | config.check() 31 | 32 | tr := *http.DefaultTransport.(*http.Transport) 33 | if config.VPN { 34 | tr.DialContext = func(ctx context.Context, network string, address string) (net.Conn, error) { 35 | return vpnDial(address) 36 | } 37 | } 38 | 39 | dialer := toh.NewDialer(config.Key, config.Upstream, 40 | toh.WithWebSocket(config.WebSocket), 41 | toh.WithInactiveTimeout(config.Timeout), 42 | toh.WithTransport(&tr), 43 | toh.WithMaxWriteBuffer(int(config.WriteBuffer)), 44 | toh.WithHeader(config.URLHeader)) 45 | 46 | mux, err := net.Listen("tcp", localaddr) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | for { 52 | conn, err := mux.Accept() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | go func(conn net.Conn) { 58 | downconn := toh.NewBufConn(conn) 59 | defer conn.Close() 60 | 61 | var bind = config.Bind 62 | 63 | if config.Dynamic { 64 | dst, err := handleSOCKS5(downconn) 65 | if err != nil { 66 | v.Eprint("SOCKS5 server error: ", err) 67 | return 68 | } 69 | bind = dst 70 | v.Vprint("SOCKS5 destination: ", dst) 71 | } 72 | 73 | up, err := dialer.Dial() 74 | 75 | if err != nil { 76 | v.Eprint("dial server: ", err) 77 | return 78 | } 79 | defer up.Close() 80 | 81 | upconn := toh.NewBufConn(up) 82 | if _, err := upconn.Write([]byte(bind + "\n")); err != nil { 83 | v.Eprint("failed to req: ", err) 84 | return 85 | } 86 | 87 | resp, err := upconn.ReadBytes('\n') 88 | if err != nil || string(resp) != "OK\n" { 89 | v.Eprint("server failed to ack: ", err, ", resp: ", string(resp)) 90 | return 91 | } 92 | 93 | if config.Dynamic { 94 | // SOCKS5 OK response 95 | downconn.Write([]byte{0x05, 0, 0, 1, 0, 0, 0, 0, 0, 0}) 96 | } 97 | 98 | Bridge(upconn, downconn, nil, config.Stat) 99 | }(conn) 100 | } 101 | } 102 | 103 | func handleSOCKS5(conn net.Conn) (string, error) { 104 | buf := make([]byte, 256) 105 | if _, err := io.ReadFull(conn, buf[:2]); err != nil { 106 | return "", fmt.Errorf("failed to read header: %v", err) 107 | } 108 | 109 | if buf[0] != 0x05 { 110 | return "", fmt.Errorf("unsupported SOCKS version: %v", buf[0]) 111 | } 112 | 113 | numMethods := int(buf[1]) 114 | if _, err := io.ReadFull(conn, buf[:numMethods]); err != nil { 115 | return "", fmt.Errorf("failed to read methods: %v", err) 116 | } 117 | 118 | if numMethods > 1 { 119 | v.VVVprint("client supported methods: ", buf[:numMethods]) 120 | } 121 | 122 | // TODO: auth 123 | if _, err := conn.Write([]byte{0x05, 0}); err != nil { 124 | return "", fmt.Errorf("failed to handshake: %v", err) 125 | } 126 | 127 | // read destination 128 | _, err := io.ReadFull(conn, buf[:3+1]) 129 | if err != nil { 130 | return "", fmt.Errorf("failed to read destination: %v", err) 131 | } 132 | 133 | var addrsize int 134 | var method = buf[3] 135 | 136 | switch method { 137 | case 0x01: 138 | addrsize = net.IPv4len + 2 139 | case 0x04: 140 | addrsize = net.IPv6len + 2 141 | case 0x03: 142 | // read one extra byte that indicates the length of the domain 143 | if _, err := io.ReadFull(conn, buf[:1]); err != nil { 144 | return "", fmt.Errorf("failed to read domain destination: %v", err) 145 | } 146 | addrsize = int(buf[0]) + 2 147 | default: 148 | return "", fmt.Errorf("invalid address type: %v", buf[3]) 149 | } 150 | 151 | if _, err = io.ReadFull(conn, buf[:addrsize]); err != nil { 152 | return "", fmt.Errorf("failed to read destination: %v", err) 153 | } 154 | 155 | var host string 156 | var port = strconv.Itoa(int(binary.BigEndian.Uint16(buf[addrsize-2 : addrsize]))) 157 | 158 | switch method { 159 | case 0x01, 0x04: 160 | host = net.IP(buf[:addrsize-2]).String() 161 | default: 162 | host = string(buf[:addrsize-2]) 163 | } 164 | 165 | if strings.Contains(host, ":") { 166 | // IPv6? 167 | host = "[" + host + "]" 168 | } 169 | 170 | return host + ":" + port, nil 171 | } 172 | -------------------------------------------------------------------------------- /cmd/goflyway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/binary" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "math/rand" 12 | "net" 13 | "net/http" 14 | "os" 15 | "path/filepath" 16 | "regexp" 17 | "strconv" 18 | "strings" 19 | "sync" 20 | "time" 21 | 22 | "github.com/coyove/common/sched" 23 | "github.com/coyove/goflyway" 24 | "github.com/coyove/goflyway/v" 25 | "golang.org/x/crypto/acme/autocert" 26 | ) 27 | 28 | var ( 29 | version = "__devel__" 30 | remoteAddr string 31 | localAddr string 32 | addr string 33 | httpsProxy string 34 | resetTraffic bool 35 | cconfig = &goflyway.ClientConfig{} 36 | sconfig = &goflyway.ServerConfig{} 37 | ) 38 | 39 | func printHelp(a ...interface{}) { 40 | if len(a) > 0 { 41 | fmt.Printf("goflyway: ") 42 | fmt.Println(a...) 43 | } 44 | fmt.Println("usage: goflyway -DLhHUvkqpPtTwWy address:port") 45 | os.Exit(0) 46 | } 47 | 48 | func main() { 49 | sched.Verbose = false 50 | 51 | for i, last := 1, rune(0); i < len(os.Args); i++ { 52 | p := strings.TrimLeft(os.Args[i], "-") 53 | 54 | // HACK: ss-local compatible command flags 55 | if p == "fast-open" || p == "V" || p == "u" || p == "m" || p == "b" { 56 | if i < len(os.Args)-1 && !strings.HasPrefix(os.Args[i+1], "-") { 57 | i++ 58 | } 59 | continue 60 | } 61 | 62 | if len(p) != len(os.Args[i]) { 63 | for i, c := range p { 64 | switch c { 65 | case 'h': 66 | printHelp() 67 | //case 'V': 68 | // printHelp(version) 69 | case 'L', 'P', 'p', 'k', 't', 'T', 'W', 'H', 'U', 'D', 'c': 70 | last = c 71 | case 'v': 72 | v.Verbose++ 73 | case 'q': 74 | v.Verbose = -1 75 | case 'w': 76 | cconfig.WebSocket = true 77 | case 'y': 78 | resetTraffic = true 79 | case '=': 80 | i++ 81 | fallthrough 82 | default: 83 | if last == 0 { 84 | printHelp("illegal option --", string(c)) 85 | } 86 | p = p[i:] 87 | goto PARSE 88 | } 89 | } 90 | continue 91 | } 92 | PARSE: 93 | if strings.HasPrefix(p, "\"") { 94 | if p, _ = strconv.Unquote(p); p == "" { 95 | printHelp("illegal option --", string(last)) 96 | } 97 | } 98 | switch last { 99 | case 'D': 100 | cconfig.Dynamic = true 101 | fallthrough 102 | case 'L': 103 | switch parts := strings.Split(p, ":"); len(parts) { 104 | case 1: 105 | localAddr = ":" + parts[0] 106 | case 2: 107 | localAddr = p 108 | case 3: 109 | localAddr, remoteAddr = ":"+parts[0], parts[1]+":"+parts[2] 110 | case 4: 111 | localAddr, remoteAddr = parts[0]+":"+parts[1], parts[2]+":"+parts[3] 112 | default: 113 | printHelp("illegal option --", string(last), p) 114 | } 115 | case 'P': 116 | sconfig.ProxyPassAddr = p 117 | case 'U': 118 | cconfig.PathPattern = p 119 | case 'T': 120 | speed, _ := strconv.ParseInt(p, 10, 64) 121 | sconfig.SpeedThrot = goflyway.NewTokenBucket(speed, speed*25) 122 | case 'W': 123 | writebuffer, _ := strconv.ParseInt(p, 10, 64) 124 | sconfig.WriteBuffer, cconfig.WriteBuffer = writebuffer, writebuffer 125 | case 't': 126 | *(*int64)(&cconfig.Timeout), _ = strconv.ParseInt(p+"000000000", 10, 64) 127 | sconfig.Timeout = cconfig.Timeout 128 | case 'p', 'k': 129 | sconfig.Key, cconfig.Key = p, p 130 | case 'H': 131 | cconfig.URLHeader = p 132 | httpsProxy = p 133 | case 'c': 134 | buf, _ := ioutil.ReadFile(p) 135 | cmds := make(map[string]interface{}) 136 | json.Unmarshal(buf, &cmds) 137 | cconfig.Key, cconfig.VPN = cmds["password"].(string), true 138 | addr = fmt.Sprintf("%v:%v", cmds["server"], cmds["server_port"]) 139 | 140 | v.Verbose = 3 141 | v.Vprint(os.Args, " config: ", cmds) 142 | default: 143 | addr = p 144 | } 145 | last = 0 146 | } 147 | 148 | if addr == "" { 149 | if localAddr == "" { 150 | v.Vprint("assume you want a default server at :8100") 151 | addr = ":8100" 152 | } else { 153 | printHelp("missing address:port to listen/connect") 154 | } 155 | } 156 | 157 | if localAddr != "" && remoteAddr == "" { 158 | _, port, err1 := net.SplitHostPort(localAddr) 159 | host, _, err2 := net.SplitHostPort(addr) 160 | remoteAddr = host + ":" + port 161 | if err1 != nil || err2 != nil { 162 | printHelp("invalid address --", localAddr, addr) 163 | } 164 | } 165 | 166 | if localAddr != "" && remoteAddr != "" { 167 | cconfig.Bind = remoteAddr 168 | cconfig.Upstream = addr 169 | cconfig.Stat = &goflyway.Traffic{} 170 | 171 | if v.Verbose > 0 { 172 | go watchTraffic(cconfig, resetTraffic) 173 | } 174 | if cconfig.Dynamic { 175 | v.Vprint("dynamic: forward ", localAddr, " to * through ", addr) 176 | } else { 177 | v.Vprint("forward ", localAddr, " to ", remoteAddr, " through ", addr) 178 | } 179 | if cconfig.WebSocket { 180 | v.Vprint("relay: use Websocket protocol") 181 | } 182 | if a := os.Getenv("http_proxy") + os.Getenv("HTTP_PROXY"); a != "" { 183 | v.Vprint("note: system HTTP proxy is set to: ", a) 184 | } 185 | if a := os.Getenv("https_proxy") + os.Getenv("HTTPS_PROXY"); a != "" { 186 | v.Vprint("note: system HTTPS proxy is set to: ", a) 187 | } 188 | 189 | v.Eprint(goflyway.NewClient(localAddr, cconfig)) 190 | } else if httpsProxy != "" { 191 | v.Vprint("server listen on ", addr, " (https://", httpsProxy, ")") 192 | m := &autocert.Manager{ 193 | Cache: autocert.DirCache("secret-dir"), 194 | Prompt: autocert.AcceptTOS, 195 | HostPolicy: autocert.HostWhitelist(httpsProxy), 196 | } 197 | s := &http.Server{ 198 | Addr: addr, 199 | TLSConfig: m.TLSConfig(), 200 | } 201 | for i, p := range s.TLSConfig.NextProtos { 202 | if p == "h2" { 203 | s.TLSConfig.NextProtos[i] = "h2-disabled" 204 | } 205 | } 206 | s.Handler = &connector{ 207 | timeout: sconfig.Timeout, 208 | auth: sconfig.Key, 209 | } 210 | if os.Getenv("GFW_TEST") == "1" { 211 | v.Eprint(s.ListenAndServe()) 212 | } else { 213 | v.Eprint(s.ListenAndServeTLS("", "")) 214 | } 215 | } else { 216 | v.Vprint("server listen on ", addr) 217 | v.Eprint(goflyway.NewServer(addr, sconfig)) 218 | } 219 | } 220 | 221 | func watchTraffic(cconfig *goflyway.ClientConfig, reset bool) { 222 | path := filepath.Join(os.TempDir(), "goflyway_traffic") 223 | 224 | tmpbuf, _ := ioutil.ReadFile(path) 225 | if len(tmpbuf) != 16 || reset { 226 | tmpbuf = make([]byte, 16) 227 | } 228 | 229 | cconfig.Stat.Set(int64(binary.BigEndian.Uint64(tmpbuf)), int64(binary.BigEndian.Uint64(tmpbuf[8:]))) 230 | 231 | var lastSent, lastRecv int64 232 | for range time.Tick(time.Second * 5) { 233 | s, r := *cconfig.Stat.Sent(), *cconfig.Stat.Recv() 234 | sv, rv := float64(s-lastSent)/1024/1024/5, float64(r-lastRecv)/1024/1024/5 235 | lastSent, lastRecv = s, r 236 | 237 | if sv >= 0.001 || rv >= 0.001 { 238 | v.Vprint("client send: ", float64(s)/1024/1024, "M (", sv, "M/s), recv: ", float64(r)/1024/1024, "M (", rv, "M/s)") 239 | } 240 | 241 | binary.BigEndian.PutUint64(tmpbuf, uint64(s)) 242 | binary.BigEndian.PutUint64(tmpbuf[8:], uint64(r)) 243 | ioutil.WriteFile(path, tmpbuf, 0644) 244 | } 245 | } 246 | 247 | type connector struct { 248 | book TTLMap 249 | mu sync.Mutex 250 | timeout time.Duration 251 | auth string 252 | } 253 | 254 | func (c *connector) getClientIP(r *http.Request) string { 255 | clientIP := r.Header.Get("X-Forwarded-For") 256 | clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) 257 | if clientIP == "" { 258 | clientIP = strings.TrimSpace(r.Header.Get("X-Real-Ip")) 259 | } 260 | if clientIP != "" { 261 | return clientIP 262 | } 263 | if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil { 264 | return ip 265 | } 266 | return "" 267 | } 268 | 269 | func (c *connector) getFingerprint(r *http.Request) string { 270 | return r.UserAgent() //+ "/" + r.Header.Get("Accept-Language") 271 | } 272 | 273 | func (c *connector) ServeHTTP(w http.ResponseWriter, r *http.Request) { 274 | plain, iscurl := false, false 275 | pp := func() { 276 | path := r.URL.Path 277 | if !strings.HasPrefix(path, "/") { 278 | path = "/" + path 279 | } 280 | req, _ := http.NewRequest("GET", "https://arxiv.org"+path, nil) 281 | req.Header.Add("Accept", "text/html") 282 | resp, err := http.DefaultClient.Do(req) 283 | if err != nil { 284 | w.WriteHeader(500) 285 | return 286 | } 287 | defer resp.Body.Close() 288 | for k, v := range resp.Header { 289 | w.Header().Add(k, v[0]) 290 | } 291 | io.Copy(w, resp.Body) 292 | } 293 | 294 | if r.Method != "CONNECT" { 295 | if r.URL.Host == "" { 296 | pp() 297 | return 298 | } 299 | 300 | v.VVprint("plain http proxy: ", r.URL.Host) 301 | plain = true 302 | } 303 | 304 | // we are inside GFW and should pass data to upstream 305 | host := r.URL.Host 306 | if !regexp.MustCompile(`:\d+$`).MatchString(host) { 307 | if plain { 308 | host += ":80" 309 | } else { 310 | host += ":443" 311 | } 312 | } 313 | 314 | ip := c.getClientIP(r) 315 | 316 | if plain && host == c.auth+".com:80" { 317 | w.Header().Add("Content-Type", "text/html") 318 | w.Write([]byte(fmt.Sprintf("BookkeeperWhitelisted: %s@%s", c.getFingerprint(r), c.getClientIP(r)))) 319 | 320 | c.book.Add(ip, "white", 0) 321 | c.book.Add(c.getFingerprint(r), ip, 0) 322 | return 323 | } 324 | 325 | { // Auth 326 | c.mu.Lock() 327 | state, _ := c.book.Get(ip) 328 | if state == "white" { 329 | goto OK 330 | } else if state == "black" { 331 | v.VVprint(ip, " is not known and is blocked") 332 | } else { 333 | authData := strings.TrimPrefix(r.Header.Get("Proxy-Authorization"), "Basic ") 334 | if authData == "" { 335 | authData = strings.TrimPrefix(r.Header.Get("Authentication"), "Basic ") 336 | } 337 | pa, err := base64.StdEncoding.DecodeString(strings.TrimSpace(authData)) 338 | if err == nil && bytes.ContainsRune(pa, ':') { 339 | if string(pa[bytes.IndexByte(pa, ':')+1:]) == c.auth { 340 | goto OK 341 | } 342 | } 343 | 344 | ip2, ok := c.book.Get(c.getFingerprint(r)) 345 | if !ok { 346 | c.mu.Unlock() 347 | pp() 348 | return 349 | } 350 | 351 | if state2, ok := c.book.Get(ip2); ok && state2 == "white" { 352 | v.VVprint(ip, " is not known, but fingerprint is okay") 353 | c.book.Delete(ip2) 354 | goto OK 355 | } 356 | v.VVprint(ip, " is not known nor is the fingerprint") 357 | } 358 | 359 | c.book.Add(ip, "black", time.Second*10) 360 | c.mu.Unlock() 361 | 362 | pp() 363 | return 364 | 365 | OK: 366 | c.book.Add(ip, "white", 0) 367 | c.book.Add(c.getFingerprint(r), ip, 0) 368 | c.mu.Unlock() 369 | 370 | iscurl = strings.Contains(strings.ToLower(r.UserAgent()), "curl/") 371 | } 372 | 373 | if plain { 374 | resp, err := http.DefaultTransport.RoundTrip(r) 375 | if err != nil { 376 | w.WriteHeader(500) 377 | return 378 | } 379 | 380 | defer resp.Body.Close() 381 | 382 | for k := range w.Header() { 383 | w.Header().Del(k) 384 | } 385 | 386 | for k, v := range resp.Header { 387 | for _, v := range v { 388 | w.Header().Add(k, v) 389 | } 390 | } 391 | w.WriteHeader(resp.StatusCode) 392 | 393 | io.Copy(w, resp.Body) 394 | return 395 | } 396 | 397 | up, err := net.DialTimeout("tcp", host, c.timeout) 398 | if err != nil { 399 | v.Eprint(host, err) 400 | w.WriteHeader(500) 401 | return 402 | } 403 | 404 | hij, _ := w.(http.Hijacker) // No HTTP2 405 | proxyClient, _, err := hij.Hijack() 406 | if err != nil { 407 | v.Eprint(host, err) 408 | w.WriteHeader(500) 409 | return 410 | } 411 | 412 | if iscurl { 413 | proxyClient.Write([]byte("HTTP/1.1 200 OK\r\n")) 414 | } else { 415 | proxyClient.Write([]byte("HTTP/1.0 200 Connection Established\r\n")) 416 | } 417 | proxyClient.Write([]byte("Filler: ")) 418 | for i := 0; i < rand.Intn(100)+300; i++ { 419 | proxyClient.Write([]byte("aaaaaaaa")) 420 | } 421 | proxyClient.Write([]byte("\r\n\r\n")) 422 | 423 | go func() { 424 | var wait = make(chan bool) 425 | var err1, err2 error 426 | var to1, to2 bool 427 | 428 | go func() { 429 | err1, to1 = bridge(proxyClient, up, c.timeout) 430 | wait <- true 431 | }() 432 | 433 | err2, to2 = bridge(up, proxyClient, c.timeout) 434 | select { 435 | case <-wait: 436 | } 437 | 438 | proxyClient.Close() 439 | up.Close() 440 | 441 | if to1 && to2 { 442 | v.Vprint(host, " unbridged due to timeout") 443 | } else if err1 != nil || err2 != nil { 444 | v.Eprint(host, " unbridged due to error: ", err1, "(down<-up) or ", err2, "(up<-down)") 445 | } 446 | }() 447 | } 448 | 449 | func bridge(dst, src net.Conn, t time.Duration) (err error, timedout bool) { 450 | buf := make([]byte, 1024*64) 451 | for { 452 | if t > 0 { 453 | src.SetReadDeadline(time.Now().Add(t)) 454 | } 455 | nr, er := src.Read(buf) 456 | if nr > 0 { 457 | nw, ew := dst.Write(buf[0:nr]) 458 | if ew != nil { 459 | err = ew 460 | break 461 | } 462 | if nr != nw { 463 | err = io.ErrShortWrite 464 | break 465 | } 466 | } 467 | if er != nil { 468 | if ne, _ := er.(net.Error); ne != nil && ne.Timeout() { 469 | timedout = true 470 | break 471 | } 472 | if er != io.EOF { 473 | err = er 474 | } 475 | break 476 | } 477 | } 478 | return 479 | } 480 | 481 | type TTLMap struct { 482 | m sync.Map 483 | } 484 | 485 | func (m *TTLMap) String() string { 486 | p := bytes.Buffer{} 487 | m.m.Range(func(k, v interface{}) bool { 488 | p.WriteString(k.(string)) 489 | p.WriteString(":") 490 | p.WriteString(fmt.Sprint(v)) 491 | p.WriteString(",") 492 | return true 493 | }) 494 | return p.String() 495 | } 496 | 497 | func (m *TTLMap) Add(key string, value string, ttl time.Duration) { 498 | if ttl == 0 { 499 | m.m.Store(key, value) 500 | } else { 501 | m.m.Store(key, [2]interface{}{value, time.Now().Add(ttl)}) 502 | } 503 | } 504 | 505 | func (m *TTLMap) Delete(key string) { 506 | m.m.Delete(key) 507 | } 508 | 509 | func (m *TTLMap) Get(key string) (string, bool) { 510 | v, ok := m.m.Load(key) 511 | if !ok { 512 | return "", false 513 | } 514 | switch v := v.(type) { 515 | case string: 516 | return v, true 517 | case [2]interface{}: 518 | if time.Now().After(v[1].(time.Time)) { 519 | m.m.Delete(key) 520 | return "", false 521 | } 522 | return v[0].(string), true 523 | default: 524 | panic("shouldn't happen") 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /cmd/mitm-relay/index.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import httplib 4 | from urlparse import urlparse 5 | 6 | urlkey = 'HTTP_X_FORWARDED_URL' 7 | 8 | def valid(key): 9 | return key.startswith('HTTP_') and not key.startswith('HTTP_BAE') and key not in [urlkey, 'HTTP_X_HOST'] 10 | 11 | class WSGIGofwRelayApplication(object): 12 | def handler(self, environ, start_response): 13 | """goflyway mitm relay proxy in python 14 | """ 15 | 16 | try: 17 | url = urlparse(environ[urlkey]) 18 | connection = httplib.HTTPConnection(url.netloc) 19 | path = url.geturl().replace('%s://%s' % (url.scheme, url.netloc), '') 20 | except Exception: 21 | start_response('400 Bad Request', [('Content-Type', 'text/html')]) 22 | yield str(environ) 23 | return 24 | 25 | body = None 26 | try: 27 | length = int(environ['CONTENT_LENGTH']) 28 | except (KeyError, ValueError): 29 | pass 30 | else: 31 | body = environ['wsgi.input'].read(length) 32 | 33 | headers = dict((key[5:].lower().replace('_', '-'), value) for key, value in environ.items() if valid(key)) 34 | headers['host'] = url.netloc 35 | if 'CONTENT_TYPE' in environ: 36 | headers['content-type'] = environ['CONTENT_TYPE'] 37 | 38 | # start_response('200 OK', [('Content-Type', 'text/html')]) 39 | # yield str(headers) 40 | # return 41 | 42 | try: 43 | connection.request(environ['REQUEST_METHOD'], path, body=body, headers=headers) 44 | except Exception as e: 45 | start_response('500 Internal Server Error', [('Content-Type', 'text/html')]) 46 | yield str(e) 47 | return 48 | 49 | response = connection.getresponse() 50 | start_response('{0.status} {0.reason}'.format(response), response.getheaders()) 51 | 52 | while True: 53 | chunk = response.read(4096) 54 | if chunk: 55 | yield chunk 56 | else: 57 | break 58 | 59 | def __call__(self, environ, start_response): 60 | return self.handler(environ, start_response) 61 | 62 | application = WSGIGofwRelayApplication() 63 | -------------------------------------------------------------------------------- /fd/fd.go: -------------------------------------------------------------------------------- 1 | package fd 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "syscall" 7 | ) 8 | 9 | func getNetFD(conn net.Conn) reflect.Value { 10 | return reflect.ValueOf(conn).Elem().Field(0).Field(0).Elem() 11 | } 12 | 13 | func toSockaddr(addr *net.TCPAddr) syscall.Sockaddr { 14 | if len(addr.IP) == net.IPv4len { 15 | sa := &syscall.SockaddrInet4{Port: addr.Port} 16 | copy(sa.Addr[:], addr.IP.To4()) 17 | return sa 18 | } 19 | 20 | sa := &syscall.SockaddrInet6{Port: addr.Port} 21 | copy(sa.Addr[:], addr.IP.To16()) 22 | return sa 23 | } 24 | -------------------------------------------------------------------------------- /fd/fd_go19.go: -------------------------------------------------------------------------------- 1 | //+build go1.9 2 | 3 | package fd 4 | 5 | import ( 6 | "net" 7 | "unsafe" 8 | ) 9 | 10 | func ConnFD(conn net.Conn) (fd int) { 11 | return int(getNetFD(conn).Field(0).FieldByName("Sysfd").Int()) 12 | } 13 | 14 | func SetConnFD(conn net.Conn, fd int) { 15 | addr := getNetFD(conn).Field(0).FieldByName("Sysfd").UnsafeAddr() 16 | *(*int)(unsafe.Pointer(addr)) = fd 17 | } 18 | -------------------------------------------------------------------------------- /fd/fd_non_go19.go: -------------------------------------------------------------------------------- 1 | //+build !go1.9 2 | 3 | package fd 4 | 5 | import ( 6 | "net" 7 | "unsafe" 8 | ) 9 | 10 | func ConnFD(conn net.Conn) (fd int) { 11 | return int(getNetFD(conn).FieldByName("sysfd").Int()) 12 | } 13 | 14 | func SetConnFD(conn net.Conn, fd int) { 15 | addr := getNetFD(conn).FieldByName("sysfd").UnsafeAddr() 16 | *(*int)(unsafe.Pointer(addr)) = fd 17 | } 18 | -------------------------------------------------------------------------------- /fd/fd_unix.go: -------------------------------------------------------------------------------- 1 | // +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris 2 | 3 | package fd 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | func Socket(family int) (int, error) { 11 | fd, err := syscall.Socket(family, syscall.SOCK_STREAM, 0) 12 | if err != nil { 13 | return 0, err 14 | } 15 | 16 | if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { 17 | return 0, err 18 | } 19 | 20 | return fd, nil 21 | } 22 | 23 | func DialWithFD(sock int, address string) (net.Conn, error) { 24 | conn, err := net.Dial("tcp", address) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | if err := syscall.Connect(sock, toSockaddr(conn.RemoteAddr().(*net.TCPAddr))); err != nil { 30 | return nil, err 31 | } 32 | 33 | oldfd := ConnFD(conn) 34 | SetConnFD(conn, sock) 35 | syscall.Close(oldfd) 36 | 37 | return conn, err 38 | } 39 | -------------------------------------------------------------------------------- /fd/fd_windows.go: -------------------------------------------------------------------------------- 1 | package fd 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | ) 7 | 8 | func Socket(family int) (syscall.Handle, error) { 9 | return syscall.Handle(0), nil 10 | } 11 | 12 | func DialWithFD(sock syscall.Handle, address string) (net.Conn, error) { 13 | return net.Dial("tcp", address) 14 | } 15 | -------------------------------------------------------------------------------- /io.go: -------------------------------------------------------------------------------- 1 | package goflyway 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "sync/atomic" 7 | 8 | . "github.com/coyove/goflyway/v" 9 | ) 10 | 11 | func Bridge(target, source net.Conn, timeout *TokenBucket, stat *Traffic) { 12 | go func() { 13 | if err := ioCopy(target, source, timeout, stat.Sent()); err != nil { 14 | Eprint("bridge: ", err) 15 | } 16 | target.Close() 17 | source.Close() 18 | }() 19 | 20 | if err := ioCopy(source, target, timeout, stat.Recv()); err != nil { 21 | Eprint("bridge: ", err) 22 | } 23 | 24 | // Multiple closes, but for tohConn they are just fine 25 | target.Close() 26 | source.Close() 27 | } 28 | 29 | func ioCopy(dst io.WriteCloser, src io.ReadCloser, bk *TokenBucket, bytes *int64) (err error) { 30 | buf := make([]byte, 32*1024) 31 | 32 | for { 33 | nr, er := src.Read(buf) 34 | 35 | if nr > 0 { 36 | if bk != nil { 37 | bk.Consume(int64(nr)) 38 | } 39 | 40 | nw, ew := dst.Write(buf[0:nr]) 41 | 42 | if nw > 0 && bytes != nil { 43 | atomic.AddInt64(bytes, int64(nw)) 44 | } 45 | 46 | if ew != nil { 47 | if !isClosedConnErr(ew) && !isTimeoutErr(ew) { 48 | err = ew 49 | } 50 | break 51 | } 52 | 53 | if nr != nw { 54 | err = io.ErrShortWrite 55 | break 56 | } 57 | } 58 | 59 | if er != nil { 60 | if er != io.EOF && !isClosedConnErr(er) && !isTimeoutErr(er) { 61 | err = er 62 | } 63 | break 64 | } 65 | } 66 | 67 | return err 68 | } 69 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # goflyway v2 - a local port forwarder built on HTTP 2 | 3 | ![](https://raw.githubusercontent.com/coyove/goflyway/gdev/.misc/logo.png) 4 | 5 | `master` is the active develop branch and containing v2 code, for the stable v1 release (though it was once called v2.0), please refer to [v1.0 branch](https://github.com/coyove/goflyway/tree/v1.0). 6 | 7 | goflyway v2 is a special tool to forward local ports to a remote server securly, just like `ssh -L`. 8 | 9 | goflyway uses pure HTTP POST requests to relay TCP connections. There is no CONNECT involved nor needed because goflyway is designed mainly for those people who are behind a CONNECT-less HTTP proxy or want to accelerate connections through static CDNs. 10 | 11 | However pure HTTP requesting is definitely a waste of bandwidth if you already have a better network environment, so use `-w` to turn on WebSocket relay, or `-K` to turn on KCP relay if possible. 12 | 13 | ## Usage 14 | Forward `localhost:1080` to `server:1080` through `server:80` 15 | 16 | ``` 17 | Server: ./goflyway :80 18 | Client: ./goflyway -L 1080::1080 server:80 -p password 19 | ``` 20 | 21 | Forward `localhost:1080` to `server2:1080` through `server:80` using WebSocket 22 | 23 | ``` 24 | Server: ./goflyway :80 25 | Client: ./goflyway -w -L 1080:server2:1080 server:80 -p password 26 | ``` 27 | 28 | Dynamically forward `localhost:1080` to `server:80` 29 | 30 | ``` 31 | Server: ./goflyway :80 32 | Client: ./goflyway -D 1080 server:80 -p password 33 | ``` 34 | 35 | HTTP reverse proxy or static file server on the same port: 36 | 37 | ``` 38 | ./goflyway :80 -P http://127.0.0.1:8080 39 | ./goflyway :80 -P /var/www/html 40 | ``` 41 | 42 | ## Write Buffer 43 | 44 | In HTTP mode when server received some data it can't just send them to the client directly because HTTP is not bi-directional, instead the server must wait until the client requests them, which means these data will be stored in memory for some time. 45 | 46 | You can use `-W bytes` to limit the maximum bytes a server can buffer (for each connection), by default it is 1048576 (1M). If the buffer reaches the limit, the following bytes will be blocked until the buffer has free space for them. 47 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package goflyway 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "net/http" 7 | "net/http/httputil" 8 | "net/url" 9 | "strings" 10 | "time" 11 | 12 | "github.com/coyove/goflyway/toh" 13 | . "github.com/coyove/goflyway/v" 14 | ) 15 | 16 | type commonConfig struct { 17 | WriteBuffer int64 18 | Key string 19 | Timeout time.Duration 20 | Stat *Traffic 21 | } 22 | 23 | func (config *commonConfig) check() { 24 | if config.Timeout == 0 { 25 | config.Timeout = time.Second * 15 26 | } 27 | if config.WriteBuffer == 0 { 28 | config.WriteBuffer = 1024 * 1024 // 1M 29 | } 30 | } 31 | 32 | type ServerConfig struct { 33 | commonConfig 34 | ProxyPassAddr string 35 | SpeedThrot *TokenBucket 36 | } 37 | 38 | func NewServer(listen string, config *ServerConfig) error { 39 | config.check() 40 | 41 | rp := append([]toh.Option{}, toh.WithMaxWriteBuffer(int(config.WriteBuffer))) 42 | 43 | if config.ProxyPassAddr != "" { 44 | if strings.HasPrefix(config.ProxyPassAddr, "http") { 45 | u, err := url.Parse(config.ProxyPassAddr) 46 | if err != nil { 47 | return err 48 | } 49 | rp = append(rp, toh.WithBadRequest(httputil.NewSingleHostReverseProxy(u).ServeHTTP)) 50 | } else { 51 | rp = append(rp, toh.WithBadRequest(http.FileServer(http.Dir(config.ProxyPassAddr)).ServeHTTP)) 52 | } 53 | } 54 | 55 | listener, err := toh.Listen(config.Key, listen, rp...) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | for { 61 | conn, err := listener.Accept() 62 | if err != nil { 63 | return err 64 | } 65 | 66 | go func(conn net.Conn) { 67 | down := toh.NewBufConn(conn) 68 | defer down.Close() 69 | 70 | buf, err := down.ReadBytes('\n') 71 | if err != nil || len(buf) < 2 { 72 | Vprint(err) 73 | return 74 | } 75 | 76 | host := string(bytes.TrimRight(buf, "\n")) 77 | 78 | dialstart := time.Now() 79 | up, err := net.DialTimeout("tcp", host, config.Timeout) 80 | if err != nil { 81 | Vprint(host, err) 82 | down.Write([]byte(err.Error() + "\n")) 83 | return 84 | } 85 | 86 | Vprint("dial ", host, " in ", time.Since(dialstart).Nanoseconds()/1e6, "ms") 87 | defer up.Close() 88 | 89 | down.Write([]byte("OK\n")) 90 | Bridge(up, down, config.SpeedThrot, config.Stat) 91 | }(conn) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ssvpn_interop.go: -------------------------------------------------------------------------------- 1 | // +build darwin dragonfly freebsd linux netbsd openbsd solaris 2 | 3 | package goflyway 4 | 5 | import ( 6 | "encoding/binary" 7 | "errors" 8 | "net" 9 | "strconv" 10 | "syscall" 11 | "unsafe" 12 | 13 | "github.com/coyove/goflyway/fd" 14 | ) 15 | 16 | func vpnDial(address string) (net.Conn, error) { 17 | var family int 18 | family = syscall.AF_INET 19 | 20 | if address[0] == '[' { 21 | // naive match 22 | family = syscall.AF_INET6 23 | } 24 | 25 | sock, err := fd.Socket(family) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | // Send our conn fd to shadowsocks vpn thread for protection 31 | if err := protectFD(sock); err != nil { 32 | return nil, err 33 | } 34 | 35 | // If succeeded, this fd will be closed while we still need it. 36 | // So we dial a new conn, replace its fd with this one 37 | return fd.DialWithFD(sock, address) 38 | } 39 | 40 | func protectFD(fd int) error { 41 | sock, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | var addr syscall.SockaddrUnix 47 | addr.Name = "protect_path" 48 | 49 | if err := (syscall.Connect(sock, &addr)); err != nil { 50 | return err 51 | } 52 | 53 | if err := sendFD(sock, fd); err != nil { 54 | return err 55 | } 56 | 57 | ret := []byte{9} 58 | if n, err := syscall.Read(sock, ret); err != nil { 59 | return err 60 | } else if n != 1 { 61 | return errors.New("protecting failed") 62 | } 63 | 64 | syscall.Close(sock) 65 | 66 | if ret[0] != 0 { 67 | return errors.New("protecting failed") 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func sendTrafficStats(recv, send int64) error { 74 | const errm = "sending traffic stats failed" 75 | 76 | sock, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | var addr syscall.SockaddrUnix 82 | addr.Name = "stat_path" 83 | 84 | if err := (syscall.Connect(sock, &addr)); err != nil { 85 | return err 86 | } 87 | 88 | payload := make([]byte, 16) 89 | binary.LittleEndian.PutUint64(payload, uint64(send)) 90 | binary.LittleEndian.PutUint64(payload[8:], uint64(recv)) 91 | 92 | if n, err := syscall.Write(sock, payload); err != nil { 93 | return err 94 | } else if n != 16 { 95 | return errors.New(errm) 96 | } 97 | 98 | ret := []byte{9} 99 | if n, err := syscall.Read(sock, ret); err != nil { 100 | return err 101 | } else if n != 1 { 102 | return errors.New(errm) 103 | } 104 | 105 | syscall.Close(sock) 106 | 107 | if ret[0] != 0 { 108 | return errors.New(errm) 109 | } 110 | 111 | return nil 112 | } 113 | 114 | var _int_value_one int = 1 115 | var _little_endian = *(*byte)(unsafe.Pointer(&_int_value_one)) == 1 116 | 117 | func sendFD(sock int, fd int) error { 118 | cmsg := &syscall.Cmsghdr{ 119 | Level: syscall.SOL_SOCKET, 120 | Type: syscall.SCM_RIGHTS, 121 | } 122 | 123 | const hdrsize = syscall.SizeofCmsghdr 124 | ln := byte(hdrsize + strconv.IntSize/8) 125 | h := (*[8]byte)(unsafe.Pointer(&cmsg.Len)) 126 | 127 | if _little_endian { 128 | h[0] = ln 129 | } else { 130 | h[3+4*(^cmsg.Len<<32>>63)] = ln 131 | } 132 | 133 | buffer := make([]byte, cmsg.Len) 134 | 135 | copy(buffer, (*[hdrsize]byte)(unsafe.Pointer(cmsg))[:]) 136 | *(*int)(unsafe.Pointer(&buffer[hdrsize])) = fd 137 | 138 | return syscall.Sendmsg(sock, []byte{'!'}, buffer, nil, 0) 139 | } 140 | -------------------------------------------------------------------------------- /ssvpn_windows.go: -------------------------------------------------------------------------------- 1 | package goflyway 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func vpnDial(address string) (net.Conn, error) { 8 | panic("not on Windows") 9 | } 10 | 11 | func sendTrafficStats(recv, send int64) error { 12 | return nil 13 | } 14 | -------------------------------------------------------------------------------- /toh/client_conn.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "sync/atomic" 15 | "time" 16 | 17 | "github.com/coyove/common/sched" 18 | "github.com/coyove/goflyway/v" 19 | ) 20 | 21 | type ClientConn struct { 22 | idx uint64 23 | dialer *Dialer 24 | 25 | write struct { 26 | sync.Mutex 27 | counter uint32 28 | sched sched.SchedKey 29 | buf []byte 30 | survey struct { 31 | lastIsPositive bool 32 | pendingSize int 33 | reschedCount int64 34 | } 35 | respCh chan io.ReadCloser 36 | respChOnce sync.Once 37 | } 38 | 39 | read *readConn 40 | } 41 | 42 | func (d *Dialer) Dial() (net.Conn, error) { 43 | if d.WebSocket { 44 | return d.wsHandshake() 45 | } 46 | return d.newClientConn() 47 | } 48 | 49 | func (d *Dialer) newClientConn() (net.Conn, error) { 50 | 51 | c := &ClientConn{dialer: d} 52 | c.idx = newConnectionIdx() 53 | c.write.survey.pendingSize = 1 54 | c.write.respCh = make(chan io.ReadCloser, 128) 55 | c.read = newReadConn(c.idx, d.blk, 'c') 56 | 57 | // Say hello 58 | resp, err := c.send(frame{ 59 | idx: rand.Uint32(), 60 | connIdx: c.idx, 61 | options: optSyncConnIdx, 62 | next: &frame{ 63 | connIdx: c.idx, 64 | options: optHello, 65 | }}) 66 | if err != nil { 67 | return nil, err 68 | } 69 | resp.Body.Close() 70 | 71 | c.write.sched = sched.Schedule(c.schedSending, time.Second) 72 | 73 | go c.respLoop() 74 | return c, nil 75 | } 76 | 77 | func (c *ClientConn) SetDeadline(t time.Time) error { 78 | c.SetReadDeadline(t) 79 | return nil 80 | } 81 | 82 | func (c *ClientConn) SetReadDeadline(t time.Time) error { 83 | c.read.ready.SetWaitDeadline(t) 84 | return nil 85 | } 86 | 87 | func (c *ClientConn) SetWriteDeadline(t time.Time) error { 88 | return nil 89 | } 90 | 91 | func (c *ClientConn) LocalAddr() net.Addr { 92 | return &net.TCPAddr{} 93 | } 94 | 95 | func (c *ClientConn) RemoteAddr() net.Addr { 96 | return &net.TCPAddr{} 97 | } 98 | 99 | func (c *ClientConn) Close() error { 100 | if c.read.closed { 101 | return nil 102 | } 103 | 104 | v.VVprint(c, " closing") 105 | c.write.sched.Cancel() 106 | c.read.close() 107 | c.write.respChOnce.Do(func() { 108 | close(c.write.respCh) 109 | go c.send(frame{ 110 | connIdx: c.idx, 111 | options: optClosed, 112 | }) 113 | }) 114 | return nil 115 | } 116 | 117 | func (c *ClientConn) Write(p []byte) (n int, err error) { 118 | REWRITE: 119 | if c.read.err != nil { 120 | return 0, c.read.err 121 | } 122 | 123 | if c.read.closed { 124 | return 0, errClosedConn 125 | } 126 | 127 | if len(c.write.buf) > c.dialer.MaxWriteBuffer { 128 | v.Eprint(c, " write buffer is full") 129 | time.Sleep(time.Second) 130 | goto REWRITE 131 | } 132 | 133 | c.write.Lock() 134 | c.write.sched.Reschedule(func() { 135 | c.write.survey.pendingSize = 1 136 | c.schedSending() 137 | }, time.Second) 138 | c.write.buf = append(c.write.buf, p...) 139 | c.write.Unlock() 140 | 141 | if len(c.write.buf) < c.write.survey.pendingSize { 142 | return len(p), nil 143 | } 144 | 145 | c.schedSending() 146 | return len(p), nil 147 | } 148 | 149 | func (c *ClientConn) schedSending() { 150 | atomic.AddInt64(&c.write.survey.reschedCount, 1) 151 | 152 | if c.read.err != nil || c.read.closed { 153 | c.Close() 154 | return 155 | } 156 | 157 | c.dialer.orchSendWriteBuf(c) 158 | c.write.sched.Reschedule(func() { 159 | c.write.survey.pendingSize = 1 160 | c.schedSending() 161 | }, time.Second) 162 | } 163 | 164 | func (c *ClientConn) sendWriteBuf() { 165 | c.write.Lock() 166 | defer c.write.Unlock() 167 | 168 | if c.write.survey.pendingSize *= 2; c.write.survey.pendingSize > 1024 { 169 | c.write.survey.pendingSize = 1024 170 | } 171 | 172 | if c.read.err != nil { 173 | return 174 | } 175 | 176 | if c.read.closed { 177 | return 178 | } 179 | 180 | f := frame{ 181 | idx: rand.Uint32(), 182 | connIdx: c.idx, 183 | options: optSyncConnIdx, 184 | next: &frame{ 185 | idx: c.write.counter + 1, 186 | connIdx: c.idx, 187 | data: c.write.buf, 188 | }, 189 | } 190 | 191 | deadline := time.Now().Add(c.dialer.Timeout - time.Second) 192 | for { 193 | if resp, err := c.send(f); err != nil { 194 | if time.Now().After(deadline) { 195 | c.read.feedError(err) 196 | return 197 | } 198 | } else { 199 | c.write.buf = c.write.buf[:0] 200 | c.write.counter++ 201 | func() { 202 | defer func() { recover() }() 203 | select { 204 | case c.write.respCh <- resp.Body: 205 | default: 206 | go func(resp *http.Response) { 207 | c.read.feedframes(resp.Body) 208 | resp.Body.Close() 209 | }(resp) 210 | } 211 | }() 212 | break 213 | } 214 | } 215 | } 216 | 217 | func (c *ClientConn) send(f frame) (resp *http.Response, err error) { 218 | client := &http.Client{ 219 | Timeout: c.dialer.Timeout, 220 | Transport: c.dialer.Transport, 221 | } 222 | 223 | body := f.marshal(c.read.blk) 224 | path := "http://" + c.dialer.endpoint + c.dialer.Path() 225 | req, _ := http.NewRequest("POST", path, bytes.NewReader(body)) 226 | 227 | if parts := strings.Split(c.dialer.URLHeader, "="); len(parts) == 2 { 228 | v.Vprint(parts) 229 | req.Header.Add(parts[0], parts[1]) 230 | } 231 | 232 | if len(body) > 1024*4 { 233 | // log of sending big payload 234 | v.VVVprint(c, " heavy sending ", float64(len(body))/1024, "K") 235 | } 236 | 237 | resp, err = client.Do(req) 238 | if err != nil { 239 | return nil, err 240 | } 241 | if resp.StatusCode != http.StatusOK { 242 | xx, _ := ioutil.ReadAll(resp.Body) 243 | resp.Body.Close() 244 | return nil, fmt.Errorf("remote is unavailable: %s, resp: %v", resp.Status, strconv.Quote(string(xx))) 245 | } 246 | return resp, nil 247 | } 248 | 249 | func (c *ClientConn) respLoop() { 250 | for body := range c.write.respCh { 251 | k := sched.Schedule(func() { body.Close() }, c.dialer.Timeout) 252 | if n, _ := c.read.feedframes(body); n == 0 { 253 | c.write.survey.lastIsPositive = false 254 | } 255 | k.Cancel() 256 | body.Close() 257 | } 258 | v.VVprint(c, " resp out") 259 | } 260 | 261 | func (c *ClientConn) Read(p []byte) (n int, err error) { 262 | return c.read.Read(p) 263 | } 264 | 265 | func (c *ClientConn) String() string { 266 | return fmt.Sprintf("", formatConnIdx(c.idx), c.read.counter, c.write.counter) 267 | } 268 | -------------------------------------------------------------------------------- /toh/conn_test.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "runtime/pprof" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | "testing" 18 | "time" 19 | ) 20 | 21 | var debugFlag = flag.Bool("debug", false, "") 22 | 23 | func init() { 24 | flag.Parse() 25 | debug = *debugFlag 26 | } 27 | 28 | func TestClientConn(t *testing.T) { 29 | go func() { 30 | ln, _ := Listen("tcp", "127.0.0.1:13739") 31 | conn, _ := ln.Accept() 32 | p := [1]byte{} 33 | conn.Read(p[:]) 34 | fmt.Println(p) 35 | p[0] = 2 36 | conn.Write(p[:]) 37 | }() 38 | 39 | conn, _ := NewDialer("tcp", "127.0.0.1:13739").Dial() 40 | conn.Write([]byte{1}) 41 | p := [1]byte{} 42 | conn.Read(p[:]) 43 | fmt.Println(p) 44 | 45 | select {} 46 | } 47 | 48 | func TestReadDeadline(t *testing.T) { 49 | go func() { 50 | ln, _ := Listen("tcp", "127.0.0.1:13739") 51 | conn, _ := ln.Accept() 52 | time.Sleep(time.Second * 2) 53 | conn.Write([]byte{1}) 54 | }() 55 | 56 | conn, _ := NewDialer("tcp", "127.0.0.1:13739").Dial() 57 | p := [1]byte{} 58 | conn.SetReadDeadline(time.Now().Add(time.Second)) 59 | _, err := conn.Read(p[:]) 60 | fmt.Println(p, err) 61 | 62 | time.Sleep(time.Second * 2) 63 | _, err = conn.Read(p[:]) 64 | fmt.Println(p, err) 65 | 66 | conn.SetReadDeadline(time.Time{}) 67 | conn.Read(p[:]) 68 | fmt.Println(p) 69 | 70 | select {} 71 | } 72 | 73 | func TestHTTPServer(t *testing.T) { 74 | ready := make(chan bool) 75 | var ln net.Listener 76 | 77 | go func() { 78 | ln, _ = Listen("tcp", "127.0.0.1:13739") 79 | //ln.(*Listener).InactivePurge = 10 * time.Second 80 | 81 | ready <- true 82 | mux := http.NewServeMux() 83 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 84 | w.Write([]byte(r.RequestURI[1:])) 85 | }) 86 | http.Serve(ln, mux) 87 | }() 88 | 89 | num := 1 90 | dd := NewDialer("tcp", "127.0.0.1:13739") 91 | client := http.Client{ 92 | Transport: &http.Transport{ 93 | Dial: func(network, addr string) (net.Conn, error) { 94 | return dd.Dial() 95 | }, 96 | MaxConnsPerHost: 10, 97 | }, 98 | } 99 | 100 | test := func(wg *sync.WaitGroup) { 101 | str := strconv.FormatUint(rand.Uint64(), 10) 102 | resp, err := client.Get("http://127.0.0.1:13739/" + str) 103 | 104 | if err != nil { 105 | panic(err.(*url.Error).Err) 106 | } 107 | 108 | buf, _ := ioutil.ReadAll(resp.Body) 109 | if string(buf) != str { 110 | panic(string(buf)) 111 | } 112 | 113 | resp.Body.Close() 114 | wg.Done() 115 | } 116 | 117 | select { 118 | case <-ready: 119 | } 120 | 121 | go func() { 122 | for { 123 | time.Sleep(2 * time.Second) 124 | f, _ := os.Create("heap.txt") 125 | pprof.Lookup("goroutine").WriteTo(f, 1) 126 | //fmt.Println("profile") 127 | } 128 | }() 129 | 130 | //debug = true 131 | start := time.Now() 132 | count := 0 133 | for { 134 | wg := &sync.WaitGroup{} 135 | if time.Now().Sub(start).Seconds() > 190 { // < 10 min, so we won't get killed by the go tester 136 | break 137 | } 138 | 139 | for i := 0; i < num*100; i++ { 140 | wg.Add(1) 141 | go test(wg) 142 | } 143 | wg.Wait() 144 | 145 | if count++; count%1 == 0 { 146 | log.Println(strings.Repeat("=", 20), count) 147 | } 148 | //logg.D(p.Count()) 149 | } 150 | 151 | ln.Close() 152 | } 153 | -------------------------------------------------------------------------------- /toh/dnshelper/dns.go: -------------------------------------------------------------------------------- 1 | package dnshelper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/coyove/common/lru" 14 | "github.com/miekg/dns" 15 | ) 16 | 17 | var ( 18 | DefaultResolver string 19 | defaultResolvermu sync.Mutex 20 | DNSCache = lru.NewCache(1024) 21 | ) 22 | 23 | func LookupIPv4(host string, local bool) (net.IP, error) { 24 | if local { 25 | return dnsNonRecursiveQueryIPv4(host) 26 | } 27 | if c, ok := DNSCache.Get(host); ok { 28 | return c.(net.IP), nil 29 | } 30 | ips, err := net.LookupIP(host) 31 | if err != nil { 32 | return net.IPv4zero, err 33 | } 34 | for _, ip := range ips { 35 | ip4 := ip.To4() 36 | if ip4 != nil { 37 | DNSCache.Add(host, ip4) 38 | return ip4, nil 39 | } 40 | } 41 | return net.IPv4zero, fmt.Errorf("empty answer") 42 | } 43 | 44 | func dnsNonRecursiveQueryIPv4(host string) (net.IP, error) { 45 | if idx := strings.LastIndex(host, ":"); idx > -1 { 46 | host = host[:idx] 47 | } 48 | if !strings.HasSuffix(host, ".") { 49 | host += "." 50 | } 51 | if c, ok := DNSCache.Get(host); ok { 52 | return c.(net.IP), nil 53 | } 54 | 55 | c := &dns.Client{Timeout: time.Millisecond * 100} 56 | m := &dns.Msg{} 57 | m.Id = dns.Id() 58 | m.RecursionDesired = false 59 | m.Question = []dns.Question{dns.Question{host, dns.TypeA, dns.ClassINET}} 60 | 61 | // For now we use some hacks to get system's default resolver address 62 | for i := 0; i < 4 && DefaultResolver == ""; i++ { 63 | defaultResolvermu.Lock() 64 | if DefaultResolver == "" { 65 | resolv := &net.Resolver{ 66 | PreferGo: true, 67 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 68 | DefaultResolver = address 69 | return nil, fmt.Errorf("abort") 70 | }, 71 | } 72 | resolv.LookupIPAddr(context.TODO(), strconv.Itoa(int(rand.Uint64()))+".com") 73 | } 74 | defaultResolvermu.Unlock() 75 | } 76 | 77 | if DefaultResolver == "" { 78 | return net.IPv4zero, fmt.Errorf("failed to get default resolver") 79 | } 80 | 81 | in, _, err := c.Exchange(m, DefaultResolver) 82 | if err != nil { 83 | return net.IPv4zero, err 84 | } 85 | 86 | for _, ans := range in.Answer { 87 | switch a := ans.(type) { 88 | case *dns.A: 89 | DNSCache.Add(host, a.A) 90 | return a.A, nil 91 | case *dns.CNAME: 92 | return dnsNonRecursiveQueryIPv4(a.Target) 93 | default: 94 | return net.IPv4zero, fmt.Errorf("unknown answer: %v", ans) 95 | } 96 | } 97 | 98 | return net.IPv4zero, fmt.Errorf("empty answer") 99 | } 100 | -------------------------------------------------------------------------------- /toh/dnshelper/dns_test.go: -------------------------------------------------------------------------------- 1 | package dnshelper 2 | 3 | import "testing" 4 | 5 | func TestDNS(t *testing.T) { 6 | t.Log(LookupIPv4("google.com", true)) 7 | t.Log(LookupIPv4("google.com", false)) 8 | } 9 | -------------------------------------------------------------------------------- /toh/frame.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "bytes" 5 | "crypto/cipher" 6 | "encoding/binary" 7 | "fmt" 8 | "hash/crc32" 9 | "io" 10 | "time" 11 | 12 | "github.com/coyove/common/sched" 13 | "github.com/coyove/goflyway/v" 14 | ) 15 | 16 | const ( 17 | optSyncConnIdx = 1 << iota 18 | optHello 19 | optPing 20 | optClosed 21 | ) 22 | 23 | type frame struct { 24 | connIdx uint64 25 | idx uint32 26 | options byte 27 | future bool 28 | data []byte 29 | next *frame 30 | } 31 | 32 | // connection id 8b | data idx 4b | data length 4b | hash 3b | option 1b 33 | func (f *frame) marshal(blk cipher.Block) []byte { 34 | buf := [20]byte{} 35 | binary.BigEndian.PutUint32(buf[:4], f.idx) 36 | binary.BigEndian.PutUint64(buf[4:], f.connIdx) 37 | 38 | gcm, _ := cipher.NewGCM(blk) 39 | x := gcm.Seal(f.data[:0], buf[:12], f.data, nil) 40 | binary.LittleEndian.PutUint32(buf[12:], uint32(len(x))) 41 | buf[16] = f.options 42 | 43 | h := crc32.Checksum(buf[:17], crc32.IEEETable) 44 | buf[17], buf[18], buf[19] = byte(h), byte(h>>8), byte(h>>16) 45 | 46 | blk.Encrypt(buf[:], buf[:]) 47 | blk.Encrypt(buf[4:], buf[4:]) 48 | 49 | p := new(bytes.Buffer) 50 | p.Write(buf[:]) 51 | p.Write(x) 52 | 53 | if f.next != nil { 54 | p.Write(f.next.marshal(blk)) 55 | } 56 | 57 | return p.Bytes() 58 | } 59 | 60 | func parseframe(r io.ReadCloser, blk cipher.Block) (f frame, ok bool) { 61 | k := sched.Schedule(func() { 62 | v.VVprint("[ParseFrame] waiting too long") 63 | go r.Close() 64 | }, time.Minute) 65 | defer k.Cancel() 66 | 67 | header := [20]byte{} 68 | if n, err := io.ReadAtLeast(r, header[:], len(header)); err != nil || n != len(header) { 69 | if err == io.EOF { 70 | ok = true 71 | } else { 72 | v.Eprint(err) 73 | } 74 | return 75 | } 76 | 77 | blk.Decrypt(header[4:], header[4:]) 78 | blk.Decrypt(header[:], header[:]) 79 | 80 | h := crc32.Checksum(header[:17], crc32.IEEETable) 81 | if header[17] != byte(h) || header[18] != byte(h>>8) || header[19] != byte(h>>16) { 82 | v.Vprint(header) 83 | return 84 | } 85 | 86 | datalen := int(binary.LittleEndian.Uint32(header[12:])) 87 | data := make([]byte, datalen) 88 | if n, err := io.ReadAtLeast(r, data, datalen); err != nil || n != datalen { 89 | v.Eprint(err) 90 | return 91 | } 92 | 93 | gcm, err := cipher.NewGCM(blk) 94 | data, err = gcm.Open(nil, header[:12], data, nil) 95 | if err != nil { 96 | v.Eprint(err) 97 | return 98 | } 99 | 100 | f.idx = binary.BigEndian.Uint32(header[:4]) 101 | f.connIdx = binary.BigEndian.Uint64(header[4:]) 102 | f.data = data 103 | f.options = header[16] 104 | return f, true 105 | } 106 | 107 | func (f frame) String() string { 108 | return fmt.Sprintf("", f.idx, formatConnIdx(f.connIdx), f.options, len(f.data)) 109 | } 110 | -------------------------------------------------------------------------------- /toh/frame_test.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "io/ioutil" 7 | "math/rand" 8 | "testing" 9 | ) 10 | 11 | func TestFrame(t *testing.T) { 12 | key := make([]byte, 16) 13 | for i := range key { 14 | key[i] = byte(rand.Uint64()) 15 | } 16 | 17 | blk, _ := aes.NewCipher(key) 18 | 19 | data := make([]byte, 128) 20 | rand.Read(data) 21 | 22 | ff := func() *frame { 23 | f := &frame{ 24 | idx: rand.Uint32(), 25 | connIdx: rand.Uint64(), 26 | data: make([]byte, rand.Intn(len(data))), 27 | } 28 | if rand.Intn(2) == 0 { 29 | f.data = nil 30 | } else { 31 | copy(f.data, data[:len(f.data)]) 32 | } 33 | return f 34 | } 35 | 36 | for i := 0; i < 1e4; i++ { 37 | f := ff() 38 | root := f 39 | 40 | for j := 0; j < 3; j++ { 41 | if rand.Intn(3-j) == 0 { 42 | break 43 | } 44 | f.next = ff() 45 | f = f.next 46 | } 47 | 48 | r := ioutil.NopCloser(root.marshal(blk)) 49 | 50 | for { 51 | f2, ok := parseframe(r, blk) 52 | if !ok || f2.idx == 0 { 53 | break 54 | } 55 | 56 | if root.idx != f2.idx { 57 | t.Fatal(root, f2) 58 | } 59 | 60 | if !bytes.Equal(root.data, f2.data) { 61 | t.Fatal(root.data, f2.data) 62 | } 63 | 64 | root = root.next 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /toh/listendial.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "fmt" 7 | "math/rand" 8 | "net" 9 | "net/http" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | "github.com/coyove/goflyway/v" 16 | ) 17 | 18 | type Listener struct { 19 | ln net.Listener 20 | closed bool 21 | conns map[uint64]*ServerConn 22 | connsmu sync.Mutex 23 | httpServeErr chan error 24 | pendingConns chan net.Conn 25 | blk cipher.Block 26 | 27 | OnBadRequest http.HandlerFunc 28 | CommonOptions 29 | } 30 | 31 | func (l *Listener) Close() error { 32 | select { 33 | case l.httpServeErr <- fmt.Errorf("accept on closed listener"): 34 | } 35 | l.closed = true 36 | return l.ln.Close() 37 | } 38 | 39 | func (l *Listener) Addr() net.Addr { 40 | return l.ln.Addr() 41 | } 42 | 43 | func (l *Listener) Accept() (net.Conn, error) { 44 | for { 45 | select { 46 | case err := <-l.httpServeErr: 47 | return nil, err 48 | case conn := <-l.pendingConns: 49 | return conn, nil 50 | } 51 | } 52 | } 53 | 54 | func Listen(network string, address string, options ...Option) (net.Listener, error) { 55 | ln, err := net.Listen("tcp", address) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | l := &Listener{ 61 | ln: ln, 62 | httpServeErr: make(chan error, 1), 63 | pendingConns: make(chan net.Conn, 1024), 64 | conns: map[uint64]*ServerConn{}, 65 | } 66 | 67 | for _, o := range options { 68 | o(nil, l) 69 | } 70 | 71 | l.check() 72 | 73 | l.blk, _ = aes.NewCipher([]byte(network + "0123456789abcdef")[:16]) 74 | 75 | go func() { 76 | mux := http.NewServeMux() 77 | mux.HandleFunc("/", l.handler) 78 | l.httpServeErr <- http.Serve(ln, mux) 79 | }() 80 | 81 | if v.Verbose > 0 { 82 | go func() { 83 | for range time.Tick(time.Second * 5) { 84 | ln := 0 85 | l.connsmu.Lock() 86 | for _, conn := range l.conns { 87 | ln += len(conn.write.buf) 88 | //v.Vprint(conn, len(conn.write.buf)) 89 | } 90 | l.connsmu.Unlock() 91 | v.VVprint("listener active connections: ", len(l.conns), ", pending bytes: ", ln) 92 | } 93 | }() 94 | } 95 | 96 | return l, nil 97 | } 98 | 99 | type Dialer struct { 100 | endpoint string 101 | orch chan *ClientConn 102 | blk cipher.Block 103 | 104 | Transport http.RoundTripper 105 | WebSocket bool 106 | URLHeader string 107 | PathPattern string 108 | CommonOptions 109 | } 110 | 111 | func NewDialer(network string, endpoint string, options ...Option) *Dialer { 112 | d := &Dialer{ 113 | endpoint: endpoint, 114 | orch: make(chan *ClientConn, 128), 115 | } 116 | d.blk, _ = aes.NewCipher([]byte(network + "0123456789abcdef")[:16]) 117 | 118 | for _, o := range options { 119 | o(d, nil) 120 | } 121 | 122 | if d.Transport == nil { 123 | d.Transport = http.DefaultTransport 124 | } 125 | if !d.WebSocket { 126 | d.startOrch() 127 | } 128 | if !strings.HasPrefix(d.PathPattern, "/") { 129 | d.PathPattern = "/" 130 | } 131 | d.check() 132 | 133 | return d 134 | } 135 | 136 | func (d *Dialer) Path() string { 137 | r := strconv.FormatUint(rand.Uint64(), 10) 138 | if strings.HasSuffix(d.PathPattern, "/") { 139 | return d.PathPattern + r 140 | } 141 | return d.PathPattern + "/" + r 142 | } 143 | -------------------------------------------------------------------------------- /toh/option.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | type CommonOptions struct { 10 | MaxWriteBuffer int 11 | Timeout time.Duration 12 | } 13 | 14 | func (d *CommonOptions) check() { 15 | if d.Timeout == 0 { 16 | d.Timeout = time.Second * 15 17 | } 18 | if d.MaxWriteBuffer == 0 { 19 | d.MaxWriteBuffer = 1024 * 1024 20 | } 21 | } 22 | 23 | type Option func(d *Dialer, ln *Listener) 24 | 25 | var ( 26 | WithTransport = func(tr http.RoundTripper) Option { 27 | return Option(func(d *Dialer, ln *Listener) { 28 | if d != nil { 29 | d.Transport = tr 30 | } 31 | }) 32 | } 33 | WithInactiveTimeout = func(t time.Duration) Option { 34 | return Option(func(d *Dialer, ln *Listener) { 35 | if d != nil { 36 | d.Timeout = t 37 | } 38 | if ln != nil { 39 | ln.Timeout = t 40 | } 41 | }) 42 | } 43 | WithWebSocket = func(ws bool) Option { 44 | return Option(func(d *Dialer, ln *Listener) { 45 | if d != nil { 46 | d.WebSocket = ws 47 | } 48 | }) 49 | } 50 | WithMaxWriteBuffer = func(size int) Option { 51 | return Option(func(d *Dialer, ln *Listener) { 52 | if d != nil { 53 | d.MaxWriteBuffer = size 54 | } 55 | if ln != nil { 56 | ln.MaxWriteBuffer = size 57 | } 58 | }) 59 | } 60 | WithHeader = func(hdr string) Option { 61 | return Option(func(d *Dialer, ln *Listener) { 62 | if d != nil { 63 | d.URLHeader = hdr 64 | } 65 | }) 66 | } 67 | WithPathPattern = func(pattern string) Option { 68 | return Option(func(d *Dialer, ln *Listener) { 69 | if d != nil { 70 | d.PathPattern = pattern 71 | } 72 | }) 73 | } 74 | WithBadRequest = func(callback http.HandlerFunc) Option { 75 | return Option(func(d *Dialer, ln *Listener) { 76 | if ln != nil { 77 | ln.OnBadRequest = callback 78 | } 79 | }) 80 | } 81 | WithBadRequestRoundTripper = func(rt http.RoundTripper) Option { 82 | return Option(func(d *Dialer, ln *Listener) { 83 | if ln != nil { 84 | ln.OnBadRequest = func(w http.ResponseWriter, r *http.Request) { 85 | resp, err := rt.RoundTrip(r) 86 | if err != nil { 87 | w.WriteHeader(http.StatusServiceUnavailable) 88 | w.Write([]byte(err.Error())) 89 | return 90 | } 91 | 92 | defer resp.Body.Close() 93 | w.WriteHeader(resp.StatusCode) 94 | 95 | for k := range w.Header() { 96 | w.Header().Del(k) 97 | } 98 | 99 | for k, v := range resp.Header { 100 | hdr := w.Header() 101 | for _, v := range v { 102 | hdr.Add(k, v) 103 | } 104 | } 105 | io.Copy(w, resp.Body) 106 | } 107 | } 108 | }) 109 | } 110 | ) 111 | -------------------------------------------------------------------------------- /toh/orch.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math/rand" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/coyove/common/sched" 11 | "github.com/coyove/goflyway/v" 12 | ) 13 | 14 | func init() { 15 | rand.Seed(time.Now().UnixNano()) 16 | } 17 | 18 | func (d *Dialer) startOrch() { 19 | sched.Verbose = false 20 | 21 | var ( 22 | directs int // number of requests with valid payload 23 | pings int // number of requests with no payload (ping) 24 | positives uint64 // number of positive pings (server said it had valid data for this ClientConn to read) 25 | loopcount int // number of orch loops 26 | ) 27 | 28 | go func() { 29 | for { 30 | conns := make(map[uint64]*ClientConn) 31 | loopcount++ 32 | 33 | READ: 34 | for { 35 | select { 36 | case c := <-d.orch: 37 | conns[c.idx] = c 38 | case <-time.After((time.Millisecond) * 50): 39 | break READ 40 | } 41 | } 42 | 43 | if loopcount%20 == 0 || positives > 0 { 44 | v.VVprint("orch pings: ", pings, "(+", positives, "), directs: ", directs) 45 | directs, pings, positives = 0, 0, 0 46 | } 47 | 48 | if len(conns) == 0 { 49 | time.Sleep(200 * time.Millisecond) 50 | continue 51 | } 52 | 53 | var p bytes.Buffer 54 | var lastconn *ClientConn 55 | 56 | for k, conn := range conns { 57 | if len(conn.write.buf) > 0 || conn.write.survey.lastIsPositive { 58 | // For connections with actual data waiting to be sent, send them directly 59 | go conn.sendWriteBuf() 60 | delete(conns, k) 61 | directs++ 62 | continue 63 | } 64 | 65 | binary.Write(&p, binary.BigEndian, conn.idx) 66 | lastconn = conn 67 | } 68 | 69 | if len(conns) <= 3 { 70 | for _, conn := range conns { 71 | directs++ 72 | go conn.sendWriteBuf() 73 | } 74 | lastconn = nil 75 | } 76 | 77 | if lastconn == nil { 78 | // vprint("batch ping: 0, direct: ", count) 79 | continue 80 | } 81 | 82 | pingframe := frame{options: optPing, data: p.Bytes()} 83 | pings += p.Len() / 8 84 | 85 | go func(pingframe frame, lastconn *ClientConn, conns map[uint64]*ClientConn) { 86 | resp, err := lastconn.send(pingframe) 87 | if err != nil { 88 | v.Eprint("send error: ", err) 89 | return 90 | } 91 | defer resp.Body.Close() 92 | 93 | f, ok := parseframe(resp.Body, lastconn.read.blk) 94 | if !ok || f.options != optPing { 95 | return 96 | } 97 | 98 | for i := 0; i < len(f.data); i += 10 { 99 | connState := binary.BigEndian.Uint16(f.data[i:]) 100 | connIdx := binary.BigEndian.Uint64(f.data[i+2:]) 101 | 102 | if c := conns[connIdx]; c != nil && !c.read.closed && c.read.err == nil { 103 | switch connState { 104 | case PING_CLOSED: 105 | v.VVprint(c, " server side has closed") 106 | c.read.feedError(errClosedConn) 107 | c.Close() 108 | case PING_OK_VOID: 109 | c.write.survey.lastIsPositive = false 110 | case PING_OK: 111 | atomic.AddUint64(&positives, 1) 112 | c.write.survey.lastIsPositive = true 113 | go c.sendWriteBuf() 114 | } 115 | } 116 | } 117 | 118 | resp.Body.Close() 119 | }(pingframe, lastconn, conns) 120 | } 121 | }() 122 | } 123 | 124 | func (d *Dialer) orchSendWriteBuf(c *ClientConn) { 125 | select { 126 | case d.orch <- c: 127 | default: 128 | go c.sendWriteBuf() 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /toh/proxy_test.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | "os" 11 | "runtime/pprof" 12 | "strings" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | func iocopy(dst io.Writer, src io.Reader) (written int64, err error) { 18 | size := 32 * 1024 19 | buf := make([]byte, size) 20 | for { 21 | nr, er := src.Read(buf) 22 | if nr > 0 { 23 | nw, ew := dst.Write(buf[0:nr]) 24 | if nw > 0 { 25 | written += int64(nw) 26 | } 27 | if ew != nil { 28 | err = ew 29 | break 30 | } 31 | if nr != nw { 32 | err = io.ErrShortWrite 33 | break 34 | } 35 | } 36 | if er != nil { 37 | if er != io.EOF { 38 | err = er 39 | } 40 | break 41 | } 42 | } 43 | return written, err 44 | } 45 | 46 | func bridge(a, b io.ReadWriteCloser) { 47 | go func() { iocopy(a, b); a.Close(); b.Close() }() 48 | go func() { iocopy(b, a); a.Close(); b.Close() }() 49 | } 50 | 51 | type client int 52 | 53 | type server int 54 | 55 | var dd *Dialer 56 | 57 | func (s *client) ServeHTTP(w http.ResponseWriter, r *http.Request) { 58 | host := r.Host 59 | if !strings.Contains(host, ":") { 60 | host += ":80" 61 | } 62 | 63 | up, err := dd.Dial() 64 | if err != nil { 65 | log.Println(err) 66 | return 67 | } 68 | up.Write([]byte(r.Method[:1] + host + "\n")) 69 | 70 | down, _, _ := w.(http.Hijacker).Hijack() 71 | if r.Method != "CONNECT" { 72 | header, _ := httputil.DumpRequestOut(r, false) 73 | x := string(header) 74 | up.Write([]byte(x)) 75 | io.Copy(up, r.Body) 76 | } 77 | 78 | bridge(down, up) 79 | } 80 | 81 | func foo(conn net.Conn) { 82 | down := NewBufConn(conn) 83 | buf, err := down.ReadBytes('\n') 84 | if err != nil || len(buf) < 2 { 85 | conn.Close() 86 | return 87 | } 88 | 89 | host := string(buf) 90 | connect := host[0] == 'C' 91 | host = host[1 : len(host)-1] 92 | // vprint(host, connect) 93 | 94 | up, _ := net.Dial("tcp", host) 95 | 96 | if up == nil || down == nil { 97 | down.Write([]byte("HTTP/1.1 503 Service Unavailable\r\n\r\n")) 98 | return 99 | } 100 | 101 | if connect { 102 | down.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")) 103 | } 104 | 105 | bridge(down, up) 106 | } 107 | 108 | func TestProxy(t *testing.T) { 109 | go func() { 110 | for { 111 | time.Sleep(2 * time.Second) 112 | f, _ := os.Create("heap.txt") 113 | pprof.Lookup("goroutine").WriteTo(f, 1) 114 | f.Close() 115 | } 116 | }() 117 | 118 | //p, _ := url.Parse("68.183.156.72:8080") 119 | //DefaultTransport.Proxy = http.ProxyURL(p) 120 | 121 | go func() { 122 | log.Println("hello") 123 | up, ws := os.Getenv("UP"), os.Getenv("WS") == "1" 124 | 125 | if up == "" { 126 | up = ":10001" 127 | } 128 | 129 | tr := *http.DefaultTransport.(*http.Transport) 130 | tr.MaxConnsPerHost = 100 131 | 132 | u, _ := url.Parse("http://example.com") 133 | 134 | dd = NewDialer("tcp", up, 135 | WithTransport(&tr), 136 | WithInactiveTimeout(time.Second*10), 137 | WithWebSocket(ws), 138 | WithPath("/aaa")) 139 | 140 | go http.ListenAndServe(":10000", new(client)) 141 | 142 | ln, _ := Listen("tcp", ":10001", 143 | WithInactiveTimeout(time.Second*10), 144 | WithPath("/aaa"), 145 | WithBadRequest(httputil.NewSingleHostReverseProxy(u).ServeHTTP)) 146 | for { 147 | conn, _ := ln.Accept() 148 | go foo(conn) 149 | } 150 | }() 151 | 152 | // Verbose = false 153 | select {} 154 | } 155 | -------------------------------------------------------------------------------- /toh/read_conn.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "crypto/cipher" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/coyove/common/waitobject" 14 | "github.com/coyove/goflyway/v" 15 | ) 16 | 17 | var ( 18 | errClosedConn = fmt.Errorf("use of closed connection") 19 | dummyTouch = func(interface{}) interface{} { return 1 } 20 | ) 21 | 22 | // Define the max pending bytes stored in memory, any further bytes will be written to disk 23 | var MaxReadBufferSize = 1024 * 1024 * 1 24 | 25 | type readConn struct { 26 | sync.Mutex 27 | idx uint64 // readConn index, should be the same as the one in ClientConn/SerevrConn 28 | buf []byte // read buffer 29 | frames chan frame // incoming frames 30 | futureframes map[uint32]frame // future frames, which have arrived early 31 | futureSize int // total size of future frames 32 | ready *waitobject.Object // it being touched means that data in "buf" are ready 33 | err error // stored error, if presented, all operations afterwards should return it 34 | blk cipher.Block // cipher block, aes-128 35 | closed bool // is readConn closed already 36 | tag byte // tag, 'c' for readConn in ClientConn, 's' for readConn in ServerConn 37 | counter uint32 // counter, must be synced with the writer on the other side 38 | } 39 | 40 | func newReadConn(idx uint64, blk cipher.Block, tag byte) *readConn { 41 | r := &readConn{ 42 | frames: make(chan frame, 1024), 43 | futureframes: map[uint32]frame{}, 44 | idx: idx, 45 | tag: tag, 46 | blk: blk, 47 | ready: waitobject.New(), 48 | } 49 | go r.readLoopRearrange() 50 | return r 51 | } 52 | 53 | func (c *readConn) feedframes(r io.ReadCloser) (datalen int, err error) { 54 | count := 0 55 | for { 56 | f, ok := parseframe(r, c.blk) 57 | if !ok { 58 | err = fmt.Errorf("invalid frames") 59 | c.feedError(err) 60 | return 0, err 61 | } 62 | if f.idx == 0 { 63 | break 64 | } 65 | if c.closed { 66 | return 0, errClosedConn 67 | } 68 | if c.err != nil { 69 | return 0, c.err 70 | } 71 | 72 | // v.Vprint("feed: ", f.data) 73 | 74 | if !c.feedframe(f) { 75 | return 0, errClosedConn 76 | } 77 | count += len(f.data) 78 | } 79 | return count, nil 80 | } 81 | 82 | func (c *readConn) feedframe(f frame) (ok bool) { 83 | defer func() { 84 | if r := recover(); r != nil { 85 | // Dirty way to avoid closed channel panic 86 | if strings.Contains(fmt.Sprintf("%v", r), "send on close") { 87 | ok = false 88 | } else { 89 | panic(r) 90 | } 91 | } 92 | }() 93 | c.frames <- f 94 | return true 95 | } 96 | 97 | func (c *readConn) feedError(err error) { 98 | c.err = err 99 | c.ready.Touch(dummyTouch) 100 | c.close() 101 | } 102 | 103 | func (c *readConn) close() { 104 | c.Lock() 105 | defer c.Unlock() 106 | if c.closed { 107 | return 108 | } 109 | c.closed = true 110 | close(c.frames) 111 | c.ready.SetWaitDeadline(time.Now()) 112 | } 113 | 114 | func (c *readConn) readLoopRearrange() { 115 | LOOP: 116 | select { 117 | // case <-time.After(time.Second * 10): 118 | // v.Vprint("timeout") 119 | case f, ok := <-c.frames: 120 | if !ok { 121 | return 122 | } 123 | 124 | c.Lock() 125 | if f.connIdx != c.idx { 126 | c.Unlock() 127 | c.feedError(fmt.Errorf("fatal: unmatched stream index")) 128 | return 129 | } 130 | 131 | if f.idx <= c.counter { 132 | c.Unlock() 133 | //c.feedError(fmt.Errorf("unmatched counter, maybe server GCed the connection")) 134 | return 135 | } 136 | 137 | c.futureframes[f.idx] = f 138 | c.futureSize += len(f.data) 139 | for { 140 | idx := c.counter + 1 141 | if f, ok := c.futureframes[idx]; ok { 142 | if f.future { 143 | buf, err := ioutil.ReadFile(frameTmpPath(c.idx, f.idx)) 144 | if err != nil { 145 | c.Unlock() 146 | c.feedError(fmt.Errorf("fatal: missing certain frame from disk")) 147 | return 148 | } 149 | os.Remove(frameTmpPath(c.idx, f.idx)) 150 | f.data = buf 151 | v.Vprint(c, " back load frame: ", f) 152 | } 153 | 154 | c.buf = append(c.buf, f.data...) 155 | c.counter = f.idx 156 | delete(c.futureframes, f.idx) 157 | c.futureSize -= len(f.data) 158 | continue 159 | } 160 | 161 | if c.futureSize > MaxReadBufferSize { 162 | if ioutil.WriteFile(frameTmpPath(c.idx, f.idx), f.data, 0755) != nil { 163 | c.Unlock() 164 | c.feedError(fmt.Errorf("fatal: missing certain frame")) 165 | return 166 | } 167 | 168 | v.Vprint(c, " tmp save frame: ", f) 169 | c.futureframes[f.idx] = frame{future: true, idx: f.idx} 170 | } 171 | break 172 | } 173 | if c.counter == 0xffffffff { 174 | panic("surprise!") 175 | } 176 | c.Unlock() 177 | c.ready.Touch(dummyTouch) 178 | } 179 | goto LOOP 180 | } 181 | 182 | func (c *readConn) Read(p []byte) (n int, err error) { 183 | READ: 184 | if c.closed { 185 | return 0, errClosedConn 186 | } 187 | 188 | if c.err != nil { 189 | return 0, c.err 190 | } 191 | 192 | if c.ready.IsTimedout() { 193 | return 0, &timeoutError{} 194 | } 195 | 196 | c.Lock() 197 | if len(c.buf) > 0 { 198 | n = copy(p, c.buf) 199 | c.buf = c.buf[n:] 200 | c.Unlock() 201 | return 202 | } 203 | c.Unlock() 204 | 205 | _, ontime := c.ready.Wait() 206 | 207 | if c.closed { 208 | return 0, errClosedConn 209 | } 210 | 211 | if !ontime { 212 | return 0, &timeoutError{} 213 | } 214 | 215 | goto READ 216 | } 217 | 218 | func (c *readConn) String() string { 219 | return fmt.Sprintf("<%s,ctr:%d>", string(c.tag), c.counter) 220 | } 221 | -------------------------------------------------------------------------------- /toh/server_conn.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | "github.com/coyove/common/sched" 16 | "github.com/coyove/goflyway/v" 17 | ) 18 | 19 | const ( 20 | PING_OK uint16 = iota + 1 21 | PING_CLOSED 22 | PING_OK_VOID 23 | ) 24 | 25 | type ServerConn struct { 26 | idx uint64 27 | rev *Listener 28 | schedPurge sched.SchedKey 29 | 30 | write struct { 31 | sync.Mutex 32 | buf []byte 33 | counter uint32 34 | } 35 | 36 | read *readConn 37 | } 38 | 39 | func newServerConn(idx uint64, ln *Listener) *ServerConn { 40 | c := &ServerConn{idx: idx} 41 | c.rev = ln 42 | c.read = newReadConn(c.idx, ln.blk, 's') 43 | return c 44 | } 45 | 46 | func (l *Listener) randomReply(w http.ResponseWriter, r *http.Request) { 47 | v.Vprint("listener random reply: ", r) 48 | 49 | r.Header.Del("Content-Length") 50 | 51 | if l.OnBadRequest != nil { 52 | l.OnBadRequest(w, r) 53 | return 54 | } 55 | 56 | p := [256]byte{} 57 | for { 58 | if rand.Intn(8) == 0 { 59 | break 60 | } 61 | rand.Read(p[:]) 62 | w.Write(p[:rand.Intn(128)+128]) 63 | } 64 | } 65 | 66 | func (l *Listener) handler(w http.ResponseWriter, r *http.Request) { 67 | if strings.ToLower(r.Header.Get("Sec-WebSocket-Key")) != "" { 68 | conn, err := l.wsHandShake(w, r) 69 | if err != nil { 70 | w.WriteHeader(http.StatusBadRequest) 71 | v.Eprint("websocket handshake error: ", err) 72 | } else { 73 | l.pendingConns <- conn 74 | } 75 | return 76 | } 77 | 78 | hdr, ok := parseframe(r.Body, l.blk) 79 | if !ok { 80 | l.randomReply(w, r) 81 | return 82 | } 83 | 84 | switch hdr.options { 85 | case optSyncConnIdx: 86 | case optClosed: 87 | l.connsmu.Lock() 88 | c := l.conns[hdr.connIdx] 89 | l.connsmu.Unlock() 90 | if c != nil { 91 | v.Vprint(c, " received close ping, client side has closed") 92 | c.Close() 93 | } 94 | case optPing: 95 | l.connsmu.Lock() 96 | p := bytes.Buffer{} 97 | for i := 0; i < len(hdr.data); i += 8 { 98 | connIdx := binary.BigEndian.Uint64(hdr.data[i : i+8]) 99 | 100 | if c := l.conns[connIdx]; c != nil && c.read.err == nil && !c.read.closed { 101 | if len(c.write.buf) > 0 { 102 | binary.Write(&p, binary.BigEndian, PING_OK) 103 | } else { 104 | binary.Write(&p, binary.BigEndian, PING_OK_VOID) 105 | } 106 | c.reschedDeath() 107 | } else { 108 | binary.Write(&p, binary.BigEndian, PING_CLOSED) 109 | } 110 | 111 | binary.Write(&p, binary.BigEndian, connIdx) 112 | } 113 | l.connsmu.Unlock() 114 | 115 | f := frame{options: optPing, data: p.Bytes()} 116 | w.Write(f.marshal(l.blk)) 117 | return 118 | default: 119 | l.randomReply(w, r) 120 | return 121 | } 122 | connIdx := hdr.connIdx 123 | 124 | var conn *ServerConn 125 | l.connsmu.Lock() 126 | if sc, _ := l.conns[connIdx]; sc != nil { 127 | conn = sc 128 | l.connsmu.Unlock() 129 | } else { 130 | // New incoming connection? 131 | f, ok := parseframe(r.Body, l.blk) 132 | if !ok || f.options&optHello == 0 || f.connIdx != connIdx { 133 | if !ok { 134 | l.randomReply(w, r) 135 | } else { 136 | // TODO: tell client the conn has gone 137 | } 138 | l.connsmu.Unlock() 139 | return 140 | } 141 | 142 | conn = newServerConn(connIdx, l) 143 | l.conns[connIdx] = conn 144 | l.connsmu.Unlock() 145 | 146 | l.pendingConns <- conn 147 | v.Vprint("accpet new conn: ", conn) 148 | conn.reschedDeath() 149 | //conn.writeTo(w) 150 | return 151 | } 152 | 153 | if datalen, err := conn.read.feedframes(r.Body); err != nil { 154 | v.Eprint("listener feed frames, error: ", err, ", ", conn, " will be deleted") 155 | conn.Close() 156 | return 157 | } else if datalen == 0 && len(conn.write.buf) == 0 { 158 | // Client sent nothing, we treat the request as a ping 159 | // However too many pings without: 160 | // 1) sending any valid data to us 161 | // 2) we sending any valid data to them 162 | // are meaningless 163 | // So we won't reschedule its deadline: it will die as expected 164 | } else { 165 | conn.reschedDeath() 166 | } 167 | 168 | conn.writeTo(w) 169 | } 170 | 171 | func (conn *ServerConn) reschedDeath() { 172 | conn.schedPurge.Reschedule(func() { 173 | v.VVVprint(conn, " will die as scheduled") 174 | conn.Close() 175 | }, conn.rev.Timeout) 176 | } 177 | 178 | func (conn *ServerConn) writeTo(w io.Writer) { 179 | 180 | for i := 0; ; i++ { 181 | conn.write.Lock() 182 | if len(conn.write.buf) == 0 { 183 | conn.write.Unlock() 184 | if i == 0 { 185 | time.Sleep(200 * time.Millisecond) 186 | continue 187 | } 188 | return 189 | } 190 | 191 | f := &frame{ 192 | idx: conn.write.counter + 1, 193 | connIdx: conn.idx, 194 | data: make([]byte, len(conn.write.buf)), 195 | } 196 | 197 | copy(f.data, conn.write.buf) 198 | conn.write.buf = conn.write.buf[:0] 199 | conn.write.counter++ 200 | conn.write.Unlock() 201 | 202 | deadline := time.Now().Add(conn.rev.Timeout - time.Second) 203 | AGAIN: 204 | if _, err := w.Write(f.marshal(conn.read.blk)); err != nil { 205 | if time.Now().Before(deadline) { 206 | goto AGAIN 207 | } 208 | v.Eprint(conn, " failed to response, error: ", err) 209 | conn.read.feedError(err) 210 | conn.Close() 211 | return 212 | } 213 | } 214 | } 215 | 216 | func (c *ServerConn) SetReadDeadline(t time.Time) error { 217 | c.read.ready.SetWaitDeadline(t) 218 | return nil 219 | } 220 | 221 | func (c *ServerConn) SetDeadline(t time.Time) error { 222 | c.SetReadDeadline(t) 223 | return nil 224 | } 225 | 226 | func (c *ServerConn) SetWriteDeadline(t time.Time) error { 227 | return nil 228 | } 229 | 230 | func (c *ServerConn) Write(p []byte) (n int, err error) { 231 | REWRITE: 232 | if c.read.closed { 233 | return 0, errClosedConn 234 | } 235 | 236 | if c.read.err != nil { 237 | return 0, c.read.err 238 | } 239 | 240 | if len(c.write.buf) > c.rev.MaxWriteBuffer { 241 | v.Vprint(c, " write buffer is full") 242 | time.Sleep(time.Second) 243 | goto REWRITE 244 | } 245 | 246 | c.write.Lock() 247 | c.write.buf = append(c.write.buf, p...) 248 | c.write.Unlock() 249 | return len(p), nil 250 | } 251 | 252 | func (c *ServerConn) Read(p []byte) (n int, err error) { 253 | return c.read.Read(p) 254 | } 255 | 256 | func (c *ServerConn) Close() error { 257 | if c.read.closed { 258 | return nil 259 | } 260 | 261 | v.VVprint(c, " closing") 262 | c.schedPurge.Cancel() 263 | c.read.close() 264 | c.rev.connsmu.Lock() 265 | delete(c.rev.conns, c.idx) 266 | c.rev.connsmu.Unlock() 267 | //v.Vprint(c, " delete", c.rev.conns) 268 | return nil 269 | } 270 | 271 | func (c *ServerConn) RemoteAddr() net.Addr { 272 | return &net.TCPAddr{} 273 | } 274 | 275 | func (c *ServerConn) LocalAddr() net.Addr { 276 | return c.rev.Addr() 277 | } 278 | 279 | func (c *ServerConn) String() string { 280 | return fmt.Sprintf("", formatConnIdx(c.idx), c.read.counter, c.write.counter) 281 | } 282 | -------------------------------------------------------------------------------- /toh/util.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "bufio" 5 | "encoding/base32" 6 | "fmt" 7 | "math/rand" 8 | "net" 9 | "os" 10 | "path/filepath" 11 | "sync/atomic" 12 | "time" 13 | "unsafe" 14 | ) 15 | 16 | var ( 17 | debug = false 18 | ) 19 | 20 | type timeoutError struct{} 21 | 22 | func (e *timeoutError) Error() string { 23 | return "operation timed out" 24 | } 25 | 26 | func (e *timeoutError) Timeout() bool { 27 | return true 28 | } 29 | 30 | func (e *timeoutError) Temporary() bool { 31 | return false 32 | } 33 | 34 | type BufConn struct { 35 | net.Conn 36 | *bufio.Reader 37 | } 38 | 39 | func NewBufConn(conn net.Conn) *BufConn { 40 | return &BufConn{Conn: conn, Reader: bufio.NewReader(conn)} 41 | } 42 | 43 | func (c *BufConn) Write(p []byte) (int, error) { 44 | return c.Conn.Write(p) 45 | } 46 | 47 | func (c *BufConn) Read(p []byte) (int, error) { 48 | return c.Reader.Read(p) 49 | } 50 | 51 | var countermark uint32 52 | 53 | func newConnectionIdx() uint64 { 54 | // 25bit timestamp (1 yr) | 16bit counter | 23bit random values 55 | now := uint32(time.Now().Unix()) 56 | c := atomic.AddUint32(&countermark, 1) 57 | return uint64(now)<<39 | uint64(c&0xffff)<<23 | uint64(rand.Uint32()&0x7fffff) 58 | } 59 | 60 | func formatConnIdx(idx uint64) string { 61 | return base32.HexEncoding.EncodeToString((*(*[8]byte)(unsafe.Pointer(&idx)))[1:6]) 62 | } 63 | 64 | func frameTmpPath(connIdx uint64, idx uint32) string { 65 | return filepath.Join(os.TempDir(), fmt.Sprintf("%x-%d.toh", connIdx, idx)) 66 | } 67 | -------------------------------------------------------------------------------- /toh/websocket.go: -------------------------------------------------------------------------------- 1 | package toh 2 | 3 | import ( 4 | "crypto/cipher" 5 | "crypto/sha1" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "encoding/binary" 9 | "fmt" 10 | "io" 11 | "math/rand" 12 | "net" 13 | "net/http" 14 | "net/url" 15 | "sync" 16 | ) 17 | 18 | type WSConn struct { 19 | net.Conn 20 | mu sync.Mutex 21 | blk cipher.Block 22 | mask bool 23 | buf []byte 24 | } 25 | 26 | func (c *WSConn) Write(p []byte) (int, error) { 27 | L := len(p) 28 | 29 | // TODO 30 | key := make([]byte, 12) 31 | rand.Read(key) 32 | 33 | gcm, _ := cipher.NewGCM(c.blk) 34 | 35 | p = gcm.Seal(p[:0], key, p, nil) 36 | p = append(p, key...) 37 | 38 | if _, err := wsWrite(c.Conn, p, c.mask); err != nil { 39 | return 0, err 40 | } 41 | return L, nil 42 | } 43 | 44 | func (c *WSConn) Read(p []byte) (int, error) { 45 | c.mu.Lock() 46 | defer c.mu.Unlock() 47 | 48 | READ: 49 | if len(c.buf) > 0 { 50 | n := copy(p, c.buf) 51 | c.buf = c.buf[n:] 52 | return n, nil 53 | } 54 | 55 | payload, _, err := wsRead(c.Conn) 56 | if err != nil { 57 | return 0, err 58 | } 59 | if len(payload) < 12 { 60 | return 0, fmt.Errorf("invalid websocket payload") 61 | } 62 | 63 | key := payload[len(payload)-12:] 64 | payload = payload[:len(payload)-12] 65 | 66 | gcm, _ := cipher.NewGCM(c.blk) 67 | 68 | payload, err = gcm.Open(payload[:0], key, payload, nil) 69 | if err != nil { 70 | return 0, err 71 | } 72 | 73 | c.buf = payload 74 | goto READ 75 | } 76 | 77 | func (d *Dialer) wsHandshake() (net.Conn, error) { 78 | var ( 79 | host = d.endpoint 80 | conn net.Conn 81 | err error 82 | https bool 83 | ) 84 | 85 | REDIR: 86 | if https { 87 | conn, err = tls.DialWithDialer(&net.Dialer{Timeout: d.Timeout}, "tcp", host, &tls.Config{InsecureSkipVerify: true}) 88 | } else { 89 | conn, err = net.DialTimeout("tcp", host, d.Timeout) 90 | } 91 | 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | wsKey := [20]byte{} 97 | rand.Read(wsKey[:]) 98 | 99 | header := "GET " + d.Path() + " HTTP/1.1\r\n" + 100 | "Host: " + host + "\r\n" + 101 | "Upgrade: websocket\r\n" + 102 | "Connection: Upgrade\r\n" + 103 | "Sec-WebSocket-Key: " + base64.StdEncoding.EncodeToString(wsKey[:]) + "\r\n" + 104 | "Sec-WebSocket-Version: 13\r\n\r\n" 105 | 106 | if _, err := conn.Write([]byte(header)); err != nil { 107 | conn.Close() 108 | return nil, err 109 | } 110 | 111 | c := &WSConn{ 112 | Conn: NewBufConn(conn), 113 | mask: true, 114 | blk: d.blk, 115 | } 116 | 117 | resp, err := http.ReadResponse(c.Conn.(*BufConn).Reader, nil) 118 | if err != nil { 119 | conn.Close() 120 | return nil, err 121 | } 122 | 123 | if resp.StatusCode != http.StatusSwitchingProtocols { 124 | conn.Close() 125 | if host == d.endpoint { 126 | switch resp.StatusCode { 127 | case http.StatusMovedPermanently, http.StatusFound, http.StatusPermanentRedirect, http.StatusTemporaryRedirect: 128 | // TODO 129 | u, _ := url.Parse(resp.Header.Get("Location")) 130 | host, https = u.Host, u.Scheme == "https" 131 | 132 | if _, _, err := net.SplitHostPort(host); err != nil { 133 | if https { 134 | host += ":443" 135 | } else { 136 | host += ":80" 137 | } 138 | } 139 | goto REDIR 140 | } 141 | } 142 | return nil, fmt.Errorf("invalid websocket response: %v", resp.Status) 143 | } 144 | 145 | return c, nil 146 | } 147 | 148 | func (ln *Listener) wsHandShake(w http.ResponseWriter, r *http.Request) (net.Conn, error) { 149 | ans := sha1.Sum([]byte(r.Header.Get("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")) 150 | conn, _, err := w.(http.Hijacker).Hijack() 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | if _, err := conn.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" + 156 | "Upgrade: websocket\r\n" + 157 | "Connection: upgrade\r\n" + 158 | "Sec-WebSocket-Accept: " + base64.StdEncoding.EncodeToString(ans[:]) + "\r\n\r\n")); err != nil { 159 | conn.Close() 160 | return nil, err 161 | } 162 | 163 | return &WSConn{Conn: conn, blk: ln.blk}, nil 164 | } 165 | 166 | // WSWrite and WSRead are simple implementations of RFC6455 167 | // we assume that all payloads are 65535 bytes at max 168 | // we don't care control frames and everything is binary 169 | // we don't close it explicitly, it closes when the TCP connection closes 170 | // we don't ping or pong 171 | // 172 | // 0 1 2 3 173 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 174 | // +-+-+-+-+-------+-+-------------+-------------------------------+ 175 | // |F|R|R|R| opcode|M| Payload len | Extended payload length | 176 | // |I|S|S|S| (4) |A| (7) | (16/64) | 177 | // |N|V|V|V| |S| | (if payload len==126/127) | 178 | // | |1|2|3| |K| | | 179 | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 180 | // | Extended payload length continued, if payload len == 127 | 181 | // + - - - - - - - - - - - - - - - +-------------------------------+ 182 | // | |Masking-key, if MASK set to 1 | 183 | // +-------------------------------+-------------------------------+ 184 | // | Masking-key (continued) | Payload Data | 185 | // +-------------------------------- - - - - - - - - - - - - - - - + 186 | // : Payload Data continued ... : 187 | // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 188 | // | Payload Data continued ... | 189 | // +---------------------------------------------------------------+ 190 | func wsWrite(dst io.Writer, payload []byte, mask bool) (n int, err error) { 191 | if len(payload) > 65535 { 192 | return 0, fmt.Errorf("don't support payload larger than 65535 yet") 193 | } 194 | 195 | buf := make([]byte, 4) 196 | buf[0] = 2 // binary 197 | buf[1] = 126 198 | binary.BigEndian.PutUint16(buf[2:], uint16(len(payload))) 199 | 200 | if mask { 201 | buf[1] |= 0x80 202 | buf = append(buf, 0, 0, 0, 0) 203 | key := rand.Uint32() 204 | binary.BigEndian.PutUint32(buf[4:8], key) 205 | 206 | for i, b := 0, buf[4:8]; i < len(payload); i++ { 207 | payload[i] ^= b[i%4] 208 | } 209 | } 210 | 211 | if n, err = dst.Write(buf); err != nil { 212 | return 213 | } 214 | 215 | return dst.Write(payload) 216 | } 217 | 218 | func wsRead(src io.Reader) (payload []byte, n int, err error) { 219 | buf := make([]byte, 4) 220 | if n, err = io.ReadAtLeast(src, buf[:2], 2); err != nil { 221 | return 222 | } 223 | 224 | if buf[0] != 2 { 225 | err = fmt.Errorf("invalid websocket opcode: %v", buf[0]) 226 | return 227 | } 228 | 229 | mask := (buf[1] & 0x80) > 0 230 | ln := int(buf[1] & 0x7f) 231 | 232 | switch ln { 233 | case 126: 234 | if n, err = io.ReadAtLeast(src, buf[2:4], 2); err != nil { 235 | return 236 | } 237 | ln = int(binary.BigEndian.Uint16(buf[2:4])) 238 | case 127: 239 | err = fmt.Errorf("payload too large") 240 | return 241 | default: 242 | } 243 | 244 | if mask { 245 | if n, err = io.ReadAtLeast(src, buf[:4], 4); err != nil { 246 | return 247 | } 248 | // now buf contains mask key 249 | } 250 | 251 | payload = make([]byte, ln) 252 | if n, err = io.ReadAtLeast(src, payload, ln); err != nil { 253 | return 254 | } 255 | 256 | if mask { 257 | for i, b := 0, buf[:4]; i < len(payload); i++ { 258 | payload[i] ^= b[i%4] 259 | } 260 | } 261 | 262 | // n == ln, err == nil, 263 | return 264 | } 265 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package goflyway 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func isClosedConnErr(err error) bool { 11 | return strings.Contains(err.Error(), "use of closed") 12 | } 13 | 14 | func isTimeoutErr(err error) bool { 15 | if ne, ok := err.(net.Error); ok { 16 | return ne.Timeout() 17 | } 18 | 19 | return false 20 | } 21 | 22 | type TokenBucket struct { 23 | Speed int64 // bytes per second 24 | 25 | capacity int64 // bytes 26 | maxCapacity int64 27 | lastConsume time.Time 28 | 29 | mu sync.Mutex 30 | } 31 | 32 | func NewTokenBucket(speed, max int64) *TokenBucket { 33 | return &TokenBucket{ 34 | Speed: speed, 35 | lastConsume: time.Now(), 36 | maxCapacity: max, 37 | } 38 | } 39 | 40 | func (tb *TokenBucket) Consume(n int64) { 41 | tb.mu.Lock() 42 | defer tb.mu.Unlock() 43 | 44 | now := time.Now() 45 | 46 | if tb.Speed == 0 { 47 | tb.lastConsume = now 48 | return 49 | } 50 | 51 | ms := now.Sub(tb.lastConsume).Nanoseconds() / 1e6 52 | tb.capacity += ms * tb.Speed / 1000 53 | 54 | if tb.capacity > tb.maxCapacity { 55 | tb.capacity = tb.maxCapacity 56 | } 57 | 58 | if n <= tb.capacity { 59 | tb.lastConsume = now 60 | tb.capacity -= n 61 | return 62 | } 63 | 64 | sec := float64(n-tb.capacity) / float64(tb.Speed) 65 | time.Sleep(time.Duration(sec*1000) * time.Millisecond) 66 | 67 | tb.capacity = 0 68 | tb.lastConsume = time.Now() 69 | } 70 | 71 | type Traffic struct { 72 | sent int64 73 | received int64 74 | } 75 | 76 | func (t *Traffic) Set(s, r int64) { 77 | t.sent, t.received = s, r 78 | } 79 | 80 | func (t *Traffic) Sent() *int64 { 81 | if t == nil { 82 | return nil 83 | } 84 | return &t.sent 85 | } 86 | 87 | func (t *Traffic) Recv() *int64 { 88 | if t == nil { 89 | return nil 90 | } 91 | return &t.received 92 | } 93 | -------------------------------------------------------------------------------- /v/errno.go: -------------------------------------------------------------------------------- 1 | package v 2 | 3 | // Refer to https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx 4 | 5 | var WSAErrno = map[int]string{ 6 | 10004: "Interrupted function call", 7 | 10009: "File handle is not valid", 8 | 10013: "Permission denied", 9 | 10014: "Bad address", 10 | 10022: "Invalid argument", 11 | 10024: "Too many open files", 12 | 10035: "Resource temporarily unavailable", 13 | 10036: "Operation now in progress", 14 | 10037: "Operation already in progress", 15 | 10038: "Socket operation on nonsocket", 16 | 10039: "Destination address required", 17 | 10040: "Message too long", 18 | 10041: "Protocol wrong type for socket", 19 | 10042: "Bad protocol option", 20 | 10043: "Protocol not supported", 21 | 10044: "Socket type not supported", 22 | 10045: "Operation not supported", 23 | 10046: "Protocol family not supported", 24 | 10047: "Address family not supported by protocol family", 25 | 10048: "Address already in use", 26 | 10049: "Cannot assign requested address", 27 | 10050: "Network is down", 28 | 10051: "Network is unreachable", 29 | 10052: "Network dropped connection on reset", 30 | 10053: "Software caused connection abort", 31 | 10054: "Connection reset by peer", 32 | 10055: "No buffer space available", 33 | 10056: "Socket is already connected", 34 | 10057: "Socket is not connected", 35 | 10058: "Cannot send after socket shutdown", 36 | 10059: "Too many references", 37 | 10060: "Connection timed out", 38 | 10061: "Connection refused", 39 | 10062: "Cannot translate name", 40 | 10063: "Name too long", 41 | 10064: "Host is down", 42 | 10065: "No route to host", 43 | 10066: "Directory not empty", 44 | 10067: "Too many processes", 45 | 10068: "User quota exceeded", 46 | 10069: "Disk quota exceeded", 47 | 10070: "Stale file handle reference", 48 | 10071: "Item is remote", 49 | 10091: "Network subsystem is unavailable", 50 | 10092: "Winsock.dll version out of range", 51 | 10093: "Successful WSAStartup not yet performed", 52 | 10101: "Graceful shutdown in progress", 53 | 10102: "No more results", 54 | 10103: "Call has been canceled", 55 | 10104: "Procedure call table is invalid", 56 | 10105: "Service provider is invalid", 57 | 10106: "Service provider failed to initialize", 58 | 10107: "System call failure", 59 | 10108: "Service not found", 60 | 10109: "Class type not found", 61 | 10110: "No more results", 62 | 10111: "Call was canceled", 63 | 10112: "Database query was refused", 64 | 11001: "Host not found", 65 | 11002: "Nonauthoritative host not found", 66 | 11003: "This is a nonrecoverable error", 67 | 11004: "Valid name, no data record of requested type", 68 | 11005: "QoS receivers", 69 | 11006: "QoS senders", 70 | 11007: "No QoS senders", 71 | 11008: "QoS no receivers", 72 | 11009: "QoS request confirmed", 73 | 11010: "QoS admission error", 74 | 11011: "QoS policy failure", 75 | 11012: "QoS bad style", 76 | 11013: "QoS bad object", 77 | 11014: "QoS traffic control error", 78 | 11015: "QoS generic error", 79 | 11016: "QoS service type error", 80 | 11017: "QoS flowspec error", 81 | 11018: "Invalid QoS provider buffer", 82 | 11019: "Invalid QoS filter style", 83 | 11020: "Invalid QoS filter type", 84 | 11021: "Incorrect QoS filter count", 85 | 11022: "Invalid QoS object length", 86 | 11023: "Incorrect QoS flow count", 87 | 11024: "Unrecognized QoS object", 88 | 11025: "Invalid QoS policy object", 89 | 11026: "Invalid QoS flow descriptor", 90 | 11027: "Invalid QoS provider-specific flowspec", 91 | 11028: "Invalid QoS provider-specific filterspec", 92 | 11029: "Invalid QoS shape discard mode object", 93 | 11030: "Invalid QoS shaping rate object", 94 | 11031: "Reserved policy QoS element type", 95 | } 96 | -------------------------------------------------------------------------------- /v/vprint.go: -------------------------------------------------------------------------------- 1 | package v 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | var ( 17 | Verbose = 0 18 | Stacktrace = new(int) 19 | FloatPrec = 3 20 | ) 21 | 22 | func Eprint(v ...interface{}) { vprint(0, v...) } 23 | 24 | func Vprint(v ...interface{}) { vprint(1, v...) } 25 | 26 | func VVprint(v ...interface{}) { vprint(2, v...) } 27 | 28 | func VVVprint(v ...interface{}) { vprint(3, v...) } 29 | 30 | func vprint(b int, v ...interface{}) { 31 | if b > Verbose { 32 | return 33 | } 34 | 35 | for i := range v { 36 | if err, _ := v[i].(error); err != nil { 37 | v[i] = tryShortenWSAError(err) 38 | } 39 | if num, ok := v[i].(float64); ok && float64(int64(num)) != num { 40 | v[i] = strconv.FormatFloat(num, 'f', FloatPrec, 64) 41 | } 42 | if num, ok := v[i].(float32); ok && float32(int32(num)) != num { 43 | v[i] = strconv.FormatFloat(float64(num), 'f', FloatPrec, 64) 44 | } 45 | if v[i] == Stacktrace { 46 | p := bytes.Buffer{} 47 | pc := make([]uintptr, 32) 48 | n := runtime.Callers(3, pc) 49 | frame := runtime.CallersFrames(pc[:n]) 50 | 51 | for { 52 | f, ok := frame.Next() 53 | if !ok { 54 | break 55 | } 56 | idx := strings.Index(f.File, "/src/") 57 | if idx == -1 { 58 | break 59 | } 60 | f.File = f.File[idx+5:] 61 | 62 | for i, fn := 0, f.File; i < len(fn) && i < len(f.Function); { 63 | if fn[i] == f.Function[i] { 64 | fn, f.Function = fn[1:], f.Function[1:] 65 | } else { 66 | break 67 | } 68 | } 69 | 70 | p.WriteString(fmt.Sprintf("%s%s:%d---", f.File, f.Function, f.Line)) 71 | } 72 | if p.Len() > 3 { 73 | p.Truncate(p.Len() - 3) 74 | } 75 | v[i] = p.String() 76 | } 77 | } 78 | 79 | var ( 80 | _, fn, line, _ = runtime.Caller(2) 81 | str = strings.TrimRightFunc(fmt.Sprint(v...), trimNewline) 82 | now = time.Now() 83 | lead = "dbg" + strconv.Itoa(b) 84 | out = os.Stdout 85 | ) 86 | 87 | if b == 0 { 88 | lead, out = "error", os.Stderr 89 | } 90 | 91 | fmt.Fprintf(out, lead+" %s%02d %02d:%02d:%02d %s:%-3d ] %s\n", 92 | "123456789OXZ"[now.Month()-1:now.Month()], now.Day(), now.Hour(), now.Minute(), now.Second(), 93 | filepath.Base(fn), line, str) 94 | } 95 | 96 | // Widnows WSA error messages are way too long to print 97 | // ex: An established connection was aborted by the software in your host machine.write tcp 127.0.0.1:8100->127.0.0.1:52466: wsasend: An established connection was aborted by the software in your host machine. 98 | func tryShortenWSAError(err interface{}) (ret string) { 99 | defer func() { 100 | if recover() != nil { 101 | ret = fmt.Sprintf("%v", err) 102 | } 103 | }() 104 | 105 | if e, sysok := err.(*net.OpError).Err.(*os.SyscallError); sysok { 106 | errno := e.Err.(syscall.Errno) 107 | if msg, ok := WSAErrno[int(errno)]; ok { 108 | ret = msg 109 | } else { 110 | // messages on linux are short enough 111 | ret = fmt.Sprintf("C%d, %s", uintptr(errno), e.Error()) 112 | } 113 | 114 | return 115 | } 116 | 117 | ret = err.(*net.OpError).Err.Error() 118 | return 119 | } 120 | 121 | func trimNewline(r rune) bool { 122 | return r == '\r' || r == '\n' 123 | } 124 | --------------------------------------------------------------------------------