├── .gitignore ├── README.MD ├── cmd └── gamma │ └── main.go ├── config.go ├── gateway.go ├── go.mod ├── go.sum ├── protocol ├── compression.go ├── conn.go ├── decoder.go ├── disconnect.go ├── encoder.go ├── login.go ├── login │ ├── data.go │ └── login.go ├── packet.go ├── reader.go └── writer.go └── proxy.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Config files 15 | *.yaml 16 | 17 | .idea 18 | ./configs/ 19 | config.yml -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Gamma - a Minecraft bedrock Proxy 2 | 3 | adaptation from [haveachin/bedprox](https://github.com/haveachin/bedprox) 4 | 5 | ## Added/changed Features 6 | 7 | - TODO 8 | 9 | ## TODO 10 | - Implement API 11 | - Implement L7 protection 12 | 13 | ## Command-Line Flags 14 | 15 | `-config-path` specifies the path to all your server configs [default: `"./configs/"`] 16 | 17 | ### Example Usage 18 | 19 | `./gamma -config-path="."` 20 | 21 | ## Global config.yml 22 | ### Example/Default 23 | ```yaml 24 | debug: false 25 | genericJoinResponse: There is no proxy associated with this domain. Please check your configuration. 26 | receiveProxyProtocol: false 27 | prometheus: 28 | enabled: false 29 | bind: :9060 30 | api: 31 | enabled: false 32 | bind: :5000 33 | ping: 34 | edition: MCPE 35 | versionname: 1.19.50 36 | versionProtocol: 560 37 | description: Gamma proxy 38 | playerCount: 0 39 | maxPlayerCount: 10 40 | gamemode: SURVIVAL 41 | gamemodeNumeric: 1 42 | ``` 43 | 44 | Values can be left out if they don't deviate from the default, a config.json with just `{}` is still required for startup. 45 | ### Fields 46 | - TODO 47 | 48 | ## Proxy Config 49 | 50 | ### Examples 51 | 52 | #### Minimal Config 53 | 54 |
55 | min.example.com 56 | 57 | ```json 58 | { 59 | "domainNames": ["mc.example.com", "example.com"], 60 | "proxyTo": ":8080" 61 | } 62 | ``` 63 | 64 |
65 | 66 | #### Full Config 67 | 68 |
69 | full.example.com 70 | 71 | ```json 72 | { 73 | "domains": ["mc.example.com", "example.com"], 74 | "listenTo": ":19132", 75 | "proxyTo": ":8080", 76 | "proxyProtocol": false, 77 | "dialTimeout": 1000, 78 | "dialTimeoutMessage": "Server is currently offline", 79 | "sendProxyProtocol": false 80 | } 81 | ``` 82 | 83 |
84 | 85 | ## Prometheus exporter 86 | The built-in prometheus exporter can be used to view metrics about gamma' operation. 87 | This can be used through `"prometheusEnabled": true` and `"prometheusBind": ":9070"` in `config.yml` 88 | It is recommended to firewall the prometheus exporter with an application like *ufw* or *iptables* to make it only accessible by your own Prometheus instance. 89 | ### Prometheus configuration: 90 | Example prometheus.yml configuration: 91 | ```yaml 92 | scrape_configs: 93 | - job_name: gamma 94 | static_configs: 95 | - targets: ['gamma-exporter-hostname:port'] 96 | ``` 97 | 98 | ### Metrics: 99 | * gamma_connected: show the amount of connected players per instance and proxy: 100 | * **Example response:** `gamma_connected{host="proxy.example.com",instance="vps1.example.com:9070",job="gamma"} 10` 101 | * **host:** listenTo domain as specified in the gamma configuration. 102 | * **instance:** what gamma instance the amount of players are connected to. 103 | * **job:** what job was specified in the prometheus configuration. 104 | * gamma_handshakes: counter of the number of handshake packets received per instance, type and target: 105 | * **Example response:** `gamma_handshakes{instance="vps1.example.com:9070",type="status",host="proxy.example.com",country="DE"} 5` 106 | * **instance:** what gamma instance handshakes were received on. 107 | * **type:** the type of handshake received; "status" or "login". 108 | * **host:** the target host specified by the client (login only). 109 | 110 | ## API 111 | ### Route examples 112 | GET `/proxies` will return 113 | ```json 114 | [ 115 | "config", 116 | "config2" 117 | ] 118 | ``` 119 | 120 | GET `/proxies/{name}` will return 121 | ```json 122 | { 123 | "domainNames": ["play.example.org"], 124 | "proxyTo": "backend.example.org:25566" 125 | } 126 | ``` 127 | 128 | POST `/proxies/{name}` with body 129 | ```json 130 | { 131 | "domainNames": ["play.example.org"], 132 | "proxyTo": "backend.example.org:25566" 133 | } 134 | ``` 135 | will return 136 | ```json 137 | {"success": true, "message": "the proxy has been succesfully added"} 138 | ``` 139 | 140 | DELETE `/proxies/{name}` will return 200(OK) 141 | 142 | GET `/` will return 200(OK) 143 | 144 | ## Used sources 145 | - [haveachin/bedprox](https://github.com/haveachin/bedprox) 146 | - [haveachin/infrared](https://github.com/haveachin/infrared) 147 | - [lhridder/infrared](https://github.com/lhridder/infrared) 148 | - [Sandertv/go-raknet](https://github.com/Sandertv/go-raknet) 149 | - [Sandertv/gophertunnel](https://github.com/Sandertv/gophertunnel) -------------------------------------------------------------------------------- /cmd/gamma/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/lhridder/gamma" 6 | "github.com/sandertv/go-raknet" 7 | "log" 8 | "net" 9 | "os" 10 | "time" 11 | ) 12 | 13 | const ( 14 | envPrefix = "INFRARED_" 15 | envConfigPath = envPrefix + "CONFIG_PATH" 16 | clfConfigPath = "config-path" 17 | ) 18 | 19 | var configPath = "./configs" 20 | 21 | func envString(name string, value string) string { 22 | envString := os.Getenv(name) 23 | if envString == "" { 24 | return value 25 | } 26 | 27 | return envString 28 | } 29 | 30 | func initEnv() { 31 | configPath = envString(envConfigPath, configPath) 32 | } 33 | 34 | func initFlags() { 35 | flag.StringVar(&configPath, clfConfigPath, configPath, "path of all proxy configs") 36 | flag.Parse() 37 | } 38 | 39 | func init() { 40 | initEnv() 41 | initFlags() 42 | } 43 | 44 | func main() { 45 | log.Println("Starting gamma") 46 | 47 | err := gamma.LoadGlobalConfig() 48 | if err != nil { 49 | log.Println(err) 50 | return 51 | } 52 | 53 | log.Println("Loading configs folder") 54 | cfgs, err := gamma.LoadProxyConfigsFromPath("./configs") 55 | if err != nil { 56 | log.Printf("Failed loading proxy configs, error: %s", err) 57 | return 58 | } 59 | 60 | var proxies []*gamma.Proxy 61 | for _, cfg := range cfgs { 62 | proxies = append(proxies, &gamma.Proxy{ 63 | Config: cfg, 64 | Dialer: raknet.Dialer{ 65 | UpstreamDialer: &net.Dialer{ 66 | Timeout: 5 * time.Second, 67 | LocalAddr: &net.UDPAddr{ 68 | IP: net.ParseIP(cfg.ProxyBind), 69 | }, 70 | }, 71 | }, 72 | }) 73 | } 74 | 75 | outCfgs := make(chan *gamma.ProxyConfig) 76 | go func() { 77 | if err := gamma.WatchProxyConfigFolder("./configs", outCfgs); err != nil { 78 | log.Println("Failed watching config folder; error:", err) 79 | log.Println("SYSTEM FAILURE: CONFIG WATCHER FAILED") 80 | } 81 | }() 82 | 83 | log.Println("Starting gateway") 84 | gateway := gamma.Gateway{ReceiveProxyProtocol: gamma.GammaConfig.ReceiveProxyProtocol} 85 | 86 | go func() { 87 | for { 88 | cfg, ok := <-outCfgs 89 | if !ok { 90 | return 91 | } 92 | 93 | proxy := &gamma.Proxy{ 94 | Config: cfg, 95 | UID: cfg.Domains[0], 96 | Dialer: raknet.Dialer{ 97 | UpstreamDialer: &net.Dialer{ 98 | Timeout: 5 * time.Second, 99 | }, 100 | }, 101 | } 102 | if err := gateway.RegisterProxy(proxy); err != nil { 103 | log.Println("Failed registering proxy; error:", err) 104 | } 105 | } 106 | }() 107 | 108 | if gamma.GammaConfig.Prometheus.Enabled { 109 | err := gateway.EnablePrometheus(gamma.GammaConfig.Prometheus.Bind) 110 | if err != nil { 111 | log.Println(err) 112 | return 113 | } 114 | } 115 | 116 | log.Println("Starting Gamma") 117 | if err := gateway.ListenAndServe(proxies); err != nil { 118 | log.Fatal("Gateway exited; error: ", err) 119 | } 120 | 121 | gateway.KeepProcessActive() 122 | 123 | } 124 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package gamma 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/fsnotify/fsnotify" 6 | "gopkg.in/yaml.v2" 7 | "io/ioutil" 8 | "log" 9 | "path/filepath" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type Service struct { 15 | Enabled bool `yaml:"enabled"` 16 | Bind string `yaml:"bind"` 17 | } 18 | 19 | type Ping struct { 20 | Edition string `yaml:"edition"` 21 | VersionName string `yaml:"versionName"` 22 | VersionProtocol int `yaml:"versionProtocol"` 23 | Description string `yaml:"description"` 24 | PlayerCount int `yaml:"playerCount"` 25 | MaxPlayerCount int `yaml:"maxPlayerCount"` 26 | Gamemode string `yaml:"gamemode"` 27 | GamemodeNumeric int `yaml:"gamemodeNumeric"` 28 | } 29 | 30 | type GlobalConfig struct { 31 | Prometheus Service 32 | Api Service 33 | Ping Ping 34 | Debug bool `yaml:"debug"` 35 | ReceiveProxyProtocol bool `yaml:"receiveProxyProtocol"` 36 | GenericJoinResponse string `yaml:"genericJoinResponse"` 37 | } 38 | 39 | type ProxyConfig struct { 40 | sync.RWMutex 41 | watcher *fsnotify.Watcher 42 | 43 | removeCallback func() 44 | changeCallback func() 45 | 46 | Domains []string `json:"domains"` 47 | ListenTo string `json:"listenTo"` 48 | ProxyTo string `json:"proxyTo"` 49 | ProxyBind string `json:"proxyBind"` 50 | DialTimeout int `json:"dialTimeout"` 51 | DialTimeoutMessage string `json:"dialTimeoutMessage"` 52 | SendProxyProtocol bool `json:"sendProxyProtocol"` 53 | } 54 | 55 | var GammaConfig GlobalConfig 56 | 57 | var DefaultConfig = GlobalConfig{ 58 | Debug: false, 59 | GenericJoinResponse: "There is no proxy associated with this domain. Please check your configuration.", 60 | ReceiveProxyProtocol: false, 61 | Prometheus: Service{ 62 | Enabled: false, 63 | Bind: ":9060", 64 | }, 65 | Api: Service{ 66 | Enabled: false, 67 | Bind: ":5000", 68 | }, 69 | Ping: Ping{ 70 | Edition: "MCPE", 71 | VersionName: "1.19.50", 72 | VersionProtocol: 560, 73 | Description: "Gamma proxy", 74 | PlayerCount: 0, 75 | MaxPlayerCount: 10, 76 | Gamemode: "SURVIVAL", 77 | GamemodeNumeric: 1, 78 | }, 79 | } 80 | 81 | var DefaultProxyConfig = ProxyConfig{ 82 | Domains: []string{"localhost"}, 83 | ListenTo: ":19132", 84 | ProxyTo: "localhost:19133", 85 | ProxyBind: "", 86 | DialTimeout: 1000, 87 | DialTimeoutMessage: "Sorry but the server is offline.", 88 | SendProxyProtocol: false, 89 | } 90 | 91 | func LoadGlobalConfig() error { 92 | log.Println("Loading config.yml") 93 | ymlFile, err := ioutil.ReadFile("config.yml") 94 | if err != nil { 95 | return err 96 | } 97 | var config = DefaultConfig 98 | err = yaml.Unmarshal(ymlFile, &config) 99 | if err != nil { 100 | return err 101 | } 102 | GammaConfig = config 103 | return nil 104 | } 105 | 106 | func readFilePaths(path string) ([]string, error) { 107 | var filePaths []string 108 | files, err := ioutil.ReadDir(path) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | for _, file := range files { 114 | if file.IsDir() { 115 | continue 116 | } 117 | 118 | filePaths = append(filePaths, filepath.Join(path, file.Name())) 119 | } 120 | 121 | return filePaths, err 122 | } 123 | 124 | func LoadProxyConfigsFromPath(path string) ([]*ProxyConfig, error) { 125 | filePaths, err := readFilePaths(path) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | var cfgs []*ProxyConfig 131 | 132 | for _, filePath := range filePaths { 133 | cfg, err := NewProxyConfigFromPath(filePath) 134 | if err != nil { 135 | return nil, err 136 | } 137 | cfgs = append(cfgs, cfg) 138 | } 139 | 140 | return cfgs, nil 141 | } 142 | 143 | func NewProxyConfigFromPath(path string) (*ProxyConfig, error) { 144 | log.Println("Loading", path) 145 | 146 | cfg, err := LoadFromPath(path) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | watcher, err := fsnotify.NewWatcher() 152 | if err != nil { 153 | return nil, err 154 | } 155 | cfg.watcher = watcher 156 | 157 | go func() { 158 | defer watcher.Close() 159 | log.Printf("Starting to watch %s", path) 160 | cfg.watch(path, time.Millisecond*50) 161 | log.Printf("Stopping to watch %s", path) 162 | }() 163 | 164 | if err := watcher.Add(path); err != nil { 165 | return nil, err 166 | } 167 | 168 | return cfg, err 169 | } 170 | 171 | func LoadFromPath(path string) (*ProxyConfig, error) { 172 | var config *ProxyConfig 173 | 174 | defaultCfg, err := json.Marshal(&DefaultProxyConfig) 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | err = json.Unmarshal(defaultCfg, &config) 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | bb, err := ioutil.ReadFile(path) 185 | if err != nil { 186 | return nil, err 187 | } 188 | 189 | if err := json.Unmarshal(bb, &config); err != nil { 190 | return nil, err 191 | } 192 | 193 | return config, err 194 | } 195 | 196 | func WatchProxyConfigFolder(path string, out chan *ProxyConfig) error { 197 | watcher, err := fsnotify.NewWatcher() 198 | if err != nil { 199 | return err 200 | } 201 | defer watcher.Close() 202 | 203 | if err := watcher.Add(path); err != nil { 204 | return err 205 | } 206 | 207 | defer close(out) 208 | for { 209 | select { 210 | case event, ok := <-watcher.Events: 211 | if !ok { 212 | return nil 213 | } 214 | if event.Op&fsnotify.Create == fsnotify.Create && filepath.Ext(event.Name) == ".json" { 215 | proxyCfg, err := NewProxyConfigFromPath(event.Name) 216 | if err != nil { 217 | log.Printf("Failed loading %s; error %s", event.Name, err) 218 | continue 219 | } 220 | out <- proxyCfg 221 | } 222 | case err, ok := <-watcher.Errors: 223 | if !ok { 224 | return nil 225 | } 226 | log.Printf("Failed watching %s; error %s", path, err) 227 | } 228 | } 229 | } 230 | 231 | func (cfg *ProxyConfig) watch(path string, interval time.Duration) { 232 | // The interval protects the watcher from write event spams 233 | // This is necessary due to how some text editors handle file safes 234 | tick := time.Tick(interval) 235 | var lastEvent *fsnotify.Event 236 | 237 | for { 238 | select { 239 | case <-tick: 240 | if lastEvent == nil { 241 | continue 242 | } 243 | cfg.onConfigWrite(*lastEvent) 244 | lastEvent = nil 245 | case event, ok := <-cfg.watcher.Events: 246 | if !ok { 247 | return 248 | } 249 | if event.Op&fsnotify.Remove == fsnotify.Remove { 250 | cfg.removeCallback() 251 | return 252 | } 253 | if event.Op&fsnotify.Write == fsnotify.Write { 254 | lastEvent = &event 255 | } 256 | case err, ok := <-cfg.watcher.Errors: 257 | if !ok { 258 | return 259 | } 260 | log.Printf("Failed watching %s; error %s", path, err) 261 | } 262 | } 263 | } 264 | 265 | func (cfg *ProxyConfig) onConfigWrite(event fsnotify.Event) { 266 | log.Println("Updating", event.Name) 267 | if err := cfg.LoadFromPath(event.Name); err != nil { 268 | log.Printf("Failed update on %s; error %s", event.Name, err) 269 | return 270 | } 271 | cfg.changeCallback() 272 | } 273 | 274 | // LoadFromPath loads the ProxyConfig from a file 275 | func (cfg *ProxyConfig) LoadFromPath(path string) error { 276 | cfg.Lock() 277 | defer cfg.Unlock() 278 | 279 | var defaultCfg map[string]interface{} 280 | bb, err := json.Marshal(&DefaultProxyConfig) 281 | if err != nil { 282 | return err 283 | } 284 | 285 | if err := json.Unmarshal(bb, &defaultCfg); err != nil { 286 | return err 287 | } 288 | 289 | bb, err = ioutil.ReadFile(path) 290 | if err != nil { 291 | return err 292 | } 293 | 294 | var loadedCfg map[string]interface{} 295 | if err := json.Unmarshal(bb, &loadedCfg); err != nil { 296 | return err 297 | } 298 | 299 | for k, v := range loadedCfg { 300 | defaultCfg[k] = v 301 | } 302 | 303 | bb, err = json.Marshal(defaultCfg) 304 | if err != nil { 305 | return err 306 | } 307 | 308 | return json.Unmarshal(bb, cfg) 309 | } 310 | -------------------------------------------------------------------------------- /gateway.go: -------------------------------------------------------------------------------- 1 | package gamma 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "github.com/lhridder/gamma/protocol" 9 | "github.com/lhridder/gamma/protocol/login" 10 | "github.com/pires/go-proxyproto" 11 | "github.com/prometheus/client_golang/prometheus" 12 | "github.com/prometheus/client_golang/prometheus/promauto" 13 | "github.com/prometheus/client_golang/prometheus/promhttp" 14 | "github.com/sandertv/go-raknet" 15 | "log" 16 | "net" 17 | "net/http" 18 | "strings" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | var ( 24 | handshakeCount = promauto.NewCounterVec(prometheus.CounterOpts{ 25 | Name: "gamma_handshakes", 26 | Help: "The total number of handshakes made to each proxy by type", 27 | }, []string{"type", "host"}) 28 | ) 29 | 30 | type Gateway struct { 31 | listeners sync.Map 32 | Proxies sync.Map 33 | closed chan bool 34 | wg sync.WaitGroup 35 | ReceiveProxyProtocol bool 36 | underAttack bool 37 | connections int 38 | } 39 | 40 | func (gateway *Gateway) KeepProcessActive() { 41 | gateway.wg.Wait() 42 | } 43 | 44 | func (gateway *Gateway) EnablePrometheus(bind string) error { 45 | gateway.wg.Add(1) 46 | 47 | go func() { 48 | defer gateway.wg.Done() 49 | 50 | http.Handle("/metrics", promhttp.Handler()) 51 | err := http.ListenAndServe(bind, nil) 52 | if err != nil { 53 | panic(err) 54 | } 55 | }() 56 | 57 | log.Println("Enabling Prometheus metrics endpoint on", bind) 58 | return nil 59 | } 60 | 61 | func (gateway *Gateway) Close() { 62 | gateway.listeners.Range(func(k, v interface{}) bool { 63 | gateway.closed <- true 64 | _ = v.(*raknet.Listener).Close() 65 | return false 66 | }) 67 | } 68 | 69 | func (gateway *Gateway) CloseProxy(proxyUID string) { 70 | log.Println("Closing config with UID", proxyUID) 71 | v, ok := gateway.Proxies.Load(proxyUID) 72 | if !ok { 73 | return 74 | } 75 | proxy := v.(*Proxy) 76 | 77 | uids := proxy.DomainNames() 78 | for _, uid := range uids { 79 | log.Println("Closing proxy with UID", uid) 80 | gateway.Proxies.Delete(uid) 81 | } 82 | 83 | playersConnected.DeleteLabelValues(proxy.DomainName()) 84 | 85 | closeListener := true 86 | gateway.Proxies.Range(func(k, v interface{}) bool { 87 | otherProxy := v.(*Proxy) 88 | if proxy.ListenTo() == otherProxy.ListenTo() { 89 | closeListener = false 90 | return false 91 | } 92 | return true 93 | }) 94 | 95 | if !closeListener { 96 | return 97 | } 98 | 99 | v, ok = gateway.listeners.Load(proxy.ListenTo()) 100 | if !ok { 101 | return 102 | } 103 | _ = v.(*raknet.Listener).Close() 104 | } 105 | 106 | func (gateway *Gateway) RegisterProxy(proxy *Proxy) error { 107 | // Register new Proxy 108 | uids := proxy.UIDs() 109 | for _, uid := range uids { 110 | log.Println("Registering proxy with UID", uid) 111 | gateway.Proxies.Store(uid, proxy) 112 | } 113 | proxyUID := proxy.UID 114 | 115 | proxy.Config.removeCallback = func() { 116 | gateway.CloseProxy(proxyUID) 117 | } 118 | 119 | proxy.Config.changeCallback = func() { 120 | gateway.CloseProxy(proxyUID) 121 | if err := gateway.RegisterProxy(proxy); err != nil { 122 | log.Println(err) 123 | } 124 | } 125 | 126 | playersConnected.WithLabelValues(proxy.DomainName()) 127 | 128 | // Check if a gate is already listening to the Proxy address 129 | addr := proxy.ListenTo() 130 | if _, ok := gateway.listeners.Load(addr); ok { 131 | return nil 132 | } 133 | 134 | log.Println("Creating listener on", addr) 135 | listener, err := raknet.Listen(addr) 136 | if err != nil { 137 | return err 138 | } 139 | gateway.listeners.Store(addr, listener) 140 | 141 | listener.PongData(marshalPong(listener)) 142 | 143 | gateway.wg.Add(1) 144 | go func() { 145 | if err := gateway.listenAndServe(listener, addr); err != nil { 146 | log.Printf("Failed to listen on %s; error: %s", proxy.ListenTo(), err) 147 | } 148 | }() 149 | return nil 150 | } 151 | 152 | func marshalPong(l *raknet.Listener) []byte { 153 | motd := strings.Split(GammaConfig.Ping.Description, "\n") 154 | motd1 := motd[0] 155 | motd2 := "" 156 | if len(motd) > 1 { 157 | motd2 = motd[1] 158 | } 159 | 160 | port := l.Addr().(*net.UDPAddr).Port 161 | return []byte(fmt.Sprintf("%v;%v;%v;%v;%v;%v;%v;%v;%v;%v;%v;%v;", 162 | GammaConfig.Ping.Edition, motd1, GammaConfig.Ping.VersionProtocol, GammaConfig.Ping.VersionName, GammaConfig.Ping.PlayerCount, GammaConfig.Ping.MaxPlayerCount, 163 | l.ID(), motd2, GammaConfig.Ping.Gamemode, GammaConfig.Ping.GamemodeNumeric, port, port)) 164 | } 165 | 166 | func (gateway *Gateway) ListenAndServe(proxies []*Proxy) error { 167 | if len(proxies) <= 0 { 168 | return errors.New("no proxies in gateway") 169 | } 170 | 171 | gateway.closed = make(chan bool, len(proxies)) 172 | 173 | for _, proxy := range proxies { 174 | if err := gateway.RegisterProxy(proxy); err != nil { 175 | gateway.Close() 176 | return err 177 | } 178 | } 179 | 180 | log.Println("All proxies are online") 181 | return nil 182 | } 183 | 184 | func (gateway *Gateway) listenAndServe(listener *raknet.Listener, addr string) error { 185 | defer gateway.wg.Done() 186 | 187 | for { 188 | conn, err := listener.Accept() 189 | if err != nil { 190 | log.Println(err) 191 | } 192 | 193 | go func() { 194 | if GammaConfig.Debug { 195 | log.Printf("[>] Incoming %s on listener %s", conn.RemoteAddr(), addr) 196 | } 197 | defer conn.Close() 198 | _ = conn.SetDeadline(time.Now().Add(5 * time.Second)) 199 | if err := gateway.serve(conn, addr); err != nil { 200 | 201 | if GammaConfig.Debug { 202 | log.Printf("[x] %s closed connection with %s; error: %s", conn.RemoteAddr(), addr, err) 203 | } 204 | return 205 | } 206 | _ = conn.SetDeadline(time.Time{}) 207 | if GammaConfig.Debug { 208 | log.Printf("[x] %s closed connection with %s", conn.RemoteAddr(), addr) 209 | } 210 | }() 211 | } 212 | } 213 | 214 | func (gateway *Gateway) serve(conn net.Conn, addr string) (rerr error) { 215 | defer func() { 216 | if r := recover(); r != nil { 217 | switch x := r.(type) { 218 | case string: 219 | rerr = errors.New(x) 220 | case error: 221 | rerr = x 222 | default: 223 | rerr = errors.New("unknown panic in client handler") 224 | } 225 | } 226 | }() 227 | 228 | pc := protocol.ProcessedConn{ 229 | Conn: conn.(*raknet.Conn), 230 | RemoteAddr: conn.RemoteAddr(), 231 | } 232 | 233 | if gateway.ReceiveProxyProtocol { 234 | header, err := proxyproto.Read(bufio.NewReader(conn)) 235 | if err != nil { 236 | return err 237 | } 238 | pc.RemoteAddr = header.SourceAddr 239 | } 240 | 241 | b, err := pc.ReadPacket() 242 | if err != nil { 243 | return err 244 | } 245 | pc.NetworkBytes = b 246 | var reqpacket protocol.RequestNetworkSettings 247 | reqdecoder := protocol.NewDecoder(bytes.NewReader(b)) 248 | reqpks, err := reqdecoder.Decode() 249 | err = protocol.UnmarshalPacket(reqpks[0], &reqpacket) 250 | if err != nil { 251 | return err 252 | } 253 | 254 | netset := []byte{254, 12, 143, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0} 255 | _, err = conn.Write(netset) 256 | if err != nil { 257 | return err 258 | } 259 | 260 | loginPacket, err := pc.ReadPacket() 261 | if err != nil { 262 | return err 263 | } 264 | pc.ReadBytes = loginPacket 265 | 266 | decoder := protocol.NewDecoder(bytes.NewReader(loginPacket)) 267 | decoder.EnableCompression(protocol.FlateCompression{}) 268 | pks, err := decoder.Decode() 269 | if err != nil { 270 | return err 271 | } 272 | 273 | if len(pks) < 1 { 274 | return errors.New("no valid packets received") 275 | } 276 | 277 | var loginPk protocol.Login 278 | if err := protocol.UnmarshalPacket(pks[0], &loginPk); err != nil { 279 | return err 280 | } 281 | 282 | iData, cData, err := login.Parse(loginPk.ConnectionRequest) 283 | if err != nil { 284 | return err 285 | } 286 | pc.Username = iData.DisplayName 287 | pc.ServerAddr = cData.ServerAddress 288 | 289 | if strings.Contains(pc.ServerAddr, ":") { 290 | pc.ServerAddr, _, err = net.SplitHostPort(pc.ServerAddr) 291 | if err != nil { 292 | return err 293 | } 294 | } 295 | 296 | proxyUID := proxyUID(pc.ServerAddr, addr) 297 | if GammaConfig.Debug { 298 | log.Printf("[i] %s requests proxy with UID %s", pc.RemoteAddr, proxyUID) 299 | } 300 | 301 | v, ok := gateway.Proxies.Load(proxyUID) 302 | if !ok { 303 | v, ok = gateway.Proxies.Load(fmt.Sprintf("*@%s", addr)) 304 | if !ok { 305 | err = pc.Disconnect(GammaConfig.GenericJoinResponse) 306 | if err != nil { 307 | return err 308 | } 309 | return nil 310 | } 311 | } 312 | 313 | proxy := v.(*Proxy) 314 | handshakeCount.With(prometheus.Labels{"type": "login", "host": proxy.DomainName()}).Inc() 315 | 316 | if GammaConfig.Debug { 317 | log.Printf("[i] %s connecting through config %s", pc.RemoteAddr, proxy.DomainName()) 318 | } 319 | 320 | _ = conn.SetDeadline(time.Time{}) 321 | 322 | err = proxy.HandleLogin(pc) 323 | if err != nil { 324 | return err 325 | } 326 | 327 | return nil 328 | } 329 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lhridder/gamma 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.5.4 7 | github.com/golang-jwt/jwt/v4 v4.4.2 8 | github.com/golang/snappy v0.0.4 9 | github.com/klauspost/compress v1.15.11 10 | github.com/pires/go-proxyproto v0.6.2 11 | github.com/prometheus/client_golang v1.12.2 12 | github.com/sandertv/go-raknet v1.12.0 13 | gopkg.in/yaml.v2 v2.4.0 14 | ) 15 | 16 | require ( 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 19 | github.com/df-mc/atomic v1.10.0 // indirect 20 | github.com/golang/protobuf v1.5.2 // indirect 21 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 22 | github.com/prometheus/client_model v0.2.0 // indirect 23 | github.com/prometheus/common v0.32.1 // indirect 24 | github.com/prometheus/procfs v0.7.3 // indirect 25 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect 26 | google.golang.org/protobuf v1.26.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 37 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 38 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 39 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 40 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 41 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 42 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 43 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 44 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 45 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 46 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 47 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 48 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 49 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 50 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 51 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 52 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 53 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 54 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 55 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 56 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 57 | github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg= 58 | github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc= 59 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 60 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 61 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 62 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 63 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 64 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 65 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 66 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 67 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 68 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 69 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 70 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 71 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 72 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 73 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 74 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 75 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 76 | github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= 77 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 78 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 79 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 80 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 81 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 82 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 83 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 84 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 85 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 86 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 87 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 88 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 89 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 90 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 91 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 92 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 93 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 94 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 95 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 96 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 97 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 98 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 99 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 100 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 101 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 102 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 103 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 104 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 105 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 106 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 107 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 108 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 109 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 110 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 111 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 112 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 113 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 114 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 115 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 116 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 117 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 118 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 119 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 120 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 121 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 122 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 123 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 124 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 125 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 126 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 127 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 128 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 129 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 130 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 131 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 132 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 133 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 134 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 135 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 136 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 137 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 138 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 139 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 140 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 141 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 142 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 143 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 144 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 145 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 146 | github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= 147 | github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 148 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 149 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 150 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 151 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 152 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 153 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 154 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 155 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 156 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 157 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 158 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 159 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 160 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 161 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 162 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 163 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 164 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 165 | github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= 166 | github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= 167 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 168 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 169 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 170 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 171 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 172 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 173 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 174 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 175 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 176 | github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= 177 | github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 178 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 179 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 180 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 181 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 182 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 183 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 184 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 185 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 186 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= 187 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 188 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 189 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 190 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 191 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 192 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= 193 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 194 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 195 | github.com/sandertv/go-raknet v1.12.0 h1:olUzZlIJyX/pgj/mrsLCZYjKLNDsYiWdvQ4NIm3z0DA= 196 | github.com/sandertv/go-raknet v1.12.0/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y= 197 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 198 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 199 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 200 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 201 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 202 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 203 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 204 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 205 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 206 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 207 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 208 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 209 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 210 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 211 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 212 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 213 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 214 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 215 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 216 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 217 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 218 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 219 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 220 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 221 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 222 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 223 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 224 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 225 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 226 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 227 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 228 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 229 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 230 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 231 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 232 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 233 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 234 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 235 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 236 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 237 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 238 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 239 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 240 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 241 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 242 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 243 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 244 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 245 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 246 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 247 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 248 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 249 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 250 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 251 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 252 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 253 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 254 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 255 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 256 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 257 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 258 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 259 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 260 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 261 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 262 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 263 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 264 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 265 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 266 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 267 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 268 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 269 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 270 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 271 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 272 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 273 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 274 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 275 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 276 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 277 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 278 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 279 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 280 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 281 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 282 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 283 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 284 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 285 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 286 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 289 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 290 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 291 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 292 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 293 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 294 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 295 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 296 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 297 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 298 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 299 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 328 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 329 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 330 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 331 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= 332 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 333 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 334 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 335 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 336 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 337 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 338 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 339 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 340 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 341 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 342 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 343 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 344 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 345 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 346 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 347 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 348 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 349 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 350 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 351 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 352 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 353 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 354 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 355 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 356 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 357 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 358 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 359 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 360 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 361 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 362 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 363 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 364 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 365 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 366 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 367 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 368 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 369 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 370 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 371 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 372 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 373 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 374 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 375 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 376 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 377 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 378 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 379 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 380 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 381 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 382 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 383 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 384 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 385 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 386 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 387 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 388 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 389 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 390 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 391 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 392 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 393 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 394 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 395 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 396 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 397 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 398 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 399 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 400 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 401 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 402 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 403 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 404 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 405 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 406 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 407 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 408 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 409 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 410 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 411 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 412 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 413 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 414 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 415 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 416 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 417 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 418 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 419 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 420 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 421 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 422 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 423 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 424 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 425 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 426 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 427 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 428 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 429 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 430 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 431 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 432 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 433 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 434 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 435 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 436 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 437 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 438 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 439 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 440 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 441 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 442 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 443 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 444 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 445 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 446 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 447 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 448 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 449 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 450 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 451 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 452 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 453 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 454 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 455 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 456 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 457 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 458 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 459 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 460 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 461 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 462 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 463 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 464 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 465 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 466 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 467 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 468 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 469 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 470 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 471 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 472 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 473 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 474 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 475 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 476 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 477 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 478 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 479 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 480 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 481 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 482 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 483 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 484 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 485 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 486 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 487 | -------------------------------------------------------------------------------- /protocol/compression.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/golang/snappy" 7 | "github.com/klauspost/compress/flate" 8 | "io" 9 | "sync" 10 | ) 11 | 12 | // Compression represents a compression algorithm that can compress and decompress data. 13 | type Compression interface { 14 | // EncodeCompression encodes the compression algorithm into a uint16 ID. 15 | EncodeCompression() uint16 16 | // Compress compresses the given data and returns the compressed data. 17 | Compress(decompressed []byte) ([]byte, error) 18 | // Decompress decompresses the given data and returns the decompressed data. 19 | Decompress(compressed []byte) ([]byte, error) 20 | } 21 | 22 | type ( 23 | // FlateCompression is the implementation of the Flate compression algorithm. This was used by default until v1.19.30. 24 | FlateCompression struct{} 25 | // SnappyCompression is the implementation of the Snappy compression algorithm. This is used by default. 26 | SnappyCompression struct{} 27 | ) 28 | 29 | // flateDecompressPool is a sync.Pool for io.ReadCloser flate readers. These are 30 | // pooled for connections. 31 | var ( 32 | flateDecompressPool = sync.Pool{ 33 | New: func() any { return flate.NewReader(bytes.NewReader(nil)) }, 34 | } 35 | flateCompressPool = sync.Pool{ 36 | New: func() any { 37 | w, _ := flate.NewWriter(io.Discard, 6) 38 | return w 39 | }, 40 | } 41 | ) 42 | 43 | // EncodeCompression ... 44 | func (FlateCompression) EncodeCompression() uint16 { 45 | return 0 46 | } 47 | 48 | // Compress ... 49 | func (FlateCompression) Compress(decompressed []byte) ([]byte, error) { 50 | compressed := BufferPool.Get().(*bytes.Buffer) 51 | w := flateCompressPool.Get().(*flate.Writer) 52 | 53 | defer func() { 54 | // Reset the buffer, so we can return it to the buffer pool safely. 55 | compressed.Reset() 56 | BufferPool.Put(compressed) 57 | flateCompressPool.Put(w) 58 | }() 59 | 60 | w.Reset(compressed) 61 | 62 | _, err := w.Write(decompressed) 63 | if err != nil { 64 | return nil, fmt.Errorf("compress flate: %w", err) 65 | } 66 | err = w.Close() 67 | if err != nil { 68 | return nil, fmt.Errorf("close flate writer: %w", err) 69 | } 70 | return compressed.Bytes(), nil 71 | } 72 | 73 | // Decompress ... 74 | func (FlateCompression) Decompress(compressed []byte) ([]byte, error) { 75 | buf := bytes.NewReader(compressed) 76 | c := flateDecompressPool.Get().(io.ReadCloser) 77 | defer flateDecompressPool.Put(c) 78 | 79 | if err := c.(flate.Resetter).Reset(buf, nil); err != nil { 80 | return nil, fmt.Errorf("reset flate: %w", err) 81 | } 82 | _ = c.Close() 83 | 84 | // Guess an uncompressed size of 2*len(compressed). 85 | decompressed := bytes.NewBuffer(make([]byte, 0, len(compressed)*2)) 86 | if _, err := io.Copy(decompressed, c); err != nil { 87 | return nil, fmt.Errorf("decompress flate: %v", err) 88 | } 89 | return decompressed.Bytes(), nil 90 | } 91 | 92 | // EncodeCompression ... 93 | func (SnappyCompression) EncodeCompression() uint16 { 94 | return 1 95 | } 96 | 97 | // Compress ... 98 | func (SnappyCompression) Compress(decompressed []byte) ([]byte, error) { 99 | // Because Snappy allocates a slice only once, it is less important to have 100 | // a dst slice pre-allocated. With FlateCompression this is more important, 101 | // because flate does a lot of smaller allocations which causes a 102 | // considerable slowdown. 103 | return snappy.Encode(nil, decompressed), nil 104 | } 105 | 106 | // Decompress ... 107 | func (SnappyCompression) Decompress(compressed []byte) ([]byte, error) { 108 | // Snappy writes a decoded data length prefix, so it can allocate the 109 | // perfect size right away and only needs to allocate once. No need to pool 110 | // byte slices here either. 111 | decompressed, err := snappy.Decode(nil, compressed) 112 | if err != nil { 113 | return nil, fmt.Errorf("decompress snappy: %w", err) 114 | } 115 | return decompressed, nil 116 | } 117 | 118 | // init registers all valid compressions with the protocol. 119 | func init() { 120 | RegisterCompression(FlateCompression{}) 121 | RegisterCompression(SnappyCompression{}) 122 | } 123 | 124 | var compressions = map[uint16]Compression{} 125 | 126 | // RegisterCompression registers a compression so that it can be used by the protocol. 127 | func RegisterCompression(compression Compression) { 128 | compressions[compression.EncodeCompression()] = compression 129 | } 130 | 131 | // CompressionByID attempts to return a compression by the ID it was registered with. If found, the compression found 132 | // is returned and the bool is true. 133 | func CompressionByID(id uint16) (Compression, bool) { 134 | c, ok := compressions[id] 135 | return c, ok 136 | } 137 | -------------------------------------------------------------------------------- /protocol/conn.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "github.com/sandertv/go-raknet" 5 | "net" 6 | ) 7 | 8 | type ProcessedConn struct { 9 | *raknet.Conn 10 | RemoteAddr net.Addr 11 | ServerAddr string 12 | Username string 13 | NetworkBytes []byte 14 | ReadBytes []byte 15 | } 16 | 17 | func (c ProcessedConn) Disconnect(msg string) error { 18 | defer c.Close() 19 | pk := Disconnect{ 20 | HideDisconnectionScreen: msg == "", 21 | Message: msg, 22 | } 23 | 24 | b, err := MarshalPacket(&pk) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | if _, err := c.Write(b); err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /protocol/decoder.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | const ( 11 | // header is the header of compressed 'batches' from Minecraft. 12 | header = 0xfe 13 | // maximumInBatch is the maximum amount of packets that may be found in a batch. If a compressed batch has 14 | // more than this amount, decoding will fail. 15 | maximumInBatch = 512 + 256 16 | ) 17 | 18 | type Decoder struct { 19 | // r holds the io.Reader that packets are read from if the reader does not implement packetReader. When 20 | // this is the case, the buf field has a non-zero length. 21 | r io.Reader 22 | buf []byte 23 | compression Compression 24 | } 25 | 26 | // NewDecoder returns a new decoder decoding data from the io.Reader passed. One read call from the reader is 27 | // assumed to consume an entire packet. 28 | func NewDecoder(reader io.Reader) *Decoder { 29 | return &Decoder{ 30 | r: reader, 31 | buf: make([]byte, 1024*1024*3), 32 | } 33 | } 34 | 35 | // Decode decodes one 'packet' from the io.Reader passed in NewDecoder(), producing a slice of packets that it 36 | // held and an error if not successful. 37 | func (decoder *Decoder) Decode() (packets [][]byte, err error) { 38 | n, err := decoder.r.Read(decoder.buf) 39 | if err != nil { 40 | return nil, fmt.Errorf("error reading batch from reader: %v", err) 41 | } 42 | data := decoder.buf[:n] 43 | 44 | if len(data) == 0 { 45 | return nil, nil 46 | } 47 | if data[0] != header { 48 | return nil, fmt.Errorf("error reading packet: invalid packet header %x: expected %x", data[0], header) 49 | } 50 | data = data[1:] 51 | 52 | if decoder.compression != nil { 53 | data, err = decoder.compression.Decompress(data) 54 | if err != nil { 55 | return nil, fmt.Errorf("error decompressing packet: %v", err) 56 | } 57 | } 58 | b := bytes.NewBuffer(data) 59 | for b.Len() != 0 { 60 | var length uint32 61 | if err := Varuint32(b, &length); err != nil { 62 | return nil, fmt.Errorf("error reading packet length: %v", err) 63 | } 64 | packets = append(packets, b.Next(int(length))) 65 | } 66 | if len(packets) > maximumInBatch { 67 | return nil, fmt.Errorf("number of packets %v in compressed batch exceeds %v", len(packets), maximumInBatch) 68 | } 69 | return packets, nil 70 | } 71 | 72 | func Varuint32(src io.ByteReader, x *uint32) error { 73 | var v uint32 74 | for i := uint(0); i < 35; i += 7 { 75 | b, err := src.ReadByte() 76 | if err != nil { 77 | return err 78 | } 79 | v |= uint32(b&0x7f) << i 80 | if b&0x80 == 0 { 81 | *x = v 82 | return nil 83 | } 84 | } 85 | return errors.New("varuint32 did not terminate after 5 bytes") 86 | } 87 | 88 | // EnableCompression enables compression for the Decoder. 89 | func (decoder *Decoder) EnableCompression(compression Compression) { 90 | decoder.compression = compression 91 | } 92 | -------------------------------------------------------------------------------- /protocol/disconnect.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "log" 4 | 5 | // Disconnect may be sent by the server to disconnect the client using an optional message to send as the 6 | // disconnect screen. 7 | type Disconnect struct { 8 | // HideDisconnectionScreen specifies if the disconnection screen should be hidden when the client is 9 | // disconnected, meaning it will be sent directly to the main menu. 10 | HideDisconnectionScreen bool 11 | // Message is an optional message to show when disconnected. This message is only written if the 12 | // HideDisconnectionScreen field is set to true. 13 | Message string 14 | } 15 | 16 | // ID ... 17 | func (*Disconnect) ID() uint32 { 18 | return 0x05 19 | } 20 | 21 | // Marshal ... 22 | func (pk *Disconnect) Marshal(w *Writer) { 23 | w.Bool(pk.HideDisconnectionScreen) 24 | if !pk.HideDisconnectionScreen { 25 | w.String(pk.Message) 26 | } 27 | } 28 | 29 | // Unmarshal ... 30 | func (pk *Disconnect) Unmarshal(buf *Reader) error { 31 | log.Fatal("not implemented yet") 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /protocol/encoder.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "sync" 8 | ) 9 | 10 | // Encoder handles the encoding of Minecraft packets that are sent to an io.Writer. The packets are compressed 11 | // and optionally encoded before they are sent to the io.Writer. 12 | type Encoder struct { 13 | w io.Writer 14 | compression Compression 15 | } 16 | 17 | // NewEncoder returns a new Encoder for the io.Writer passed. Each final packet produced by the Encoder is 18 | // sent with a single call to io.Writer.Write(). 19 | func NewEncoder(w io.Writer) *Encoder { 20 | return &Encoder{ 21 | w: w, 22 | } 23 | } 24 | 25 | // writeCloseResetter is an interface composed of an io.WriteCloser and a Reset(io.Writer) method. 26 | type writeCloseResetter interface { 27 | io.WriteCloser 28 | Reset(w io.Writer) 29 | } 30 | 31 | // EnableCompression enables compression for the Encoder. 32 | func (encoder *Encoder) EnableCompression(compression Compression) { 33 | encoder.compression = compression 34 | } 35 | 36 | // Encode encodes the packet passed and compresses it. 37 | func (encoder *Encoder) Encode(packet []byte) error { 38 | buf := BufferPool.Get().(*bytes.Buffer) 39 | defer func() { 40 | // Reset the buffer so we can return it to the buffer pool safely. 41 | buf.Reset() 42 | BufferPool.Put(buf) 43 | }() 44 | if err := buf.WriteByte(header); err != nil { 45 | return fmt.Errorf("error writing 0xfe header: %v", err) 46 | } 47 | 48 | data := buf.Bytes() 49 | if encoder.compression != nil { 50 | var err error 51 | data, err = encoder.compression.Compress(data) 52 | if err != nil { 53 | return fmt.Errorf("error compressing packet: %v", err) 54 | } 55 | } 56 | 57 | l := make([]byte, 5) 58 | 59 | // Each packet is prefixed with a varuint32 specifying the length of the packet. 60 | if err := writeVaruint32(encoder.w, uint32(len(packet)), l); err != nil { 61 | return fmt.Errorf("error writing varuint32 length: %v", err) 62 | } 63 | if _, err := encoder.w.Write(packet); err != nil { 64 | return fmt.Errorf("error writing packet payload: %v", err) 65 | } 66 | 67 | if _, err := encoder.w.Write(data); err != nil { 68 | return fmt.Errorf("error writing compressed packet to io.Writer: %v", err) 69 | } 70 | return nil 71 | } 72 | 73 | // writeVaruint32 writes a uint32 to the destination buffer passed with a size of 1-5 bytes. It uses byte 74 | // slice b in order to prevent allocations. 75 | func writeVaruint32(dst io.Writer, x uint32, b []byte) error { 76 | b[4] = 0 77 | b[3] = 0 78 | b[2] = 0 79 | b[1] = 0 80 | b[0] = 0 81 | 82 | i := 0 83 | for x >= 0x80 { 84 | b[i] = byte(x) | 0x80 85 | i++ 86 | x >>= 7 87 | } 88 | b[i] = byte(x) 89 | _, err := dst.Write(b[:i+1]) 90 | return err 91 | } 92 | 93 | // BufferPool is a sync.Pool for buffers used to write compressed data to. 94 | var BufferPool = sync.Pool{ 95 | New: func() interface{} { 96 | return bytes.NewBuffer(make([]byte, 0, 256)) 97 | }, 98 | } 99 | -------------------------------------------------------------------------------- /protocol/login.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "log" 4 | 5 | // Login is sent when the client initially tries to join the server. It is the first packet sent and contains 6 | // information specific to the player. 7 | type Login struct { 8 | // ClientProtocol is the protocol version of the player. The player is disconnected if the protocol is 9 | // incompatible with the protocol of the server. 10 | ClientProtocol int32 11 | // ConnectionRequest is a string containing information about the player and JWTs that may be used to 12 | // verify if the player is connected to XBOX Live. The connection request also contains the necessary 13 | // client public key to initiate encryption. 14 | ConnectionRequest []byte 15 | } 16 | 17 | // RequestNetworkSettings is sent by the client to request network settings, such as compression, from the server. 18 | type RequestNetworkSettings struct { 19 | // ClientProtocol is the protocol version of the player. The player is disconnected if the protocol is 20 | // incompatible with the protocol of the server. 21 | ClientProtocol int32 22 | } 23 | 24 | func (pk *Login) ID() uint32 { 25 | return 0x01 26 | } 27 | 28 | func (pk *Login) Unmarshal(r *Reader) error { 29 | if err := r.BEInt32(&pk.ClientProtocol); err != nil { 30 | return err 31 | } 32 | if err := r.ByteSlice(&pk.ConnectionRequest); err != nil { 33 | return err 34 | } 35 | return nil 36 | } 37 | 38 | // Marshal ... 39 | func (pk *Login) Marshal(buf *Writer) { 40 | log.Fatal("not implemented yet") 41 | } 42 | 43 | // ID ... 44 | func (pk *RequestNetworkSettings) ID() uint32 { 45 | return 0xC1 46 | } 47 | 48 | // Marshal ... 49 | func (pk *RequestNetworkSettings) Marshal(w *Writer) { 50 | w.BEInt32(&pk.ClientProtocol) 51 | } 52 | 53 | // Unmarshal ... 54 | func (pk *RequestNetworkSettings) Unmarshal(r *Reader) error { 55 | err := r.BEInt32(&pk.ClientProtocol) 56 | if err != nil { 57 | return err 58 | } 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /protocol/login/data.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import "github.com/golang-jwt/jwt/v4" 4 | 5 | // IdentityData contains identity data of the player logged in. It is found in one of the JWT claims signed 6 | // by Mojang, and can thus be trusted. 7 | type IdentityData struct { 8 | // DisplayName is the username of the player, which may be changed by the user. It should for that reason 9 | // not be used as a key to store information. 10 | DisplayName string `json:"displayName"` 11 | } 12 | 13 | // ClientData is a container of client specific data of a Login packet. It holds data such as the skin of a 14 | // player, but also its language code and device information. 15 | type ClientData struct { 16 | jwt.RegisteredClaims 17 | // ServerAddress is the exact address the player used to join the server with. This may be either an 18 | // actual address, or a hostname. ServerAddress also has the port in it, in the shape of 19 | // 'address:port`. 20 | ServerAddress string 21 | } 22 | -------------------------------------------------------------------------------- /protocol/login/login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "fmt" 8 | 9 | "github.com/golang-jwt/jwt/v4" 10 | ) 11 | 12 | // chain holds a chain with claims, each with their own headers, payloads and signatures. Each claim holds 13 | // a public key used to verify other claims. 14 | type chain []string 15 | 16 | // request is the outer encapsulation of the request. It holds a chain and a ClientData object. 17 | type request struct { 18 | // Chain is the client certificate chain. It holds several claims that the server may verify in order to 19 | // make sure that the client is logged into XBOX Live. 20 | Chain chain `json:"chain"` 21 | // RawToken holds the raw token that follows the JWT chain, holding the ClientData. 22 | RawToken string `json:"-"` 23 | } 24 | 25 | func Parse(request []byte) (IdentityData, ClientData, error) { 26 | req, err := parseLoginRequest(request) 27 | if err != nil { 28 | return IdentityData{}, ClientData{}, fmt.Errorf("parse login request: %w", err) 29 | } 30 | 31 | jwtParser := jwt.Parser{} 32 | var identityClaims identityClaims 33 | switch len(req.Chain) { 34 | case 1: 35 | // Player was not authenticated with XBOX Live, meaning the one token in here is self-signed. 36 | _, _, err = jwtParser.ParseUnverified(req.Chain[2], &identityClaims) 37 | if err != nil { 38 | return IdentityData{}, ClientData{}, err 39 | } 40 | if err := identityClaims.Valid(); err != nil { 41 | return IdentityData{}, ClientData{}, fmt.Errorf("validate token 0: %w", err) 42 | } 43 | case 3: 44 | // Player was (or should be) authenticated with XBOX Live, meaning the chain is exactly 3 tokens 45 | // long. 46 | var c jwt.RegisteredClaims 47 | _, _, err := jwtParser.ParseUnverified(req.Chain[0], &c) 48 | if err != nil { 49 | return IdentityData{}, ClientData{}, fmt.Errorf("parse token 0: %w", err) 50 | } 51 | 52 | _, _, err = jwtParser.ParseUnverified(req.Chain[1], &c) 53 | if err != nil { 54 | return IdentityData{}, ClientData{}, fmt.Errorf("parse token 1: %w", err) 55 | } 56 | _, _, err = jwtParser.ParseUnverified(req.Chain[2], &identityClaims) 57 | if err != nil { 58 | return IdentityData{}, ClientData{}, fmt.Errorf("parse token 2: %w", err) 59 | } 60 | default: 61 | return IdentityData{}, ClientData{}, fmt.Errorf("unexpected login chain length %v", len(req.Chain)) 62 | } 63 | 64 | var cData ClientData 65 | _, _, err = jwtParser.ParseUnverified(req.RawToken, &cData) 66 | if err != nil { 67 | return IdentityData{}, cData, fmt.Errorf("parse client data: %w", err) 68 | } 69 | 70 | return identityClaims.ExtraData, cData, nil 71 | } 72 | 73 | // parseLoginRequest parses the structure of a login request from the data passed and returns it. 74 | func parseLoginRequest(requestData []byte) (*request, error) { 75 | buf := bytes.NewBuffer(requestData) 76 | chain, err := decodeChain(buf) 77 | if err != nil { 78 | return nil, err 79 | } 80 | if len(chain) < 1 { 81 | return nil, fmt.Errorf("JWT chain must be at least 1 token long") 82 | } 83 | var rawLength int32 84 | if err := binary.Read(buf, binary.LittleEndian, &rawLength); err != nil { 85 | return nil, fmt.Errorf("error reading raw token length: %v", err) 86 | } 87 | return &request{Chain: chain, RawToken: string(buf.Next(int(rawLength)))}, nil 88 | } 89 | 90 | // decodeChain reads a certificate chain from the buffer passed and returns each claim found in the chain. 91 | func decodeChain(buf *bytes.Buffer) (chain, error) { 92 | var chainLength int32 93 | if err := binary.Read(buf, binary.LittleEndian, &chainLength); err != nil { 94 | return nil, fmt.Errorf("error reading chain length: %v", err) 95 | } 96 | chainData := buf.Next(int(chainLength)) 97 | 98 | request := &request{} 99 | if err := json.Unmarshal(chainData, request); err != nil { 100 | return nil, fmt.Errorf("error decoding request chain JSON: %v", err) 101 | } 102 | // First check if the chain actually has any elements in it. 103 | if len(request.Chain) == 0 { 104 | return nil, fmt.Errorf("connection request had no claims in the chain") 105 | } 106 | return request.Chain, nil 107 | } 108 | 109 | // identityClaims holds the claims for the last token in the chain, which contains the IdentityData of the 110 | // player. 111 | type identityClaims struct { 112 | jwt.RegisteredClaims 113 | 114 | // ExtraData holds the extra data of this claim, which is the IdentityData of the player. 115 | ExtraData IdentityData `json:"extraData"` 116 | 117 | IdentityPublicKey string `json:"identityPublicKey"` 118 | } 119 | -------------------------------------------------------------------------------- /protocol/packet.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | type Header struct { 10 | PacketID uint32 11 | SenderSubClient byte 12 | TargetSubClient byte 13 | } 14 | 15 | func (header *Header) Write(w io.ByteWriter) error { 16 | x := header.PacketID | (uint32(header.SenderSubClient) << 10) | (uint32(header.TargetSubClient) << 12) 17 | for x >= 0x80 { 18 | if err := w.WriteByte(byte(x) | 0x80); err != nil { 19 | return err 20 | } 21 | x >>= 7 22 | } 23 | return w.WriteByte(byte(x)) 24 | } 25 | 26 | func (header *Header) Read(r io.ByteReader) error { 27 | var value uint32 28 | if err := Varuint32(r, &value); err != nil { 29 | return err 30 | } 31 | header.PacketID = value & 0x3FF 32 | header.SenderSubClient = byte((value >> 10) & 0x3) 33 | header.TargetSubClient = byte((value >> 12) & 0x3) 34 | return nil 35 | } 36 | 37 | func UnmarshalPacket(b []byte, pk Packet) error { 38 | data, err := parseData(b) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | if data.h.PacketID != pk.ID() { 44 | return fmt.Errorf("invalid id: 0x%x", data.h.PacketID) 45 | } 46 | 47 | return data.decode(pk) 48 | } 49 | 50 | func MarshalPacket(pk Packet) ([]byte, error) { 51 | buf := bytes.NewBuffer([]byte{}) 52 | w := NewWriter(buf) 53 | 54 | header := Header{PacketID: pk.ID()} 55 | _ = header.Write(w) 56 | pk.Marshal(w) 57 | 58 | encodedPk := bytes.NewBuffer([]byte{}) 59 | encoder := NewEncoder(encodedPk) 60 | if err := encoder.Encode(buf.Bytes()); err != nil { 61 | return nil, err 62 | } 63 | 64 | return encodedPk.Bytes(), nil 65 | } 66 | 67 | type packetData struct { 68 | h *Header 69 | full []byte 70 | payload *bytes.Buffer 71 | } 72 | 73 | // parseData parses the packet data slice passed into a packetData struct. 74 | func parseData(data []byte) (*packetData, error) { 75 | buf := bytes.NewBuffer(data) 76 | header := &Header{} 77 | if err := header.Read(buf); err != nil { 78 | // We don't return this as an error as it's not in the hand of the user to control this. Instead, 79 | // we return to reading a new packet. 80 | return nil, fmt.Errorf("error reading packet header: %v", err) 81 | } 82 | return &packetData{h: header, full: data, payload: buf}, nil 83 | } 84 | 85 | // Packet represents a packet that may be sent over a Minecraft network connection. The packet needs to hold 86 | // a method to encode itself to binary and decode itself from binary. 87 | type Packet interface { 88 | // ID returns the ID of the packet. All of these identifiers of packets may be found in id.go. 89 | ID() uint32 90 | // Unmarshal decodes a serialised packet in buf into the Packet instance. The serialised packet passed 91 | // into Unmarshal will not have a header in it. 92 | Unmarshal(r *Reader) error 93 | Marshal(w *Writer) 94 | } 95 | 96 | // decode decodes the packet payload held in the packetData and returns the packet.Packet decoded. 97 | func (p *packetData) decode(pk Packet) error { 98 | if err := pk.Unmarshal(NewReader(p.payload)); err != nil { 99 | return err 100 | } 101 | if p.payload.Len() != 0 { 102 | return fmt.Errorf("%T: %v unread bytes left: 0x%x", pk, p.payload.Len(), p.payload.Bytes()) 103 | } 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /protocol/reader.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | type DecodeReader interface { 10 | io.Reader 11 | io.ByteReader 12 | } 13 | 14 | type Reader struct { 15 | DecodeReader 16 | } 17 | 18 | func NewReader(r DecodeReader) *Reader { 19 | return &Reader{DecodeReader: r} 20 | } 21 | 22 | func (r *Reader) BEInt32(x *int32) error { 23 | b := make([]byte, 4) 24 | if _, err := r.Read(b); err != nil { 25 | return err 26 | } 27 | *x = int32(binary.BigEndian.Uint32(b)) 28 | return nil 29 | } 30 | 31 | func (r *Reader) ByteSlice(x *[]byte) error { 32 | var length uint32 33 | r.Varuint32(&length) 34 | l := int(length) 35 | int32max := 1<<31 - 1 36 | if l > int32max { 37 | return errors.New("byte slice overflows int32") 38 | } 39 | data := make([]byte, l) 40 | if _, err := r.Read(data); err != nil { 41 | return err 42 | } 43 | *x = data 44 | return nil 45 | } 46 | 47 | func (r *Reader) Varuint32(x *uint32) error { 48 | var v uint32 49 | for i := 0; i < 35; i += 7 { 50 | b, err := r.ReadByte() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | v |= uint32(b&0x7f) << i 56 | if b&0x80 == 0 { 57 | *x = v 58 | return nil 59 | } 60 | } 61 | return errors.New("varint overflows int32") 62 | } 63 | -------------------------------------------------------------------------------- /protocol/writer.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "io" 5 | "unsafe" 6 | ) 7 | 8 | type EncodeReader interface { 9 | io.Writer 10 | io.ByteWriter 11 | } 12 | 13 | type Writer struct { 14 | EncodeReader 15 | } 16 | 17 | func NewWriter(w EncodeReader) *Writer { 18 | return &Writer{EncodeReader: w} 19 | } 20 | 21 | func (w *Writer) Bool(x bool) { 22 | if x { 23 | w.WriteByte(0x01) 24 | } else { 25 | w.WriteByte(0x00) 26 | } 27 | } 28 | 29 | func (w *Writer) String(x string) { 30 | l := uint32(len(x)) 31 | w.Varuint32(l) 32 | _, _ = w.Write([]byte(x)) 33 | } 34 | 35 | func (w *Writer) Varuint32(x uint32) { 36 | for x >= 0x80 { 37 | _ = w.WriteByte(byte(x) | 0x80) 38 | x >>= 7 39 | } 40 | _ = w.WriteByte(byte(x)) 41 | } 42 | 43 | // BEInt32 writes a big endian int32 to the underlying buffer. 44 | func (w *Writer) BEInt32(x *int32) { 45 | data := *(*[4]byte)(unsafe.Pointer(x)) 46 | _, _ = w.Write(data[:]) 47 | } 48 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package gamma 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lhridder/gamma/protocol" 6 | "github.com/pires/go-proxyproto" 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/prometheus/client_golang/prometheus/promauto" 9 | "github.com/sandertv/go-raknet" 10 | "log" 11 | "net" 12 | "strings" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | var ( 18 | playersConnected = promauto.NewGaugeVec(prometheus.GaugeOpts{ 19 | Name: "gamma_connected", 20 | Help: "The total number of connected players", 21 | }, []string{"host"}) 22 | ) 23 | 24 | type Proxy struct { 25 | Config *ProxyConfig 26 | UID string 27 | mu sync.Mutex 28 | Dialer raknet.Dialer 29 | } 30 | 31 | func (proxy *Proxy) DomainNames() []string { 32 | proxy.Config.RLock() 33 | defer proxy.Config.RUnlock() 34 | return proxy.Config.Domains 35 | } 36 | 37 | func (proxy *Proxy) DomainName() string { 38 | proxy.Config.RLock() 39 | defer proxy.Config.RUnlock() 40 | return proxy.Config.Domains[0] 41 | } 42 | 43 | func (proxy *Proxy) ListenTo() string { 44 | proxy.Config.RLock() 45 | defer proxy.Config.RUnlock() 46 | return proxy.Config.ListenTo 47 | } 48 | 49 | func (proxy *Proxy) ProxyTo() string { 50 | proxy.Config.RLock() 51 | defer proxy.Config.RUnlock() 52 | return proxy.Config.ProxyTo 53 | } 54 | 55 | func (proxy *Proxy) DisconnectMessage() string { 56 | proxy.Config.RLock() 57 | defer proxy.Config.RUnlock() 58 | return proxy.Config.DialTimeoutMessage 59 | } 60 | 61 | func (proxy *Proxy) Timeout() time.Duration { 62 | proxy.Config.RLock() 63 | defer proxy.Config.RUnlock() 64 | return time.Duration(proxy.Config.DialTimeout) * time.Millisecond 65 | } 66 | 67 | func (proxy *Proxy) ProxyProtocol() bool { 68 | proxy.Config.RLock() 69 | defer proxy.Config.RUnlock() 70 | return proxy.Config.SendProxyProtocol 71 | } 72 | 73 | func (proxy *Proxy) ProxyBind() string { 74 | proxy.Config.RLock() 75 | defer proxy.Config.RUnlock() 76 | return proxy.Config.ProxyBind 77 | } 78 | 79 | func (proxy *Proxy) UIDs() []string { 80 | var uids []string 81 | for _, domain := range proxy.DomainNames() { 82 | uid := proxyUID(domain, proxy.ListenTo()) 83 | uids = append(uids, uid) 84 | } 85 | return uids 86 | } 87 | 88 | func proxyUID(domain, addr string) string { 89 | return fmt.Sprintf("%s@%s", strings.ToLower(domain), addr) 90 | } 91 | 92 | func (proxy *Proxy) Dial() (*raknet.Conn, error) { 93 | c, err := proxy.Dialer.Dial(proxy.Config.ProxyTo) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return c, err 98 | } 99 | 100 | type proxyProtocolDialer struct { 101 | connAddr net.Addr 102 | upstreamDialer raknet.UpstreamDialer 103 | } 104 | 105 | func (d proxyProtocolDialer) Dial(network, address string) (net.Conn, error) { 106 | rc, err := d.upstreamDialer.Dial(network, address) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | header := &proxyproto.Header{ 112 | Version: 2, 113 | Command: proxyproto.PROXY, 114 | TransportProtocol: proxyproto.UDPv4, 115 | SourceAddr: d.connAddr.(*net.UDPAddr), 116 | DestinationAddr: rc.RemoteAddr(), 117 | } 118 | 119 | if _, err = header.WriteTo(rc); err != nil { 120 | return rc, err 121 | } 122 | 123 | return rc, nil 124 | } 125 | 126 | func (proxy *Proxy) HandleLogin(conn protocol.ProcessedConn) error { 127 | if proxy.ProxyProtocol() { 128 | proxy.Dialer = raknet.Dialer{ 129 | UpstreamDialer: &net.Dialer{ 130 | Timeout: 5 * time.Second, 131 | LocalAddr: &net.UDPAddr{ 132 | IP: net.ParseIP(proxy.ProxyBind()), 133 | }, 134 | }, 135 | } 136 | proxy.Dialer.UpstreamDialer = &proxyProtocolDialer{ 137 | connAddr: conn.RemoteAddr, 138 | upstreamDialer: proxy.Dialer.UpstreamDialer, 139 | } 140 | } 141 | 142 | rc, err := proxy.Dial() 143 | if err != nil { 144 | log.Printf("[i] %s did not respond to ping; is the target offline?", proxy.ProxyTo()) 145 | err := conn.Disconnect(proxy.DisconnectMessage()) 146 | if err != nil { 147 | return err 148 | } 149 | return nil 150 | } 151 | defer rc.Close() 152 | 153 | if _, err := rc.Write(conn.NetworkBytes); err != nil { 154 | rc.Close() 155 | return err 156 | } 157 | 158 | _, err = rc.ReadPacket() 159 | if err != nil { 160 | return err 161 | } 162 | 163 | if _, err := rc.Write(conn.ReadBytes); err != nil { 164 | rc.Close() 165 | return err 166 | } 167 | playersConnected.With(prometheus.Labels{"host": proxy.DomainName()}).Inc() 168 | defer playersConnected.With(prometheus.Labels{"host": proxy.DomainName()}).Dec() 169 | 170 | go func() { 171 | for { 172 | pk, err := rc.ReadPacket() 173 | if err != nil { 174 | return 175 | } 176 | _, err = conn.Write(pk) 177 | if err != nil { 178 | return 179 | } 180 | } 181 | }() 182 | for { 183 | pk, err := conn.ReadPacket() 184 | if err != nil { 185 | return err 186 | } 187 | _, err = rc.Write(pk) 188 | if err != nil { 189 | return err 190 | } 191 | } 192 | } 193 | --------------------------------------------------------------------------------