├── .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 |
--------------------------------------------------------------------------------