├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── app ├── app.go ├── cfg.go ├── graphics.go └── roboto.go ├── cmd ├── android │ ├── AndroidManifest.xml │ └── main.go └── desktop │ └── main.go ├── go.mod ├── go.sum ├── network ├── glider.go ├── hermes.go ├── info.go └── speedtest.go ├── react ├── .prettierrc ├── mv-assets.js ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.js │ ├── api.js │ ├── components │ │ ├── CSVInput.js │ │ ├── Glider.js │ │ ├── Hermes.js │ │ ├── Logs.js │ │ ├── Status.js │ │ └── TextInput.js │ ├── index.css │ ├── index.js │ └── theme.js └── yarn.lock └── web └── web.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | *.temp.* 17 | *.apk 18 | dev.env.bat 19 | node_modules 20 | assets/ 21 | react/build 22 | .eslintcache 23 | *.zip -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Shrivu Shankar 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apollgo 2 | 3 | > An Android app for hosting a variety of proxy servers, proxy chains, and port forwarding (via [glider](https://github.com/nadoo/glider) and [hermes](https://github.com/sshh12/hermes)). 4 | 5 | ![demo](https://user-images.githubusercontent.com/6625384/102667166-5ee12500-414e-11eb-9a0c-4c179186f500.gif) 6 | 7 | ## Install 8 | 9 | ### Android 10 | 11 | > Note: You may be able to download, extract, and install the APK all from the android device without needing a PC to set things up. This is untested. 12 | 13 | 1. Download and extract the [latest release](https://github.com/sshh12/apollgo/releases) on a PC. 14 | 2. Install [adb](https://developer.android.com/studio/command-line/adb) 15 | 3. Run `$ adb install apollgo.apk` with an android device connected 16 | 4. (Optional) While the app is running use `$ adb forward tcp:8888 tcp:8888` and you'll be able to access the app's settings by visiting [http://localhost:8888](http://localhost:8888) 17 | 18 | ## Use Cases 19 | 20 | ### Simple (SOCKS5, HTTP, HTTPS) Proxy Server 21 | 22 | 1. Launch the app and go to [http://localhost:8888](http://localhost:8888) in your device's browser 23 | 2. Go to the `glider` tab 24 | 3. Add a listener on `mixed://:1080`, then **Apply**, and restart the app. 25 | 4. Forward the proxy server to a PC using `$ adb forward tcp:1080 tcp:1080` (ensure device is plugged in via USB) 26 | 5. Update your PC's proxy settings to use `127.0.0.1:1080` for all traffic. 27 | 28 | ### Android Hotspot (Without Using Hotspot Mode 🔮) 29 | 30 | 1. Disable WiFi on your device as well as any auto-enable-WiFi settings 31 | 2. Launch the app and go to [http://localhost:8888](http://localhost:8888) in your device's browser 32 | 3. Go to the `glider` tab 33 | 4. Add `mixed://:1080`, then **Apply**, and restart the app. 34 | 5. Start adb as [`$ adb -a nodaemon server start`](https://stackoverflow.com/questions/56130335/adb-port-forwarding-to-listen-on-all-interfaces) (ensure device is plugged in via USB) 35 | 6. Forward the proxy server to a PC using `$ adb forward tcp:1080 tcp:1080` 36 | 7. Update your PC's proxy settings to use `127.0.0.1:1080` for all traffic. 37 | 8. (Optional) Connect your PC to a router (via WiFi or preferably ethernet) and have other devices use `:1080` (eg `192.168.1.12:1080`) as a proxy server. 38 | 39 | ### Multi-Android Hotspot 40 | 41 | Use several cellular-enabled devices as a single hotspot by load balancing proxy traffic between them. 42 | 43 | 1. Follow steps **1**-**5** from above on every device (every device should be connected via USB to the same PC). 44 | 2. Run `$ adb forward tcp:1080 tcp:1080`, `$ adb forward tcp:2080 tcp:1080`, ... for each device. 45 | 3. Download the latest [glider release](https://github.com/nadoo/glider/releases) (this will have to be done while the PC has an internet connection) 46 | 4. `$ glider -listen mixed://:1180 -forward socks5://127.0.0.1:1080 -forward socks5://127.0.0.1:2080 -checkwebsite www.google.com -checkinterval 300 -strategy rr -verbose` (include a `-forward socks5://127.0.0.1:` for every connected device) 47 | 5. Update your PC's proxy settings to use `127.0.0.1:1180` for all traffic. 48 | 6. (Optional) Connect your PC to a router (via WiFi or preferably ethernet) and have other devices use `:1180` (eg `192.168.1.12:1180`) as a proxy server. 49 | 50 | ### Port Forwarding An IP Camera App 51 | 52 | 1. Setup a [hermes server](https://github.com/sshh12/hermes) on eg DigitalOcean. 53 | 2. Install and start an [IP Camera](https://play.google.com/store/apps/details?id=com.pas.webcam) app, ensure it's running an HTTP webserver by visiting [http://localhost:8080](http://localhost:8080) (or whatever port its on) 54 | 3. Launch the apollgo and go to [http://localhost:8888](http://localhost:8888) in your device's browser 55 | 4. Go to the `glider` tab 56 | 5. Enable hermes and fill in fields to match your hermes server setup. Set forwards to `8080/8080` (forwarding local port 8080 to `:8080`). **Apply** and restart app. 57 | 6. Visit `:8080` anywhere to access the IP Camera stream 58 | 59 | ## Building 60 | 61 | 1. `$ cd react && yarn build:assets` 62 | 2. `$ gomobile build -target=android -o apollgo.apk ./cmd/android` 63 | 64 | ## Alternatives 65 | 66 | If apollgo doesn't work for you, [Every Proxy](https://play.google.com/store/apps/details?id=com.gorillasoftware.everyproxy) is a closed-source alternative that works pretty well for SOCKS/HTTPS proxying. 67 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/sshh12/apollgo/network" 7 | "io/ioutil" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | // ApollgoApp is global app state 13 | type ApollgoApp struct { 14 | cfg *Config 15 | status *Status 16 | cfgFn string 17 | statusLock sync.Mutex 18 | } 19 | 20 | // LogLine is a single log 21 | type LogLine struct { 22 | Text string `json:"text"` 23 | Time int64 `json:"time"` 24 | } 25 | 26 | // Status is app status 27 | type Status struct { 28 | IP string `json:"ip"` 29 | DLSpeed float64 `json:"dlSpeed"` 30 | ULSpeed float64 `json:"ulSpeed"` 31 | Latency float64 `json:"latency"` 32 | Logs []LogLine `json:"logs"` 33 | } 34 | 35 | // NewApollgoApp creates default state 36 | func NewApollgoApp(cfgFn string) *ApollgoApp { 37 | cfg := DefaultCfg 38 | if cfgFile, err := ioutil.ReadFile(cfgFn); err == nil { 39 | var savedCfg Config 40 | if err := json.Unmarshal(cfgFile, &savedCfg); err == nil { 41 | cfg = &savedCfg 42 | } else { 43 | fmt.Println(err.Error()) 44 | } 45 | } 46 | return &ApollgoApp{ 47 | cfgFn: cfgFn, 48 | cfg: cfg, 49 | status: &Status{ 50 | IP: "0.0.0.0", 51 | DLSpeed: 0, 52 | ULSpeed: 0, 53 | Latency: 0, 54 | }, 55 | statusLock: sync.Mutex{}, 56 | } 57 | } 58 | 59 | // Run starts misc tasks 60 | func (s *ApollgoApp) Run() { 61 | ticker := time.NewTicker(2 * time.Hour) 62 | for ; true; <-ticker.C { 63 | if ip, err := network.ExternalIP(); err == nil { 64 | s.status.IP = ip 65 | } else { 66 | s.Log(err.Error()) 67 | } 68 | if dl, up, lat, err := network.RunSpeedTest(); err == nil { 69 | s.status.DLSpeed = dl 70 | s.status.ULSpeed = up 71 | s.status.Latency = lat 72 | } else { 73 | s.Log(err.Error()) 74 | } 75 | } 76 | } 77 | 78 | // Log logs something 79 | func (s *ApollgoApp) Log(val string) { 80 | s.statusLock.Lock() 81 | defer s.statusLock.Unlock() 82 | newLog := LogLine{ 83 | Time: time.Now().Unix(), 84 | Text: val, 85 | } 86 | s.status.Logs = append(s.status.Logs, newLog) 87 | if len(s.status.Logs) > 1000 { 88 | extra := 1000 - len(s.status.Logs) 89 | s.status.Logs = s.status.Logs[extra:] 90 | } 91 | } 92 | 93 | // GetCfg gets config 94 | func (s *ApollgoApp) GetCfg() *Config { 95 | return s.cfg 96 | } 97 | 98 | // SetCfg updates config 99 | func (s *ApollgoApp) SetCfg(newCfg *Config) { 100 | data, err := json.Marshal(newCfg) 101 | if err != nil { 102 | s.Log(err.Error()) 103 | return 104 | } 105 | if err := ioutil.WriteFile(s.cfgFn, data, 0o644); err != nil { 106 | s.Log(err.Error()) 107 | return 108 | } 109 | s.cfg = newCfg 110 | } 111 | 112 | // GetStatus gets status 113 | func (s *ApollgoApp) GetStatus() *Status { 114 | s.statusLock.Lock() 115 | defer s.statusLock.Unlock() 116 | return s.status 117 | } 118 | -------------------------------------------------------------------------------- /app/cfg.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/sshh12/apollgo/network" 4 | 5 | // DefaultCfg default config 6 | var DefaultCfg = &Config{ 7 | ApollgoPort: 8888, 8 | EnableGlider: true, 9 | Listeners: []network.ListenerConfig{ 10 | network.ListenerConfig{ 11 | URIs: []string{"socks5://:3080"}, 12 | Strategy: "rr", 13 | Check: "https://google.com", 14 | CheckInterval: 300, 15 | Forwarders: []string{}, 16 | MaxFailures: 3, 17 | DialTimeout: 3, 18 | RelayTimeout: 0, 19 | IntFace: "", 20 | DNSListener: "", 21 | DNSAlwaysTCP: false, 22 | DNSServers: []string{"8.8.8.8:53"}, 23 | DNSMaxTTL: 1800, 24 | DNSMinTTL: 0, 25 | DNSTimeout: 3, 26 | DNSCacheSize: 4096, 27 | DNSRecords: []string{}, 28 | }, 29 | }, 30 | EnableHermes: false, 31 | HermesConfig: network.HermesConfig{ 32 | Password: "", 33 | HermesPort: 4000, 34 | Server: "127.0.0.1", 35 | ForwardPairs: []string{"8888/80"}, 36 | }, 37 | } 38 | 39 | // Config server config 40 | type Config struct { 41 | Listeners []network.ListenerConfig `json:"listeners"` 42 | ApollgoPort int `json:"apollgoPort"` 43 | EnableGlider bool `json:"enableGlider"` 44 | EnableHermes bool `json:"enableHermes"` 45 | HermesConfig network.HermesConfig `json:"hermesConfig"` 46 | } 47 | -------------------------------------------------------------------------------- /app/graphics.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tfriedel6/canvas" 7 | "github.com/tfriedel6/canvas/backend/xmobilebackend" 8 | "golang.org/x/mobile/app" 9 | "golang.org/x/mobile/event/lifecycle" 10 | "golang.org/x/mobile/event/paint" 11 | "golang.org/x/mobile/event/size" 12 | "golang.org/x/mobile/gl" 13 | ) 14 | 15 | // OnAppLaunch handles app launch and event loop 16 | func OnAppLaunch(app app.App) { 17 | var cv, painter *canvas.Canvas 18 | var cvb *xmobilebackend.XMobileBackendOffscreen 19 | var painterb *xmobilebackend.XMobileBackend 20 | var glctx gl.Context 21 | var width int 22 | var height int 23 | for e := range app.Events() { 24 | switch e := app.Filter(e).(type) { 25 | case lifecycle.Event: 26 | switch e.Crosses(lifecycle.StageVisible) { 27 | case lifecycle.CrossOn: 28 | var err error 29 | glctx = e.DrawContext.(gl.Context) 30 | ctx, err := xmobilebackend.NewGLContext(glctx) 31 | if err != nil { 32 | fmt.Print(err) 33 | return 34 | } 35 | cvb, err = xmobilebackend.NewOffscreen(0, 0, false, ctx) 36 | if err != nil { 37 | fmt.Print(err) 38 | return 39 | } 40 | painterb, err = xmobilebackend.New(0, 0, 0, 0, ctx) 41 | if err != nil { 42 | fmt.Print(err) 43 | return 44 | } 45 | cv = canvas.New(cvb) 46 | cv.LoadFont(robotoFont) 47 | painter = canvas.New(painterb) 48 | app.Send(paint.Event{}) 49 | case lifecycle.CrossOff: 50 | cvb.Delete() 51 | glctx = nil 52 | } 53 | case size.Event: 54 | width, height = e.WidthPx, e.HeightPx 55 | case paint.Event: 56 | if glctx != nil { 57 | fw, fh := float64(width), float64(height) 58 | cvb.SetSize(width, height) 59 | cv.SetFillStyle("#eee") 60 | cv.FillRect(0, 0, fw, fh) 61 | cv.SetFont(nil, 50) 62 | cv.SetFillStyle("#222") 63 | cv.SetTextAlign(canvas.Center) 64 | cv.SetTextBaseline(canvas.Middle) 65 | cv.FillText("view in browser", fw/2, fh/2) 66 | painterb.SetBounds(0, 0, width, height) 67 | painter.DrawImage(cv) 68 | app.Publish() 69 | app.Send(paint.Event{}) 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cmd/android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /cmd/android/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | gliderlog "github.com/nadoo/glider/log" 6 | "github.com/sshh12/apollgo/app" 7 | "github.com/sshh12/apollgo/network" 8 | "github.com/sshh12/apollgo/web" 9 | mobileapp "golang.org/x/mobile/app" 10 | ) 11 | 12 | func main() { 13 | apollgo := app.NewApollgoApp("/sdcard/apollgo.json") 14 | go apollgo.Run() 15 | apollgo.Log("Apollgo started.") 16 | go web.ServeWebApp(apollgo) 17 | gliderlog.F = func(s string, v ...interface{}) { apollgo.Log(fmt.Sprintf(s, v...)) } 18 | initCfg := apollgo.GetCfg() 19 | if initCfg.EnableGlider { 20 | apollgo.Log("Serving glider...") 21 | if err := network.ServeGlider(initCfg.Listeners); err != nil { 22 | apollgo.Log(err.Error()) 23 | } 24 | } else { 25 | apollgo.Log("Glider disabled by settings.") 26 | } 27 | if initCfg.EnableHermes { 28 | apollgo.Log("Serving hermes...") 29 | if err := network.ServeHermes(initCfg.HermesConfig, apollgo.Log); err != nil { 30 | apollgo.Log(err.Error()) 31 | } 32 | } else { 33 | apollgo.Log("Hermes disabled by settings.") 34 | } 35 | apollgo.Log("Launching UI.") 36 | mobileapp.Main(app.OnAppLaunch) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/desktop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | gliderlog "github.com/nadoo/glider/log" 6 | "github.com/sshh12/apollgo/app" 7 | "github.com/sshh12/apollgo/network" 8 | "github.com/sshh12/apollgo/web" 9 | ) 10 | 11 | func main() { 12 | apollgo := app.NewApollgoApp("apollgo.json") 13 | go apollgo.Run() 14 | apollgo.Log("Apollgo started.") 15 | go web.ServeWebApp(apollgo) 16 | gliderlog.F = func(s string, v ...interface{}) { apollgo.Log(fmt.Sprintf(s, v...)) } 17 | initCfg := apollgo.GetCfg() 18 | if initCfg.EnableGlider { 19 | apollgo.Log("Serving glider...") 20 | if err := network.ServeGlider(initCfg.Listeners); err != nil { 21 | apollgo.Log(err.Error()) 22 | } 23 | } else { 24 | apollgo.Log("Glider disabled by settings.") 25 | } 26 | if initCfg.EnableHermes { 27 | apollgo.Log("Serving hermes...") 28 | if err := network.ServeHermes(initCfg.HermesConfig, apollgo.Log); err != nil { 29 | apollgo.Log(err.Error()) 30 | } 31 | } else { 32 | apollgo.Log("Hermes disabled by settings.") 33 | } 34 | select {} 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sshh12/apollgo 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gorilla/mux v1.8.0 7 | github.com/nadoo/glider v0.13.0 8 | github.com/showwin/speedtest-go v1.0.5 9 | github.com/sshh12/hermes v0.1.1 10 | github.com/tfriedel6/canvas v0.12.1 11 | golang.org/x/mobile v0.0.0-20200801112145-973feb4309de 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 2 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= 3 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 4 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 5 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 h1:EBTWhcAX7rNQ80RLwLCpHZBBrJuzallFHnF+yMXo928= 7 | github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 8 | github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE= 12 | github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0= 13 | github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4= 14 | github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170= 15 | github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0= 16 | github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI= 17 | github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow= 18 | github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= 19 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 20 | github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk= 21 | github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= 22 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 23 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 24 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 25 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 26 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 28 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 31 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 32 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 33 | github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= 34 | github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ= 35 | github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI= 36 | github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= 37 | github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= 38 | github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= 39 | github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= 40 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 41 | github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 42 | github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= 43 | github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= 44 | github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54= 45 | github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo= 46 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 47 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 48 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 49 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 50 | github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= 51 | github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= 52 | github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= 53 | github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= 54 | github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= 55 | github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= 56 | github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= 57 | github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= 58 | github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= 59 | github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls= 60 | github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI= 61 | github.com/nadoo/conflag v0.2.3 h1:/+rTaN0bHTIiQbPl1WZK78JRoqjlNqJ9Zf05ep0o5jI= 62 | github.com/nadoo/conflag v0.2.3/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co= 63 | github.com/nadoo/glider v0.13.0 h1:zhN5vo8mIJihYa6MKTUaXt1jYHhc8u8O/do9UVXVx7M= 64 | github.com/nadoo/glider v0.13.0/go.mod h1:Dt4731Nh8yy0iUMkx8co2WHv8az0fjirLwyg25QBy9U= 65 | github.com/nadoo/ipset v0.3.0 h1:TgULgp4s2PI3ItoCykDzMp8R49fRhMUNoUUEahERr5o= 66 | github.com/nadoo/ipset v0.3.0/go.mod h1:ugJe3mH5N1UNQbXayGJnLEMALeiwCJYo49Wg4MnZTHU= 67 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 68 | github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw= 69 | github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= 70 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 71 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 72 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 73 | github.com/showwin/speedtest-go v1.0.5 h1:1jexTN+GULuOwWiyyThtyAd+lqCHtBKAmr5+vv3NkN0= 74 | github.com/showwin/speedtest-go v1.0.5/go.mod h1:QuXk+aoaFiKYZXyesof8CA9K6ukwawc9TzBTTb/ymBE= 75 | github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= 76 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 77 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 78 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 79 | github.com/sshh12/hermes v0.1.1 h1:DUuLvEdcLH6y/mPJN3+NSBNyOXp3n1/iJdakOACu5BA= 80 | github.com/sshh12/hermes v0.1.1/go.mod h1:s6z5o2NEVCaMVLy0XC7+h5lzgYDZeTRBP+7t+RyxWl0= 81 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 82 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 83 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 84 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 85 | github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= 86 | github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo= 87 | github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= 88 | github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg= 89 | github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo= 90 | github.com/tfriedel6/canvas v0.12.1 h1:Oc4gww+cOtix69IaYo8TmRbwpbTl6D1jza2mBM4ZOPo= 91 | github.com/tfriedel6/canvas v0.12.1/go.mod h1:WIe1YgsQiKA1awmU6tSs8e5DkceDHC5MHgV5vQQZr/0= 92 | github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= 93 | github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= 94 | github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8= 95 | github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY= 96 | github.com/veandco/go-sdl2 v0.4.0 h1:l9q6K+Dvpd/VlZdw2ufApKnWhAQqx9UL8Zrvbjtm3Lw= 97 | github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= 98 | github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI= 99 | github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo= 100 | github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= 101 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 102 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 103 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 104 | golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 105 | golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 106 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 107 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 108 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 109 | golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 110 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 111 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 112 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k= 113 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 114 | golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 115 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= 116 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 117 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 118 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 119 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= 120 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 121 | golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 122 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 123 | golang.org/x/mobile v0.0.0-20200801112145-973feb4309de h1:OVJ6QQUBAesB8CZijKDSsXX7xYVtUhrkY0gwMfbi4p4= 124 | golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= 125 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 126 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 127 | golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 128 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 129 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 130 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 131 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 132 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 133 | golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 134 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 135 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 136 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 137 | golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 138 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 139 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 140 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 141 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 142 | golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 143 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 144 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 145 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= 146 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 147 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 148 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 149 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 150 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 151 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 152 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 153 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 154 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 155 | golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 156 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 158 | golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 161 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 162 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 163 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= 169 | golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk= 171 | golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 173 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 174 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 175 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 176 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 177 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 178 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 179 | golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 180 | golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 181 | golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 182 | golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 183 | golang.org/x/tools v0.0.0-20201204062850-545788942d5f/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 184 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 185 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 186 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 187 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 188 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 189 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 190 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 191 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 192 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 193 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 194 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 195 | -------------------------------------------------------------------------------- /network/glider.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/nadoo/glider/dns" 5 | "github.com/nadoo/glider/proxy" 6 | "github.com/nadoo/glider/rule" 7 | "net" 8 | 9 | "context" 10 | "time" 11 | // proto support 12 | _ "github.com/nadoo/glider/proxy/http" 13 | _ "github.com/nadoo/glider/proxy/kcp" 14 | _ "github.com/nadoo/glider/proxy/mixed" 15 | _ "github.com/nadoo/glider/proxy/obfs" 16 | _ "github.com/nadoo/glider/proxy/reject" 17 | _ "github.com/nadoo/glider/proxy/socks4" 18 | _ "github.com/nadoo/glider/proxy/socks5" 19 | _ "github.com/nadoo/glider/proxy/ss" 20 | _ "github.com/nadoo/glider/proxy/ssh" 21 | _ "github.com/nadoo/glider/proxy/ssr" 22 | _ "github.com/nadoo/glider/proxy/tcp" 23 | _ "github.com/nadoo/glider/proxy/tls" 24 | _ "github.com/nadoo/glider/proxy/trojan" 25 | _ "github.com/nadoo/glider/proxy/udp" 26 | _ "github.com/nadoo/glider/proxy/vless" 27 | _ "github.com/nadoo/glider/proxy/vmess" 28 | _ "github.com/nadoo/glider/proxy/ws" 29 | ) 30 | 31 | // ListenerConfig config for listeners 32 | type ListenerConfig struct { 33 | URIs []string `json:"uris"` 34 | Strategy string `json:"strategy"` 35 | Forwarders []string `json:"forwarders"` 36 | Check string `json:"check"` 37 | CheckInterval int `json:"checkInterval"` 38 | MaxFailures int `json:"maxFailures"` 39 | DialTimeout int `json:"dialTimeout"` 40 | RelayTimeout int `json:"relayTimeout"` 41 | IntFace string `json:"interface"` 42 | DNSListener string `json:"dns"` 43 | DNSAlwaysTCP bool `json:"dnsAlwaysTCP"` 44 | DNSServers []string `json:"dnsServers"` 45 | DNSMaxTTL int `json:"dnsMaxTTL"` 46 | DNSMinTTL int `json:"dnsMinTTL"` 47 | DNSTimeout int `json:"dnsTimeout"` 48 | DNSCacheSize int `json:"dnsCacheSize"` 49 | DNSRecords []string `json:"dnsRecords"` 50 | } 51 | 52 | // ServeGlider starts glider server 53 | func ServeGlider(listeners []ListenerConfig) error { 54 | for _, listener := range listeners { 55 | if err := runListener(&listener); err != nil { 56 | return err 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func runListener(listener *ListenerConfig) error { 63 | rules := []*rule.Config{} 64 | strat := &rule.Strategy{ 65 | Strategy: listener.Strategy, 66 | Check: listener.Check, 67 | CheckInterval: listener.CheckInterval, 68 | MaxFailures: listener.MaxFailures, 69 | DialTimeout: listener.DialTimeout, 70 | RelayTimeout: listener.RelayTimeout, 71 | IntFace: listener.IntFace, 72 | } 73 | forwarders := make([]string, 0) 74 | for _, forward := range listener.Forwarders { 75 | if forward != "" { 76 | forwarders = append(forwarders, forward) 77 | } 78 | } 79 | pxy := rule.NewProxy(forwarders, strat, rules) 80 | if listener.DNSListener != "" { 81 | dnsConfig := &dns.Config{ 82 | Servers: listener.DNSServers, 83 | Timeout: listener.DNSTimeout, 84 | MaxTTL: listener.DNSMaxTTL, 85 | MinTTL: listener.DNSMinTTL, 86 | Records: listener.DNSRecords, 87 | AlwaysTCP: listener.DNSAlwaysTCP, 88 | CacheSize: listener.DNSCacheSize, 89 | } 90 | d, err := dns.NewServer(listener.DNSListener, pxy, dnsConfig) 91 | if err != nil { 92 | return err 93 | } 94 | d.AddHandler(pxy.AddDomainIP) 95 | d.Start() 96 | net.DefaultResolver = &net.Resolver{ 97 | PreferGo: true, 98 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 99 | d := net.Dialer{Timeout: time.Second * 3} 100 | return d.DialContext(ctx, "udp", listener.DNSListener) 101 | }, 102 | } 103 | } 104 | pxy.Check() 105 | for _, uri := range listener.URIs { 106 | if uri == "" { 107 | continue 108 | } 109 | local, err := proxy.ServerFromURL(uri, pxy) 110 | if err != nil { 111 | return err 112 | } 113 | go local.ListenAndServe() 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /network/hermes.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "strconv" 8 | hermesTCP "github.com/sshh12/hermes/tcp" 9 | ) 10 | 11 | // HermesConfig configures hermes settings 12 | type HermesConfig struct { 13 | Password string `json:"password"` 14 | HermesPort int `json:"port"` 15 | Server string `json:"server"` 16 | ForwardPairs []string `json:"forwardPairs"` 17 | } 18 | 19 | // ServeHermes starts hermes client 20 | func ServeHermes(cfg HermesConfig, log func(string)) error { 21 | for _, pair := range cfg.ForwardPairs { 22 | split := strings.Split(pair, "/") 23 | if len(split) != 2 { 24 | return fmt.Errorf("Invalid port pair %s", pair) 25 | } 26 | appPort, err := strconv.Atoi(split[0]) 27 | if err != nil { 28 | return err 29 | } 30 | remotePort, err := strconv.Atoi(split[1]) 31 | if err != nil { 32 | return err 33 | } 34 | serverAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", cfg.Server, cfg.HermesPort)) 35 | if err != nil { 36 | return err 37 | } 38 | go func() { 39 | log(fmt.Sprintf("Running hermes :%d -> %s:%d", appPort, cfg.Server, remotePort)) 40 | client, err := hermesTCP.NewClient( 41 | appPort, 42 | remotePort, 43 | cfg.Server, 44 | hermesTCP.WithRestarts(), 45 | hermesTCP.WithPassword(cfg.Password), 46 | hermesTCP.WithServerAddress(serverAddr), 47 | ) 48 | if err != nil { 49 | log(err.Error()) 50 | } 51 | if err := client.Start(); err != nil { 52 | log(err.Error()) 53 | } 54 | }() 55 | } 56 | return nil 57 | } -------------------------------------------------------------------------------- /network/info.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | // ExternalIP gets external ip 10 | func ExternalIP() (string, error) { 11 | resp, err := http.Get("https://ifconfig.me/") 12 | if err != nil { 13 | return "", err 14 | } 15 | defer resp.Body.Close() 16 | body, err := ioutil.ReadAll(resp.Body) 17 | if err != nil { 18 | return "", err 19 | } 20 | return strings.TrimSpace(string(body)), nil 21 | } 22 | -------------------------------------------------------------------------------- /network/speedtest.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "github.com/showwin/speedtest-go/speedtest" 6 | "sort" 7 | "time" 8 | ) 9 | 10 | // RunSpeedTest runs a speedtest 11 | func RunSpeedTest() (float64, float64, float64, error) { 12 | user, err := speedtest.FetchUserInfo() 13 | if err != nil { 14 | return 0, 0, 0, err 15 | } 16 | serverList, err := speedtest.FetchServerList(user) 17 | if err != nil { 18 | return 0, 0, 0, err 19 | } 20 | targets, err := serverList.FindServer([]int{}) 21 | if err != nil { 22 | return 0, 0, 0, err 23 | } 24 | if len(targets) == 0 { 25 | return 0, 0, 0, fmt.Errorf("no speed test servers found") 26 | } 27 | sort.Slice(targets, func(i, j int) bool { 28 | return targets[i].Distance > targets[j].Distance 29 | }) 30 | s := targets[0] 31 | s.PingTest() 32 | s.DownloadTest() 33 | s.UploadTest() 34 | return s.DLSpeed / 8, s.ULSpeed / 8, float64(s.Latency / time.Millisecond), nil 35 | } 36 | -------------------------------------------------------------------------------- /react/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none" 4 | } 5 | -------------------------------------------------------------------------------- /react/mv-assets.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | glob('../react/build/**/*.*', {}, (err, files) => { 6 | if (err) { 7 | console.warn(err); 8 | return; 9 | } 10 | files.map((fn) => { 11 | let newFn = path.join( 12 | '../cmd/android/assets', 13 | fn.replace(/[\\/]/g, '__').replace('..__react__build__', '') 14 | ); 15 | console.log(newFn); 16 | fs.mkdir(path.dirname(newFn), { recursive: true }, (err) => { 17 | if (err) { 18 | console.warn(err); 19 | return; 20 | } 21 | fs.rename(fn, newFn, (err) => { 22 | if (err) console.warn(err); 23 | }); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollgo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@rebass/forms": "^4.0.6", 7 | "react": "^17.0.1", 8 | "react-dom": "^17.0.1", 9 | "react-scripts": "4.0.1", 10 | "rebass": "^4.0.7", 11 | "theme-ui": "^0.3.4" 12 | }, 13 | "scripts": { 14 | "dev": "react-scripts start", 15 | "build": "react-scripts build", 16 | "build:assets": "react-scripts build && node mv-assets.js" 17 | }, 18 | "eslintConfig": { 19 | "extends": [ 20 | "react-app", 21 | "react-app/jest" 22 | ] 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sshh12/apollgo/2a490f6c9c44ad893b703aaa1efd675c663744d3/react/public/favicon.ico -------------------------------------------------------------------------------- /react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | apollgo 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /react/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sshh12/apollgo/2a490f6c9c44ad893b703aaa1efd675c663744d3/react/public/logo192.png -------------------------------------------------------------------------------- /react/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sshh12/apollgo/2a490f6c9c44ad893b703aaa1efd675c663744d3/react/public/logo512.png -------------------------------------------------------------------------------- /react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "apollgo", 3 | "name": "Apollgo", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /react/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /react/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { ThemeProvider } from 'theme-ui'; 4 | import theme from './theme'; 5 | import { Box, Text, Flex, Link } from 'rebass'; 6 | import Glider from './components/Glider'; 7 | import Status from './components/Status'; 8 | import Hermes from './components/Hermes'; 9 | import Logs from './components/Logs'; 10 | import api from './api'; 11 | 12 | const TABS = { 13 | status: Status, 14 | glider: Glider, 15 | hermes: Hermes, 16 | logs: Logs 17 | }; 18 | 19 | function getTab() { 20 | let path = window.location.pathname.replace('/', ''); 21 | if (Object.keys(TABS).includes(path)) { 22 | return path; 23 | } 24 | return 'status'; 25 | } 26 | 27 | function App() { 28 | let [tab, setTab] = useState(getTab()); 29 | let [status, setStatus] = useState(null); 30 | let [config, setConfig] = useState(null); 31 | let [defaults, setDefaults] = useState(null); 32 | let [loading, setLoading] = useState(false); 33 | let [lostConn, setLostConn] = useState(false); 34 | useEffect(() => { 35 | api.get('/api/status').then(setStatus); 36 | setInterval(() => { 37 | api 38 | .get('/api/status') 39 | .then((status) => { 40 | setStatus(status); 41 | setLostConn(false); 42 | }) 43 | .catch((err) => { 44 | console.warn(err); 45 | setLostConn(true); 46 | }); 47 | }, 10 * 1000); 48 | api.get('/api/config').then(setConfig); 49 | api.get('/api/config/defaults').then(setDefaults); 50 | }, []); 51 | let applyConfig = (newCfg) => { 52 | setLoading(true); 53 | api.put('/api/config', newCfg).then((cfg) => { 54 | setConfig(cfg); 55 | setLoading(false); 56 | alert('Restart the app for the new config to take effect.'); 57 | }); 58 | }; 59 | let TabView = TABS[tab]; 60 | return ( 61 | 62 |
63 | 64 | 65 | apollgo 66 | 67 | {loading && *loading*} 68 | {lostConn && *connection lost*} 69 | 70 | {Object.keys(TABS).map((tb) => ( 71 | { 75 | setTab(tb); 76 | window.history.pushState({}, '', '/' + tb); 77 | }} 78 | sx={{ 79 | display: 'inline-block', 80 | fontWeight: 'bold', 81 | px: 2, 82 | py: 1, 83 | color: 'inherit' 84 | }} 85 | > 86 | {tb} 87 | 88 | ))} 89 | 90 | 96 |
97 |
98 | ); 99 | } 100 | 101 | export default App; 102 | -------------------------------------------------------------------------------- /react/src/api.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = 2 | window.location.origin == 'http://localhost:3000' 3 | ? 'http://localhost:5000' 4 | : window.location.origin; 5 | 6 | let get = async (path) => { 7 | let resp = await fetch(BASE_URL + path); 8 | return await resp.json(); 9 | }; 10 | 11 | let put = async (path, data) => { 12 | let resp = await fetch(BASE_URL + path, { 13 | method: 'PUT', 14 | headers: { 'Content-Type': 'application/json' }, 15 | body: JSON.stringify(data) 16 | }); 17 | return await resp.json(); 18 | }; 19 | 20 | export default { get, put }; 21 | -------------------------------------------------------------------------------- /react/src/components/CSVInput.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Text } from 'rebass'; 3 | import TextInput from './TextInput'; 4 | 5 | export default function CSVInput({ 6 | value, 7 | placeholder, 8 | label, 9 | key, 10 | onChange, 11 | validate 12 | }) { 13 | let [text, setText] = useState(value.join(', ')); 14 | let [values, setValues] = useState(value); 15 | return ( 16 | <> 17 | { 23 | setText(v); 24 | let vals = v 25 | .split(',') 26 | .map((item) => item.trim()) 27 | .filter(validate); 28 | vals = [...new Set(vals)]; 29 | setValues(vals); 30 | onChange(vals); 31 | }} 32 | /> 33 | 34 | {values.map((v) => `(${v})`).join(',')} 35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /react/src/components/Glider.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Box, Card, Heading, Flex, Button, Text } from 'rebass'; 3 | import { Label, Select, Checkbox, Switch } from '@rebass/forms'; 4 | import TextInput from './TextInput'; 5 | import CSVInput from './CSVInput'; 6 | 7 | export default function Glider({ config, setConfig, defaults }) { 8 | let [editCfg, setEditCfg] = useState(config); 9 | useEffect(() => { 10 | setEditCfg(config); 11 | }, [config]); 12 | let editListener = (idx, newList) => { 13 | let newCfg = JSON.parse(JSON.stringify(editCfg)); 14 | newCfg.listeners = Object.assign([], newCfg.listeners, { [idx]: newList }); 15 | setEditCfg(newCfg); 16 | }; 17 | let addListener = () => { 18 | let newCfg = JSON.parse(JSON.stringify(editCfg)); 19 | newCfg.listeners = newCfg.listeners.concat([defaults.listeners[0]]); 20 | setEditCfg(newCfg); 21 | }; 22 | let deleteListener = (idx) => { 23 | if (editCfg.listeners.length == 1) { 24 | return; 25 | } 26 | let newCfg = JSON.parse(JSON.stringify(editCfg)); 27 | newCfg.listeners = newCfg.listeners.filter((_, i) => i != idx); 28 | setEditCfg(newCfg); 29 | }; 30 | let cfgChanged = JSON.stringify(config) != JSON.stringify(editCfg); 31 | return ( 32 | 33 | 34 | 35 |

See glider docs and schemes

36 |
37 | {editCfg && ( 38 | 41 | setEditCfg({ ...editCfg, enableGlider: !editCfg.enableGlider }) 42 | } 43 | /> 44 | )} 45 |
46 | {editCfg && ( 47 | 48 | {editCfg.listeners.map((list, listIdx) => ( 49 | <> 50 | editListener(listIdx, newList)} 54 | del={() => deleteListener(listIdx)} 55 | /> 56 |
57 | 58 | ))} 59 |
60 | )} 61 | 62 | 63 | {cfgChanged && ( 64 | 67 | )} 68 | {cfgChanged && ( 69 | 72 | )} 73 | 74 |
75 | ); 76 | } 77 | 78 | function ListenerSettingsCard({ list, listIdx, edit, del }) { 79 | let editURI = (uriIdx, newURI) => { 80 | let newURIs = Object.assign([], list.uris, { 81 | [uriIdx]: newURI 82 | }); 83 | while (newURIs[newURIs.length - 1] == '') { 84 | newURIs.pop(); 85 | } 86 | newURIs.push(''); 87 | edit({ 88 | ...list, 89 | uris: newURIs 90 | }); 91 | }; 92 | let editFoward = (forwardIdx, newForward) => { 93 | let newForwards = Object.assign([], list.forwarders, { 94 | [forwardIdx]: newForward 95 | }); 96 | while (newForwards[newForwards.length - 1] == '') { 97 | newForwards.pop(); 98 | } 99 | if (newForwards.length > 0) newForwards.push(''); 100 | edit({ 101 | ...list, 102 | forwarders: newForwards 103 | }); 104 | }; 105 | let hasForward = list.forwarders.length > 0; 106 | return ( 107 | 108 | 109 | {list.uris?.filter((v) => !!v).join(', ') || 'Listener ' + listIdx} 110 | 111 | <> 112 | {list.uris.map((uri, uriIdx) => ( 113 | editURI(uriIdx, v)} 119 | /> 120 | ))} 121 | 122 | {!hasForward ? ( 123 | <> 124 | { 130 | edit({ ...list, forwarders: [v, ''] }); 131 | }} 132 | /> 133 | 134 | ) : ( 135 | <> 136 | {list.forwarders.map((forward, forwardIdx) => ( 137 | editFoward(forwardIdx, v)} 143 | /> 144 | ))} 145 | 146 | )} 147 | {hasForward && ( 148 | 149 | 150 | 161 | 162 | )} 163 | 164 | 165 | edit({ ...list, check: v })} 171 | /> 172 | 173 | 174 | edit({ ...list, checkInterval: v })} 181 | /> 182 | 183 | 184 | 185 | 197 | 198 | {list.dns != '' && ( 199 | 200 | { 206 | edit({ ...list, dns: v }); 207 | }} 208 | /> 209 | !!v.match(/^\d+\.\d+\.\d+\.\d+:\d+$/)} 215 | onChange={(v) => { 216 | edit({ ...list, dnsServers: v }); 217 | }} 218 | /> 219 | !!v.match(/^[\w\.]+\/[A-Za-z0-9\.\.]+$/)} 225 | onChange={(v) => { 226 | edit({ ...list, dnsRecords: v }); 227 | }} 228 | /> 229 | 230 | )} 231 | 232 | [delete] 233 | 234 | 235 | ); 236 | } 237 | -------------------------------------------------------------------------------- /react/src/components/Hermes.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Box, Flex, Button } from 'rebass'; 3 | import { Switch } from '@rebass/forms'; 4 | import TextInput from './TextInput'; 5 | import CSVInput from './CSVInput'; 6 | 7 | export default function Hermes({ config, setConfig }) { 8 | let [editCfg, setEditCfg] = useState(config); 9 | useEffect(() => { 10 | setEditCfg(config); 11 | }, [config]); 12 | let cfgChanged = JSON.stringify(config) != JSON.stringify(editCfg); 13 | return ( 14 | 15 | 16 | 17 |

See hermes docs

18 |
19 | {editCfg && ( 20 | 23 | setEditCfg({ ...editCfg, enableHermes: !editCfg.enableHermes }) 24 | } 25 | /> 26 | )} 27 |
28 | {editCfg && ( 29 | 30 | 31 | 32 | 38 | setEditCfg({ 39 | ...editCfg, 40 | hermesConfig: { ...editCfg.hermesConfig, server: v } 41 | }) 42 | } 43 | /> 44 | 45 | 46 | 53 | setEditCfg({ 54 | ...editCfg, 55 | hermesConfig: { ...editCfg.hermesConfig, port: v } 56 | }) 57 | } 58 | /> 59 | 60 | 61 | 67 | setEditCfg({ 68 | ...editCfg, 69 | hermesConfig: { ...editCfg.hermesConfig, password: v } 70 | }) 71 | } 72 | /> 73 | !!v.match(/^\d+\/\d+$/)} 79 | onChange={(v) => 80 | setEditCfg({ 81 | ...editCfg, 82 | hermesConfig: { ...editCfg.hermesConfig, forwardPairs: v } 83 | }) 84 | } 85 | /> 86 | 87 | )} 88 | 89 | {cfgChanged && ( 90 | 93 | )} 94 | {cfgChanged && ( 95 | 98 | )} 99 | 100 |
101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /react/src/components/Logs.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box } from 'rebass'; 3 | 4 | export default function Logs({ status }) { 5 | let logs = status?.logs || []; 6 | return ( 7 | 8 | 9 |
10 |           {logs
11 |             .map((l) => `[${new Date(l.time * 1000).toISOString()}] ${l.text}`)
12 |             .join('\n')}
13 |         
14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /react/src/components/Status.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, Card, Flex, Heading, Text } from 'rebass'; 3 | 4 | export default function Status({ status }) { 5 | return ( 6 | 7 | 8 | 9 | 10 | Connection 11 | IPv4 {status?.ip} 12 | 13 | 14 | 15 | 16 | Metrics 17 | {Math.round(status?.dlSpeed * 100) / 100} Mb/s ↓ 18 | {Math.round(status?.ulSpeed * 100) / 100} Mb/s ↑ 19 | {status?.latency} ms 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /react/src/components/TextInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box } from 'rebass'; 3 | import { Label, Input } from '@rebass/forms'; 4 | 5 | export default function TextInput({ 6 | value, 7 | placeholder, 8 | label, 9 | key, 10 | onChange, 11 | type 12 | }) { 13 | return ( 14 | 15 | 16 | onChange(e.target.value)} 22 | placeholder={placeholder} 23 | /> 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /react/src/theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | space: [0, 4, 8, 16, 32, 64, 128, 256, 512], 3 | fonts: { 4 | body: 5 | 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif', 6 | heading: 'inherit', 7 | monospace: 'Menlo, monospace' 8 | }, 9 | fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 96], 10 | fontWeights: { 11 | body: 400, 12 | heading: 700, 13 | bold: 700 14 | }, 15 | lineHeights: { 16 | body: 1.5, 17 | heading: 1.125 18 | }, 19 | colors: { 20 | text: '#000', 21 | background: '#fff', 22 | primary: '#07c', 23 | secondary: '#30c', 24 | muted: '#f6f6f6' 25 | }, 26 | styles: { 27 | root: { 28 | fontFamily: 'body', 29 | lineHeight: 'body', 30 | fontWeight: 'body' 31 | }, 32 | h1: { 33 | color: 'text', 34 | fontFamily: 'heading', 35 | lineHeight: 'heading', 36 | fontWeight: 'heading', 37 | fontSize: 5 38 | }, 39 | h2: { 40 | color: 'text', 41 | fontFamily: 'heading', 42 | lineHeight: 'heading', 43 | fontWeight: 'heading', 44 | fontSize: 4 45 | }, 46 | h3: { 47 | color: 'text', 48 | fontFamily: 'heading', 49 | lineHeight: 'heading', 50 | fontWeight: 'heading', 51 | fontSize: 3 52 | }, 53 | h4: { 54 | color: 'text', 55 | fontFamily: 'heading', 56 | lineHeight: 'heading', 57 | fontWeight: 'heading', 58 | fontSize: 2 59 | }, 60 | h5: { 61 | color: 'text', 62 | fontFamily: 'heading', 63 | lineHeight: 'heading', 64 | fontWeight: 'heading', 65 | fontSize: 1 66 | }, 67 | h6: { 68 | color: 'text', 69 | fontFamily: 'heading', 70 | lineHeight: 'heading', 71 | fontWeight: 'heading', 72 | fontSize: 0 73 | }, 74 | p: { 75 | color: 'text', 76 | fontFamily: 'body', 77 | fontWeight: 'body', 78 | lineHeight: 'body' 79 | }, 80 | a: { 81 | color: 'primary' 82 | }, 83 | pre: { 84 | fontFamily: 'monospace', 85 | overflowX: 'auto', 86 | code: { 87 | color: 'inherit' 88 | } 89 | }, 90 | code: { 91 | fontFamily: 'monospace', 92 | fontSize: 'inherit' 93 | }, 94 | table: { 95 | width: '100%', 96 | borderCollapse: 'separate', 97 | borderSpacing: 0 98 | }, 99 | th: { 100 | textAlign: 'left', 101 | borderBottomStyle: 'solid' 102 | }, 103 | td: { 104 | textAlign: 'left', 105 | borderBottomStyle: 'solid' 106 | }, 107 | img: { 108 | maxWidth: '100%' 109 | } 110 | } 111 | }; 112 | -------------------------------------------------------------------------------- /web/web.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "path/filepath" 8 | "strings" 9 | "time" 10 | 11 | "fmt" 12 | 13 | "github.com/gorilla/mux" 14 | "github.com/sshh12/apollgo/app" 15 | "golang.org/x/mobile/asset" 16 | ) 17 | 18 | // SpaHandler for SPA 19 | type SpaHandler struct{} 20 | 21 | var extToContentType = map[string]string{ 22 | "html": "text/html; charset=utf-8", 23 | "js": "text/javascript; charset=UTF-8", 24 | "svg": "image/svg+xml", 25 | "css": "text/css; charset=UTF-8", 26 | "json": "application/json; charset=UTF-8", 27 | "png": "image/png", 28 | } 29 | 30 | func serveAsset(w http.ResponseWriter, r *http.Request, file asset.File, ext string) { 31 | data, err := ioutil.ReadAll(file) 32 | if err != nil { 33 | w.Write([]byte(err.Error())) 34 | return 35 | } 36 | w.Header().Set("Content-Type", extToContentType[ext]) 37 | w.WriteHeader(http.StatusOK) 38 | w.Write(data) 39 | } 40 | 41 | func (h SpaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 42 | path, err := filepath.Abs(r.URL.Path) 43 | if err != nil { 44 | http.Error(w, err.Error(), http.StatusBadRequest) 45 | return 46 | } 47 | path = strings.ReplaceAll(path[1:], "/", "__") 48 | 49 | if file, err := asset.Open(path); err == nil { 50 | parts := strings.Split(path, ".") 51 | serveAsset(w, r, file, parts[len(parts)-1]) 52 | return 53 | } 54 | indexFile, err := asset.Open("index.html") 55 | if err != nil { 56 | w.Write([]byte(err.Error())) 57 | } else { 58 | serveAsset(w, r, indexFile, "html") 59 | } 60 | } 61 | 62 | // ServeWebApp starts http server 63 | func ServeWebApp(apollgo *app.ApollgoApp) { 64 | 65 | initCfg := apollgo.GetCfg() 66 | router := mux.NewRouter() 67 | 68 | router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { 69 | json.NewEncoder(w).Encode(map[string]bool{"ok": true}) 70 | }) 71 | router.HandleFunc("/api/config", func(w http.ResponseWriter, r *http.Request) { 72 | if r.Method == "PUT" { 73 | var newCfg app.Config 74 | if err := json.NewDecoder(r.Body).Decode(&newCfg); err != nil { 75 | apollgo.Log(err.Error()) 76 | } else { 77 | apollgo.SetCfg(&newCfg) 78 | } 79 | } 80 | json.NewEncoder(w).Encode(apollgo.GetCfg()) 81 | }) 82 | router.HandleFunc("/api/config/defaults", func(w http.ResponseWriter, r *http.Request) { 83 | json.NewEncoder(w).Encode(app.DefaultCfg) 84 | }) 85 | router.HandleFunc("/api/status", func(w http.ResponseWriter, r *http.Request) { 86 | json.NewEncoder(w).Encode(apollgo.GetStatus()) 87 | }) 88 | router.PathPrefix("/").Handler(SpaHandler{}) 89 | 90 | addr := fmt.Sprintf("0.0.0.0:%d", initCfg.ApollgoPort) 91 | srv := &http.Server{ 92 | Handler: router, 93 | Addr: addr, 94 | WriteTimeout: 15 * time.Second, 95 | ReadTimeout: 15 * time.Second, 96 | } 97 | 98 | apollgo.Log("Web server starting on " + addr) 99 | 100 | srv.ListenAndServe() 101 | } 102 | --------------------------------------------------------------------------------