├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bedrock ├── config.go ├── conn.go ├── conn_processer.go ├── gateway.go ├── protocol │ ├── decoder.go │ ├── disconnect.go │ ├── encoder.go │ ├── login.go │ ├── login │ │ ├── data.go │ │ └── login.go │ ├── packet.go │ ├── reader.go │ └── writer.go └── server.go ├── cmd └── bedprox │ ├── config.go │ ├── config.yml │ └── main.go ├── config.go ├── conn.go ├── cpn.go ├── docker-compose.yml ├── event.go ├── gateway.go ├── go.mod ├── go.sum ├── pool.go ├── proxy.go ├── server.go └── webhook ├── event.go ├── event_test.go ├── webhook.go └── webhook_test.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 | .vscode -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang AS builder 2 | LABEL stage=intermediate 3 | COPY . /bedprox 4 | WORKDIR /bedprox/cmd/bedprox 5 | ENV GO111MODULE=on 6 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /main . 7 | 8 | FROM alpine:latest 9 | LABEL maintainer="Hendrik Jonas Schlehlein " 10 | RUN apk --no-cache add ca-certificates 11 | WORKDIR / 12 | COPY --from=builder /main ./ 13 | RUN chmod +x ./main 14 | ENTRYPOINT [ "./main" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BedProx **[WIP]** 2 | Simple Minecraft Bedrock reverse proxy 3 | 4 | ## Features 5 | - [x] Reverse Proxy 6 | - [x] HAProxy Protocol Support (NOT TESTED) 7 | - [ ] Webhooks 8 | - [ ] REST API 9 | 10 | ## How to use/deploy 11 | ``` 12 | git clone https://github.com/haveachin/bedprox 13 | cd bedprox 14 | docker-compose up -build 15 | ``` 16 | Config can be found in the data/bedprox folder 17 | 18 | ## Sources 19 | https://github.com/Sandertv/gophertunnel/tree/f8b092d0378cc7fdbf87640dae4def2d06fddeb4 20 | https://wiki.vg/Raknet_Protocol 21 | https://wiki.vg/Bedrock_Protocol -------------------------------------------------------------------------------- /bedrock/config.go: -------------------------------------------------------------------------------- 1 | package bedrock 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/haveachin/bedprox" 10 | "github.com/haveachin/bedprox/webhook" 11 | "github.com/sandertv/go-raknet" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | type Config struct{} 16 | 17 | type pingStatusConfig struct { 18 | Edition string `mapstructure:"edition"` 19 | ProtocolVersion int `mapstructure:"protocol_version,omitempty"` 20 | VersionName string `mapstructure:"version_name,omitempty"` 21 | PlayerCount int `mapstructure:"player_count,omitempty"` 22 | MaxPlayerCount int `mapstructure:"max_player_count,omitempty"` 23 | GameMode string `mapstructure:"game_mode"` 24 | GameModeNumeric int `mapstructure:"game_mode_numeric"` 25 | MOTD string `mapstructure:"motd,omitempty"` 26 | } 27 | 28 | func newPingStatus(cfg pingStatusConfig) PingStatus { 29 | return PingStatus{ 30 | Edition: cfg.Edition, 31 | ProtocolVersion: cfg.ProtocolVersion, 32 | VersionName: cfg.VersionName, 33 | PlayerCount: cfg.PlayerCount, 34 | MaxPlayerCount: cfg.MaxPlayerCount, 35 | GameMode: cfg.GameMode, 36 | GameModeNumeric: cfg.GameModeNumeric, 37 | MOTD: cfg.MOTD, 38 | } 39 | } 40 | 41 | type listenerConfig struct { 42 | Bind string `mapstructure:"bind"` 43 | PingStatus pingStatusConfig `mapstructure:"ping_status"` 44 | ReceiveProxyProtocol bool `mapstructure:"receive_proxy_protocol"` 45 | ReceiveRealIP bool `mapstructure:"receive_real_ip"` 46 | } 47 | 48 | func newListener(cfg listenerConfig) Listener { 49 | return Listener{ 50 | Bind: cfg.Bind, 51 | PingStatus: newPingStatus(cfg.PingStatus), 52 | ReceiveProxyProtocol: cfg.ReceiveProxyProtocol, 53 | ReceiveRealIP: cfg.ReceiveRealIP, 54 | } 55 | } 56 | 57 | func loadListeners(gatewayID string) ([]Listener, error) { 58 | key := fmt.Sprintf("gateways.%s.listeners", gatewayID) 59 | ll, ok := viper.Get(key).([]interface{}) 60 | if !ok { 61 | return nil, fmt.Errorf("gateway %q is missing listeners", gatewayID) 62 | } 63 | 64 | listeners := make([]Listener, len(ll)) 65 | for n := range ll { 66 | vpr := viper.Sub("defaults.gateway.listener") 67 | lKey := fmt.Sprintf("%s.%d", key, n) 68 | vMap := viper.GetStringMap(lKey) 69 | if err := vpr.MergeConfigMap(vMap); err != nil { 70 | return nil, err 71 | } 72 | var cfg listenerConfig 73 | if err := vpr.Unmarshal(&cfg); err != nil { 74 | return nil, err 75 | } 76 | listeners[n] = newListener(cfg) 77 | } 78 | return listeners, nil 79 | } 80 | 81 | type gatewayConfig struct { 82 | ClientTimeout time.Duration `mapstructure:"client_timeout"` 83 | Servers []string `mapstructure:"servers"` 84 | ServerNotFoundMessage string `mapstructure:"server_not_found_message"` 85 | } 86 | 87 | func newGateway(id string, cfg gatewayConfig) (bedprox.Gateway, error) { 88 | listeners, err := loadListeners(id) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return &Gateway{ 94 | ID: id, 95 | Listeners: listeners, 96 | ClientTimeout: cfg.ClientTimeout, 97 | ServerIDs: cfg.Servers, 98 | ServerNotFoundMessage: cfg.ServerNotFoundMessage, 99 | }, nil 100 | } 101 | 102 | func (cfg Config) LoadGateways() ([]bedprox.Gateway, error) { 103 | var gateways []bedprox.Gateway 104 | for id, v := range viper.GetStringMap("gateways") { 105 | vpr := viper.Sub("defaults.gateway") 106 | vMap := v.(map[string]interface{}) 107 | if err := vpr.MergeConfigMap(vMap); err != nil { 108 | return nil, err 109 | } 110 | var cfg gatewayConfig 111 | if err := vpr.Unmarshal(&cfg); err != nil { 112 | return nil, err 113 | } 114 | gateway, err := newGateway(id, cfg) 115 | if err != nil { 116 | return nil, err 117 | } 118 | gateways = append(gateways, gateway) 119 | } 120 | 121 | return gateways, nil 122 | } 123 | 124 | type serverConfig struct { 125 | Domains []string `mapstructure:"domains"` 126 | Address string `mapstructure:"address"` 127 | ProxyBind string `mapstructure:"proxy_bind"` 128 | DialTimeout time.Duration `mapstructure:"dial_timeout"` 129 | SendProxyProtocol bool `mapstructure:"send_proxy_protocol"` 130 | DialTimeoutMessage string `mapstructure:"dial_timeout_message"` 131 | } 132 | 133 | func newServer(id string, cfg serverConfig) bedprox.Server { 134 | return &Server{ 135 | ID: id, 136 | Domains: cfg.Domains, 137 | Dialer: raknet.Dialer{ 138 | UpstreamDialer: &net.Dialer{ 139 | LocalAddr: &net.UDPAddr{ 140 | IP: net.ParseIP(cfg.ProxyBind), 141 | }, 142 | }, 143 | }, 144 | DialTimeout: cfg.DialTimeout, 145 | Address: cfg.Address, 146 | SendProxyProtocol: cfg.SendProxyProtocol, 147 | DialTimeoutMessage: cfg.DialTimeoutMessage, 148 | } 149 | } 150 | 151 | func (cfg Config) LoadServers() ([]bedprox.Server, error) { 152 | var servers []bedprox.Server 153 | for id, v := range viper.GetStringMap("servers") { 154 | vpr := viper.Sub("defaults.server") 155 | vMap := v.(map[string]interface{}) 156 | if err := vpr.MergeConfigMap(vMap); err != nil { 157 | return nil, err 158 | } 159 | var cfg serverConfig 160 | if err := vpr.Unmarshal(&cfg); err != nil { 161 | return nil, err 162 | } 163 | servers = append(servers, newServer(id, cfg)) 164 | } 165 | 166 | return servers, nil 167 | } 168 | 169 | type cpnConfig struct { 170 | Count int `mapstructure:"count"` 171 | } 172 | 173 | func (cfg Config) LoadCPNs() ([]bedprox.CPN, error) { 174 | var cpnCfg cpnConfig 175 | if err := viper.UnmarshalKey("processing_nodes", &cpnCfg); err != nil { 176 | return nil, err 177 | } 178 | 179 | cpns := make([]bedprox.CPN, cpnCfg.Count) 180 | for n := range cpns { 181 | cpns[n].ConnProcessor = ConnProcessor{} 182 | } 183 | 184 | return cpns, nil 185 | } 186 | 187 | type webhookConfig struct { 188 | ClientTimeout time.Duration `mapstructure:"client_timeout"` 189 | URL string `mapstructure:"url"` 190 | Events []string `mapstructure:"events"` 191 | } 192 | 193 | func newWebhook(id string, cfg webhookConfig) webhook.Webhook { 194 | return webhook.Webhook{ 195 | ID: id, 196 | HTTPClient: &http.Client{ 197 | Timeout: cfg.ClientTimeout, 198 | }, 199 | URL: cfg.URL, 200 | EventTypes: cfg.Events, 201 | } 202 | } 203 | 204 | func (cfg Config) LoadWebhooks() ([]webhook.Webhook, error) { 205 | var webhooks []webhook.Webhook 206 | for id, v := range viper.GetStringMap("webhooks") { 207 | vpr := viper.Sub("defaults.webhook") 208 | vMap := v.(map[string]interface{}) 209 | if err := vpr.MergeConfigMap(vMap); err != nil { 210 | return nil, err 211 | } 212 | var cfg webhookConfig 213 | if err := vpr.Unmarshal(&cfg); err != nil { 214 | return nil, err 215 | } 216 | webhooks = append(webhooks, newWebhook(id, cfg)) 217 | } 218 | 219 | return webhooks, nil 220 | } 221 | -------------------------------------------------------------------------------- /bedrock/conn.go: -------------------------------------------------------------------------------- 1 | package bedrock 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/haveachin/bedprox/bedrock/protocol" 7 | "github.com/sandertv/go-raknet" 8 | ) 9 | 10 | // Conn is a minecraft Connection 11 | type Conn struct { 12 | *raknet.Conn 13 | 14 | gatewayID string 15 | proxyProtocol bool 16 | realIP bool 17 | } 18 | 19 | type ProcessedConn struct { 20 | *Conn 21 | readBytes []byte 22 | remoteAddr net.Addr 23 | serverAddr string 24 | username string 25 | proxyProtocol bool 26 | } 27 | 28 | func (c ProcessedConn) RemoteAddr() net.Addr { 29 | return c.remoteAddr 30 | } 31 | 32 | func (c ProcessedConn) GatewayID() string { 33 | return c.gatewayID 34 | } 35 | 36 | func (c ProcessedConn) Username() string { 37 | return c.username 38 | } 39 | 40 | func (c ProcessedConn) ServerAddr() string { 41 | return c.serverAddr 42 | } 43 | 44 | func (c ProcessedConn) Disconnect(msg string) error { 45 | defer c.Close() 46 | pk := protocol.Disconnect{ 47 | HideDisconnectionScreen: msg == "", 48 | Message: msg, 49 | } 50 | 51 | b, err := protocol.MarshalPacket(&pk) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | if _, err := c.Write(b); err != nil { 57 | return err 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /bedrock/conn_processer.go: -------------------------------------------------------------------------------- 1 | package bedrock 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "net" 8 | "strings" 9 | 10 | "github.com/haveachin/bedprox" 11 | "github.com/haveachin/bedprox/bedrock/protocol" 12 | "github.com/haveachin/bedprox/bedrock/protocol/login" 13 | "github.com/pires/go-proxyproto" 14 | ) 15 | 16 | // Processing Node 17 | type ConnProcessor struct{} 18 | 19 | func (cp ConnProcessor) ProcessConn(c net.Conn) (bedprox.ProcessedConn, error) { 20 | pc := ProcessedConn{ 21 | Conn: c.(*Conn), 22 | remoteAddr: c.RemoteAddr(), 23 | } 24 | 25 | if pc.proxyProtocol { 26 | header, err := proxyproto.Read(bufio.NewReader(c)) 27 | if err != nil { 28 | return nil, err 29 | } 30 | pc.remoteAddr = header.SourceAddr 31 | } 32 | 33 | b, err := pc.ReadPacket() 34 | if err != nil { 35 | return nil, err 36 | } 37 | pc.readBytes = b 38 | 39 | decoder := protocol.NewDecoder(bytes.NewReader(b)) 40 | pks, err := decoder.Decode() 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | if len(pks) < 1 { 46 | return nil, errors.New("no valid packets received") 47 | } 48 | 49 | var loginPk protocol.Login 50 | if err := protocol.UnmarshalPacket(pks[0], &loginPk); err != nil { 51 | return nil, err 52 | } 53 | 54 | iData, cData, err := login.Parse(loginPk.ConnectionRequest) 55 | if err != nil { 56 | return nil, err 57 | } 58 | pc.username = iData.DisplayName 59 | pc.serverAddr = cData.ServerAddress 60 | 61 | if strings.Contains(pc.serverAddr, ":") { 62 | pc.serverAddr, _, err = net.SplitHostPort(pc.serverAddr) 63 | if err != nil { 64 | return nil, err 65 | } 66 | } 67 | 68 | return &pc, nil 69 | } 70 | -------------------------------------------------------------------------------- /bedrock/gateway.go: -------------------------------------------------------------------------------- 1 | package bedrock 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "github.com/go-logr/logr" 11 | "github.com/sandertv/go-raknet" 12 | ) 13 | 14 | type PingStatus struct { 15 | Edition string 16 | ProtocolVersion int 17 | VersionName string 18 | PlayerCount int 19 | MaxPlayerCount int 20 | GameMode string 21 | GameModeNumeric int 22 | MOTD string 23 | } 24 | 25 | func (p PingStatus) marshal(l *raknet.Listener) []byte { 26 | motd := strings.Split(p.MOTD, "\n") 27 | motd1 := motd[0] 28 | motd2 := "" 29 | if len(motd) > 1 { 30 | motd2 = motd[1] 31 | } 32 | 33 | port := l.Addr().(*net.UDPAddr).Port 34 | return []byte(fmt.Sprintf("%v;%v;%v;%v;%v;%v;%v;%v;%v;%v;%v;%v;", 35 | p.Edition, motd1, p.ProtocolVersion, p.VersionName, p.PlayerCount, p.MaxPlayerCount, 36 | l.ID(), motd2, p.GameMode, p.GameModeNumeric, port, port)) 37 | } 38 | 39 | type Listener struct { 40 | Bind string 41 | ReceiveProxyProtocol bool 42 | ReceiveRealIP bool 43 | PingStatus PingStatus 44 | 45 | *raknet.Listener 46 | } 47 | 48 | type Gateway struct { 49 | ID string 50 | Listeners []Listener 51 | ClientTimeout time.Duration 52 | ServerIDs []string 53 | Log logr.Logger 54 | ServerNotFoundMessage string 55 | } 56 | 57 | func (gw Gateway) GetID() string { 58 | return gw.ID 59 | } 60 | 61 | func (gw Gateway) GetServerIDs() []string { 62 | return gw.ServerIDs 63 | } 64 | 65 | func (gw Gateway) GetServerNotFoundMessage() string { 66 | return gw.ServerNotFoundMessage 67 | } 68 | 69 | func (gw *Gateway) SetLogger(log logr.Logger) { 70 | gw.Log = log 71 | } 72 | 73 | func (gw *Gateway) ListenAndServe(cpnChan chan<- net.Conn) error { 74 | for n, listener := range gw.Listeners { 75 | gw.Log.Info("start listener", 76 | "bind", listener.Bind, 77 | ) 78 | 79 | l, err := raknet.Listen(listener.Bind) 80 | if err != nil { 81 | return err 82 | } 83 | l.PongData(listener.PingStatus.marshal(l)) 84 | 85 | gw.Listeners[n].Listener = l 86 | } 87 | 88 | gw.listenAndServe(cpnChan) 89 | return nil 90 | } 91 | 92 | func (gw Gateway) wrapConn(c net.Conn, l Listener) *Conn { 93 | return &Conn{ 94 | Conn: c.(*raknet.Conn), 95 | gatewayID: gw.ID, 96 | proxyProtocol: l.ReceiveProxyProtocol, 97 | realIP: l.ReceiveRealIP, 98 | } 99 | } 100 | 101 | func (gw *Gateway) listenAndServe(cpnChan chan<- net.Conn) { 102 | wg := sync.WaitGroup{} 103 | wg.Add(len(gw.Listeners)) 104 | 105 | for _, listener := range gw.Listeners { 106 | l := listener 107 | go func() { 108 | for { 109 | c, err := l.Accept() 110 | if err != nil { 111 | break 112 | } 113 | 114 | gw.Log.Info("new connection", 115 | "remoteAddress", c.RemoteAddr(), 116 | ) 117 | 118 | cpnChan <- gw.wrapConn(c, l) 119 | } 120 | wg.Done() 121 | }() 122 | } 123 | 124 | wg.Wait() 125 | } 126 | -------------------------------------------------------------------------------- /bedrock/protocol/decoder.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | // header is the header of compressed 'batches' from Minecraft. 14 | header = 0xfe 15 | // maximumInBatch is the maximum amount of packets that may be found in a batch. If a compressed batch has 16 | // more than this amount, decoding will fail. 17 | maximumInBatch = 512 + 256 18 | ) 19 | 20 | type Decoder struct { 21 | // r holds the io.Reader that packets are read from if the reader does not implement packetReader. When 22 | // this is the case, the buf field has a non-zero length. 23 | r io.Reader 24 | buf []byte 25 | } 26 | 27 | // NewDecoder returns a new decoder decoding data from the io.Reader passed. One read call from the reader is 28 | // assumed to consume an entire packet. 29 | func NewDecoder(reader io.Reader) *Decoder { 30 | return &Decoder{ 31 | r: reader, 32 | buf: make([]byte, 1024*1024*3), 33 | } 34 | } 35 | 36 | // Decode decodes one 'packet' from the io.Reader passed in NewDecoder(), producing a slice of packets that it 37 | // held and an error if not successful. 38 | func (decoder *Decoder) Decode() (packets [][]byte, err error) { 39 | n, err := decoder.r.Read(decoder.buf) 40 | if err != nil { 41 | return nil, fmt.Errorf("error reading batch from reader: %v", err) 42 | } 43 | data := decoder.buf[:n] 44 | 45 | if len(data) == 0 { 46 | return nil, nil 47 | } 48 | if data[0] != header { 49 | return nil, fmt.Errorf("error reading packet: invalid packet header %x: expected %x", data[0], header) 50 | } 51 | data = data[1:] 52 | 53 | b, err := decoder.decompress(data) 54 | if err != nil { 55 | return nil, err 56 | } 57 | for b.Len() != 0 { 58 | var length uint32 59 | if err := Varuint32(b, &length); err != nil { 60 | return nil, fmt.Errorf("error reading packet length: %v", err) 61 | } 62 | packets = append(packets, b.Next(int(length))) 63 | } 64 | if len(packets) > maximumInBatch { 65 | return nil, fmt.Errorf("number of packets %v in compressed batch exceeds %v", len(packets), maximumInBatch) 66 | } 67 | return packets, nil 68 | } 69 | 70 | // decompress decompresses the data passed and returns it as a byte slice. 71 | func (decoder *Decoder) decompress(data []byte) (*bytes.Buffer, error) { 72 | buf := bytes.NewBuffer(data) 73 | c := DecompressPool.Get().(io.ReadCloser) 74 | defer DecompressPool.Put(c) 75 | 76 | if err := c.(flate.Resetter).Reset(buf, nil); err != nil { 77 | return nil, fmt.Errorf("error resetting flate decompressor: %w", err) 78 | } 79 | _ = c.Close() 80 | 81 | raw := bytes.NewBuffer(make([]byte, 0, len(data)*2)) 82 | if _, err := io.Copy(raw, c); err != nil { 83 | return nil, fmt.Errorf("error reading decompressed data: %v", err) 84 | } 85 | return raw, nil 86 | } 87 | 88 | // DecompressPool is a sync.Pool for io.ReadCloser flate readers. These are pooled for connections. 89 | var DecompressPool = sync.Pool{ 90 | New: func() interface{} { 91 | return flate.NewReader(bytes.NewReader(nil)) 92 | }, 93 | } 94 | 95 | func Varuint32(src io.ByteReader, x *uint32) error { 96 | var v uint32 97 | for i := uint(0); i < 35; i += 7 { 98 | b, err := src.ReadByte() 99 | if err != nil { 100 | return err 101 | } 102 | v |= uint32(b&0x7f) << i 103 | if b&0x80 == 0 { 104 | *x = v 105 | return nil 106 | } 107 | } 108 | return errors.New("varuint32 did not terminate after 5 bytes") 109 | } 110 | -------------------------------------------------------------------------------- /bedrock/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 | -------------------------------------------------------------------------------- /bedrock/protocol/encoder.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "sync" 10 | ) 11 | 12 | // Encoder handles the encoding of Minecraft packets that are sent to an io.Writer. The packets are compressed 13 | // and optionally encoded before they are sent to the io.Writer. 14 | type Encoder struct { 15 | w io.Writer 16 | } 17 | 18 | // NewEncoder returns a new Encoder for the io.Writer passed. Each final packet produced by the Encoder is 19 | // sent with a single call to io.Writer.Write(). 20 | func NewEncoder(w io.Writer) *Encoder { 21 | return &Encoder{ 22 | w: w, 23 | } 24 | } 25 | 26 | // writeCloseResetter is an interface composed of an io.WriteCloser and a Reset(io.Writer) method. 27 | type writeCloseResetter interface { 28 | io.WriteCloser 29 | Reset(w io.Writer) 30 | } 31 | 32 | // Encode encodes the packet passed and compresses it. 33 | func (encoder *Encoder) Encode(packet []byte) error { 34 | buf := BufferPool.Get().(*bytes.Buffer) 35 | defer func() { 36 | // Reset the buffer so we can return it to the buffer pool safely. 37 | buf.Reset() 38 | BufferPool.Put(buf) 39 | }() 40 | if err := buf.WriteByte(header); err != nil { 41 | return fmt.Errorf("error writing 0xfe header: %v", err) 42 | } 43 | 44 | w := CompressPool.Get().(writeCloseResetter) 45 | defer CompressPool.Put(w) 46 | 47 | w.Reset(buf) 48 | l := make([]byte, 5) 49 | 50 | // Each packet is prefixed with a varuint32 specifying the length of the packet. 51 | if err := writeVaruint32(w, uint32(len(packet)), l); err != nil { 52 | return fmt.Errorf("error writing varuint32 length: %v", err) 53 | } 54 | if _, err := w.Write(packet); err != nil { 55 | return fmt.Errorf("error writing packet payload: %v", err) 56 | } 57 | 58 | // We compress the data and write the full data to the io.Writer. The data returned includes the header 59 | // we wrote at the start. 60 | b, err := encoder.compress(w, buf) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if _, err := encoder.w.Write(b); err != nil { 66 | return fmt.Errorf("error writing compressed packet to io.Writer: %v", err) 67 | } 68 | return nil 69 | } 70 | 71 | // compress compresses the data passed using the writer passed and returns it in a byte slice. It returns 72 | // the full content of encoder.buf, so any data currently set in that buffer will also be returned. 73 | func (encoder *Encoder) compress(w writeCloseResetter, buf *bytes.Buffer) ([]byte, error) { 74 | if err := w.Close(); err != nil { 75 | return nil, fmt.Errorf("error closing compressor: %v", err) 76 | } 77 | return buf.Bytes(), nil 78 | } 79 | 80 | // writeVaruint32 writes a uint32 to the destination buffer passed with a size of 1-5 bytes. It uses byte 81 | // slice b in order to prevent allocations. 82 | func writeVaruint32(dst writeCloseResetter, x uint32, b []byte) error { 83 | b[4] = 0 84 | b[3] = 0 85 | b[2] = 0 86 | b[1] = 0 87 | b[0] = 0 88 | 89 | i := 0 90 | for x >= 0x80 { 91 | b[i] = byte(x) | 0x80 92 | i++ 93 | x >>= 7 94 | } 95 | b[i] = byte(x) 96 | _, err := dst.Write(b[:i+1]) 97 | return err 98 | } 99 | 100 | // CompressPool is a sync.Pool for writeCloseResetter flate readers. These are pooled for connections. 101 | var CompressPool = sync.Pool{ 102 | New: func() interface{} { 103 | w, _ := flate.NewWriter(ioutil.Discard, 6) 104 | return w 105 | }, 106 | } 107 | 108 | // BufferPool is a sync.Pool for buffers used to write compressed data to. 109 | var BufferPool = sync.Pool{ 110 | New: func() interface{} { 111 | return bytes.NewBuffer(make([]byte, 0, 256)) 112 | }, 113 | } 114 | -------------------------------------------------------------------------------- /bedrock/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 | func (pk *Login) ID() uint32 { 18 | return 0x01 19 | } 20 | 21 | func (pk *Login) Unmarshal(r *Reader) error { 22 | if err := r.BEInt32(&pk.ClientProtocol); err != nil { 23 | return err 24 | } 25 | if err := r.ByteSlice(&pk.ConnectionRequest); err != nil { 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | // Marshal ... 32 | func (pk *Login) Marshal(buf *Writer) { 33 | log.Fatal("not implemented yet") 34 | } 35 | -------------------------------------------------------------------------------- /bedrock/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 | -------------------------------------------------------------------------------- /bedrock/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 | -------------------------------------------------------------------------------- /bedrock/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 | -------------------------------------------------------------------------------- /bedrock/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 | -------------------------------------------------------------------------------- /bedrock/protocol/writer.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type EncodeReader interface { 8 | io.Writer 9 | io.ByteWriter 10 | } 11 | 12 | type Writer struct { 13 | EncodeReader 14 | } 15 | 16 | func NewWriter(w EncodeReader) *Writer { 17 | return &Writer{EncodeReader: w} 18 | } 19 | 20 | func (w *Writer) Bool(x bool) { 21 | if x { 22 | w.WriteByte(0x01) 23 | } else { 24 | w.WriteByte(0x00) 25 | } 26 | } 27 | 28 | func (w *Writer) String(x string) { 29 | l := uint32(len(x)) 30 | w.Varuint32(l) 31 | _, _ = w.Write([]byte(x)) 32 | } 33 | 34 | func (w *Writer) Varuint32(x uint32) { 35 | for x >= 0x80 { 36 | _ = w.WriteByte(byte(x) | 0x80) 37 | x >>= 7 38 | } 39 | _ = w.WriteByte(byte(x)) 40 | } 41 | -------------------------------------------------------------------------------- /bedrock/server.go: -------------------------------------------------------------------------------- 1 | package bedrock 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "time" 8 | 9 | "github.com/go-logr/logr" 10 | "github.com/haveachin/bedprox" 11 | "github.com/haveachin/bedprox/webhook" 12 | "github.com/sandertv/go-raknet" 13 | ) 14 | 15 | type Server struct { 16 | ID string 17 | Domains []string 18 | Dialer raknet.Dialer 19 | DialTimeout time.Duration 20 | Address string 21 | SendProxyProtocol bool 22 | DialTimeoutMessage string 23 | WebhookIDs []string 24 | Log logr.Logger 25 | } 26 | 27 | func (s Server) GetID() string { 28 | return s.ID 29 | } 30 | 31 | func (s Server) GetDomains() []string { 32 | return s.Domains 33 | } 34 | 35 | func (s Server) GetWebhookIDs() []string { 36 | return s.WebhookIDs 37 | } 38 | 39 | func (s *Server) SetLogger(log logr.Logger) { 40 | s.Log = log 41 | } 42 | 43 | func (s Server) Dial() (*raknet.Conn, error) { 44 | c, err := s.Dialer.DialTimeout(s.Address, s.DialTimeout) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return c, nil 50 | } 51 | 52 | func (s Server) replaceTemplates(c ProcessedConn, msg string) string { 53 | tmpls := map[string]string{ 54 | "username": c.username, 55 | "now": time.Now().Format(time.RFC822), 56 | "remoteAddress": c.RemoteAddr().String(), 57 | "localAddress": c.LocalAddr().String(), 58 | "serverAddress": c.serverAddr, 59 | "serverID": s.ID, 60 | } 61 | 62 | for k, v := range tmpls { 63 | msg = strings.Replace(msg, fmt.Sprintf("{{%s}}", k), v, -1) 64 | } 65 | 66 | return msg 67 | } 68 | 69 | func (s Server) handleOffline(c ProcessedConn) error { 70 | msg := s.replaceTemplates(c, s.DialTimeoutMessage) 71 | return c.Disconnect(msg) 72 | } 73 | 74 | func (s Server) ProcessConn(c net.Conn, webhooks []webhook.Webhook) (bedprox.ConnTunnel, error) { 75 | pc := c.(*ProcessedConn) 76 | rc, err := s.Dial() 77 | if err != nil { 78 | if err := s.handleOffline(*pc); err != nil { 79 | s.Log.Error(err, "failed to handle offline") 80 | return bedprox.ConnTunnel{}, err 81 | } 82 | s.Log.Info("disconnected client") 83 | return bedprox.ConnTunnel{}, err 84 | } 85 | 86 | if _, err := rc.Write(pc.readBytes); err != nil { 87 | s.Log.Error(err, "failed to write to server") 88 | rc.Close() 89 | return bedprox.ConnTunnel{}, err 90 | } 91 | 92 | return bedprox.ConnTunnel{ 93 | Conn: pc, 94 | RemoteConn: rc, 95 | }, nil 96 | } 97 | -------------------------------------------------------------------------------- /cmd/bedprox/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | 7 | _ "embed" 8 | 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | //go:embed config.yml 13 | var defaultConfig []byte 14 | 15 | func init() { 16 | configPath = envString(configPathEnv, configPath) 17 | 18 | viper.SetConfigFile(configPath) 19 | if err := viper.ReadConfig(bytes.NewBuffer(defaultConfig)); err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | if err := viper.SafeWriteConfigAs(configPath); err == nil { 24 | return 25 | } 26 | 27 | if err := viper.ReadInConfig(); err != nil { 28 | log.Fatal(err) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cmd/bedprox/config.yml: -------------------------------------------------------------------------------- 1 | processing_nodes: 2 | count: 10 3 | 4 | api: 5 | bind: 0.0.0.0:8080 6 | 7 | gateways: 8 | mygateway: 9 | listeners: 10 | - bind: 192.168.1.31:19132 11 | receive_proxy_protocol: true 12 | - bind: 192.168.1.21:19132 13 | servers: 14 | - myserver 15 | server_not_found_message: Sorry {{username}}, but {{serverAddress}} was not found 16 | 17 | servers: 18 | myserver: 19 | domains: 20 | - 192.168.1.31 21 | - 192.168.1.21 22 | address: example.com:19132 23 | send_proxy_protocol: false 24 | webhooks: 25 | - mywebhook 26 | 27 | webhooks: 28 | mywebhook: 29 | url: https://mc.example.com/callback 30 | events: 31 | - PlayerJoin 32 | - PlayerLeave 33 | 34 | defaults: 35 | gateway: 36 | listener: 37 | receive_proxy_protocol: false 38 | receive_real_ip: false 39 | ping_status: 40 | edition: MCPE 41 | protocol_version: 471 42 | version_name: "1.17.41" 43 | player_count: 0 44 | max_player_count: 10 45 | game_mode: SURVIVAL 46 | game_mode_numeric: 1 47 | motd: | 48 | BedProx 49 | Join! 50 | server: 51 | proxy_bind: 0.0.0.0 52 | dial_timeout: 1s 53 | dial_timeout_message: Sorry {{username}}, but the server is currently unreachable 54 | webhook: 55 | client_timeout: 1s 56 | -------------------------------------------------------------------------------- /cmd/bedprox/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/go-logr/logr" 10 | "github.com/go-logr/zapr" 11 | "github.com/haveachin/bedprox" 12 | "github.com/haveachin/bedprox/bedrock" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | const configPathEnv = "BEDPROX_CONFIG_PATH" 17 | 18 | var configPath = "config.yml" 19 | 20 | func envString(name string, value string) string { 21 | envString := os.Getenv(name) 22 | if envString == "" { 23 | return value 24 | } 25 | 26 | return envString 27 | } 28 | 29 | var logger logr.Logger 30 | 31 | func init() { 32 | zapLog, err := zap.NewDevelopment() 33 | if err != nil { 34 | log.Fatalf("Failed to init logger; err: %s", err) 35 | } 36 | logger = zapr.NewLogger(zapLog) 37 | } 38 | 39 | func main() { 40 | logger.Info("loading proxy") 41 | 42 | p, err := bedprox.NewProxy(&bedrock.Config{}) 43 | if err != nil { 44 | logger.Error(err, "failed to load proxy") 45 | return 46 | } 47 | 48 | logger.Info("starting proxy") 49 | 50 | go func() { 51 | if err := p.Start(logger); err != nil { 52 | logger.Error(err, "failed to start the proxy") 53 | os.Exit(1) 54 | } 55 | }() 56 | 57 | sc := make(chan os.Signal, 1) 58 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) 59 | <-sc 60 | } 61 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | 3 | import "github.com/haveachin/bedprox/webhook" 4 | 5 | type ProxyConfig interface { 6 | LoadGateways() ([]Gateway, error) 7 | LoadServers() ([]Server, error) 8 | LoadCPNs() ([]CPN, error) 9 | LoadWebhooks() ([]webhook.Webhook, error) 10 | } 11 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "github.com/haveachin/bedprox/webhook" 8 | ) 9 | 10 | type ProcessedConn interface { 11 | net.Conn 12 | // GatewayID is the ID of the gateway that they connected through 13 | GatewayID() string 14 | // Username returns the username of the connecting player 15 | Username() string 16 | // ServerAddr returns the exact Server Address string 17 | // that the client send to the server 18 | ServerAddr() string 19 | // Disconnect sends the client a disconnect message 20 | // and closes the connection 21 | Disconnect(msg string) error 22 | } 23 | 24 | type ConnTunnel struct { 25 | Conn ProcessedConn 26 | RemoteConn net.Conn 27 | Webhooks []webhook.Webhook 28 | } 29 | 30 | func (t ConnTunnel) Start() { 31 | defer t.Close() 32 | 33 | go io.Copy(t.Conn, t.RemoteConn) 34 | io.Copy(t.RemoteConn, t.Conn) 35 | } 36 | 37 | func (t ConnTunnel) Close() { 38 | if t.Conn != nil { 39 | _ = t.Conn.Close() 40 | } 41 | if t.RemoteConn != nil { 42 | _ = t.RemoteConn.Close() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cpn.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/go-logr/logr" 7 | ) 8 | 9 | // Processing Node 10 | type CPN struct { 11 | ConnProcessor 12 | Log logr.Logger 13 | } 14 | 15 | type ConnProcessor interface { 16 | ProcessConn(c net.Conn) (ProcessedConn, error) 17 | } 18 | 19 | func (cpn *CPN) Start(cpnChan <-chan net.Conn, srvChan chan<- ProcessedConn) { 20 | for { 21 | c, ok := <-cpnChan 22 | if !ok { 23 | break 24 | } 25 | cpn.Log.Info("processing", 26 | "remoteAddress", c.RemoteAddr(), 27 | ) 28 | 29 | pc, err := cpn.ProcessConn(c) 30 | if err != nil { 31 | cpn.Log.Error(err, "processing", 32 | "remoteAddress", c.RemoteAddr(), 33 | ) 34 | c.Close() 35 | continue 36 | } 37 | srvChan <- pc 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | bedrocktest: 5 | build: . 6 | container_name: bedprox 7 | restart: unless-stopped 8 | ports: 9 | - "19132:19132/udp" 10 | volumes: 11 | - "./data/bedprox:/configs" 12 | environment: 13 | BEDPROX_CONFIG_PATH: "/configs/config.yml" 14 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | -------------------------------------------------------------------------------- /gateway.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/go-logr/logr" 7 | ) 8 | 9 | type Gateway interface { 10 | // GetID resturns the ID of the gateway 11 | GetID() string 12 | // GetServerIDs returns the IDs of the servers 13 | // that are registered in that gateway 14 | GetServerIDs() []string 15 | GetServerNotFoundMessage() string 16 | SetLogger(log logr.Logger) 17 | ListenAndServe(cpnChan chan<- net.Conn) error 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/haveachin/bedprox 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/golang-jwt/jwt/v4 v4.1.0 7 | github.com/sandertv/go-raknet v1.10.0 8 | github.com/spf13/viper v1.9.0 9 | go.uber.org/zap v1.19.0 10 | ) 11 | 12 | require ( 13 | go.uber.org/atomic v1.7.0 // indirect 14 | go.uber.org/multierr v1.6.0 // indirect 15 | ) 16 | 17 | require ( 18 | github.com/fsnotify/fsnotify v1.5.1 // indirect 19 | github.com/go-logr/logr v1.2.0 20 | github.com/go-logr/zapr v1.2.0 21 | github.com/hashicorp/hcl v1.0.0 // indirect 22 | github.com/magiconair/properties v1.8.5 // indirect 23 | github.com/mitchellh/mapstructure v1.4.2 // indirect 24 | github.com/pelletier/go-toml v1.9.4 // indirect 25 | github.com/pires/go-proxyproto v0.6.1 26 | github.com/spf13/afero v1.6.0 // indirect 27 | github.com/spf13/cast v1.4.1 // indirect 28 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 29 | github.com/spf13/pflag v1.0.5 // indirect 30 | github.com/subosito/gotenv v1.2.0 // indirect 31 | golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect 32 | golang.org/x/text v0.3.6 // indirect 33 | gopkg.in/ini.v1 v1.63.2 // indirect 34 | gopkg.in/yaml.v2 v2.4.0 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= 22 | cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= 23 | cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= 24 | cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= 25 | cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= 26 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 27 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 28 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 29 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 30 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 31 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 32 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 33 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 34 | cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= 35 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 36 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 37 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 38 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 39 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 40 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 41 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 42 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 43 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 44 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 45 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 46 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 47 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 48 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 49 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 50 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 51 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 52 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 53 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 54 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 55 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 56 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 57 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 58 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 59 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 60 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 61 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 62 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 63 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 64 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 65 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 66 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 67 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 68 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 69 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 70 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 71 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 72 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 73 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 74 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 75 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 76 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 77 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 78 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 79 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 80 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 81 | github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= 82 | github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= 83 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 84 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 85 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 86 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 87 | github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= 88 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 89 | github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= 90 | github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= 91 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 92 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 93 | github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= 94 | github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 95 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 96 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 97 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 98 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 99 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 100 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 101 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 102 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 103 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 104 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 105 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 106 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 107 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 108 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 109 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 110 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 111 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 112 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 113 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 114 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 115 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 116 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 117 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 118 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 119 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 120 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 121 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 122 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 123 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 124 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 125 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 126 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 127 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 128 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 129 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 130 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 131 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 132 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 133 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 134 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 135 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 136 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 137 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 138 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 139 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 141 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 142 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 143 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 144 | github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 145 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 146 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 147 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 148 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 149 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 150 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 151 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 152 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 153 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 154 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 155 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 156 | github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 157 | github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 158 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 159 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 160 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 161 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 162 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 163 | github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= 164 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 165 | github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= 166 | github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= 167 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 168 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 169 | github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 170 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 171 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 172 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 173 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 174 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 175 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 176 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 177 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 178 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 179 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 180 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 181 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 182 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 183 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 184 | github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= 185 | github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= 186 | github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= 187 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 188 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 189 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 190 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 191 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 192 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 193 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 194 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 195 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 196 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 197 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 198 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 199 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 200 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 201 | github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= 202 | github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 203 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 204 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 205 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 206 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 207 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 208 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 209 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 210 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 211 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 212 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 213 | github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 214 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 215 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 216 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 217 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 218 | github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= 219 | github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 220 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 221 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 222 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 223 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 224 | github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= 225 | github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 226 | github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw= 227 | github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= 228 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 229 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 230 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 231 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 232 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 233 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 234 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 235 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 236 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 237 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 238 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 239 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 240 | github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= 241 | github.com/sandertv/go-raknet v1.10.0 h1:KERyx6Ooc+4VmmYaa1ncByO1g18k3+Ste/wyUdCjtTw= 242 | github.com/sandertv/go-raknet v1.10.0/go.mod h1:s1lD7LTts74R9csTeI4WlGcp6PmXAGX+DeJlQF3KMlg= 243 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 244 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 245 | github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= 246 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 247 | github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= 248 | github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 249 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 250 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 251 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 252 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 253 | github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= 254 | github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= 255 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 256 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 257 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 258 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 259 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 260 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 261 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 262 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 263 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 264 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 265 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 266 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 267 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 268 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 269 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 270 | go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= 271 | go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= 272 | go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= 273 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 274 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 275 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 276 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 277 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 278 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 279 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 280 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 281 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 282 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 283 | go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= 284 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 285 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 286 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 287 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 288 | go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= 289 | go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 290 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 291 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 292 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 293 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 294 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 295 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 296 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 297 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 298 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 299 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 300 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 301 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 302 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 303 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 304 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 305 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 306 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 307 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 308 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 309 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 310 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 311 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 312 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 313 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 314 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 315 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 316 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 317 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 318 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 319 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 320 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 321 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 322 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= 323 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 324 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 325 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 326 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 327 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 328 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 329 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 330 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 331 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 332 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 333 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 334 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 335 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 336 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 337 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 338 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 339 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 340 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 341 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 342 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 343 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 344 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 345 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 346 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 347 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 348 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 349 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 350 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 351 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 352 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 353 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 354 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 355 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 356 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 357 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 358 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 359 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 360 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 361 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 362 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 363 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 364 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 365 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 366 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 367 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 368 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 369 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 370 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 371 | golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 372 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 373 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 374 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 375 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 376 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 377 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 378 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 379 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 380 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 381 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 382 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 383 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 384 | golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 385 | golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 386 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 387 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 388 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 389 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 390 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 391 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 392 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 393 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 394 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 395 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 396 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 397 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 398 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 399 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 400 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 401 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 402 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 403 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 411 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 412 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 413 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 417 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 418 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 419 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 420 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 421 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 422 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 423 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 424 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 425 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 426 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 427 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 428 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 429 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 430 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 431 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 432 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 433 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 434 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 435 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 436 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 437 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 438 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 439 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 440 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 441 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 442 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 443 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 444 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 445 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 446 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 447 | golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 448 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 449 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 450 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 451 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 452 | golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= 453 | golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 454 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 455 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 456 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 457 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 458 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 459 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 460 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 461 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 462 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 463 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 464 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 465 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 466 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 467 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 468 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 469 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 470 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 471 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 472 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 473 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 474 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 475 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 476 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 477 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 478 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 479 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 480 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 481 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 482 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 483 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 484 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 485 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 486 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 487 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 488 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 489 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 490 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 491 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 492 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 493 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 494 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 495 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 496 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 497 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 498 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 499 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 500 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 501 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 502 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 503 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 504 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 505 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 506 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 507 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 508 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 509 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 510 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 511 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 512 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 513 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 514 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 515 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 516 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 517 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 518 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 519 | golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 520 | golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 521 | golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= 522 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 523 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 524 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 525 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 526 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 527 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 528 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 529 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 530 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 531 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 532 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 533 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 534 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 535 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 536 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 537 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 538 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 539 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 540 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 541 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 542 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 543 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 544 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 545 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 546 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 547 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 548 | google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= 549 | google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= 550 | google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= 551 | google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= 552 | google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= 553 | google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= 554 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 555 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 556 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 557 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 558 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 559 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 560 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 561 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 562 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 563 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 564 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 565 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 566 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 567 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 568 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 569 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 570 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 571 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 572 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 573 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 574 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 575 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 576 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 577 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 578 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 579 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 580 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 581 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 582 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 583 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 584 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 585 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 586 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 587 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 588 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 589 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 590 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 591 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 592 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 593 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 594 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 595 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 596 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 597 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 598 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 599 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 600 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 601 | google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= 602 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 603 | google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 604 | google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 605 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= 606 | google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= 607 | google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= 608 | google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= 609 | google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= 610 | google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= 611 | google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 612 | google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 613 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 614 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 615 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 616 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 617 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 618 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 619 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 620 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 621 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 622 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 623 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 624 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 625 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 626 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 627 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 628 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 629 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 630 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 631 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 632 | google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 633 | google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 634 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 635 | google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= 636 | google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= 637 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 638 | google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 639 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 640 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 641 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 642 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 643 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 644 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 645 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 646 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 647 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 648 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 649 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 650 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 651 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 652 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 653 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 654 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 655 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 656 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 657 | gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= 658 | gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 659 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 660 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 661 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 662 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 663 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 664 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 665 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 666 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 667 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 668 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 669 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 670 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 671 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 672 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 673 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 674 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 675 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 676 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 677 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | 3 | import ( 4 | "github.com/go-logr/logr" 5 | ) 6 | 7 | type ConnPool struct { 8 | Log logr.Logger 9 | } 10 | 11 | func (cp *ConnPool) Start(poolChan <-chan ConnTunnel) { 12 | for { 13 | ct, ok := <-poolChan 14 | if !ok { 15 | break 16 | } 17 | 18 | cp.Log.Info("starting tunnel", 19 | "client", ct.Conn.RemoteAddr(), 20 | "server", ct.RemoteConn.RemoteAddr(), 21 | ) 22 | 23 | go ct.Start() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/go-logr/logr" 7 | ) 8 | 9 | type Proxy struct { 10 | Gateways []Gateway 11 | CPNs []CPN 12 | ServerGateway ServerGateway 13 | ConnPool ConnPool 14 | } 15 | 16 | func NewProxy(cfg ProxyConfig) (Proxy, error) { 17 | gateways, err := cfg.LoadGateways() 18 | if err != nil { 19 | return Proxy{}, err 20 | } 21 | 22 | gwIDsIDs := map[string][]string{} 23 | srvNotFoundMsgs := map[string]string{} 24 | for _, gw := range gateways { 25 | gwIDsIDs[gw.GetID()] = gw.GetServerIDs() 26 | srvNotFoundMsgs[gw.GetID()] = gw.GetServerNotFoundMessage() 27 | } 28 | 29 | cpns, err := cfg.LoadCPNs() 30 | if err != nil { 31 | return Proxy{}, err 32 | } 33 | 34 | servers, err := cfg.LoadServers() 35 | if err != nil { 36 | return Proxy{}, err 37 | } 38 | 39 | return Proxy{ 40 | Gateways: gateways, 41 | CPNs: cpns, 42 | ServerGateway: ServerGateway{ 43 | GatewayIDServerIDs: gwIDsIDs, 44 | ServerNotFoundMessages: srvNotFoundMsgs, 45 | Servers: servers, 46 | }, 47 | ConnPool: ConnPool{}, 48 | }, nil 49 | } 50 | 51 | func (p Proxy) Start(log logr.Logger) error { 52 | cpnChan := make(chan net.Conn, 10) 53 | srvChan := make(chan ProcessedConn, 10) 54 | poolChan := make(chan ConnTunnel, 10) 55 | 56 | for _, gw := range p.Gateways { 57 | gw.SetLogger(log) 58 | go gw.ListenAndServe(cpnChan) 59 | } 60 | 61 | for _, cpn := range p.CPNs { 62 | cpn.Log = log 63 | go cpn.Start(cpnChan, srvChan) 64 | } 65 | 66 | p.ConnPool.Log = log 67 | go p.ConnPool.Start(poolChan) 68 | 69 | for _, srv := range p.ServerGateway.Servers { 70 | srv.SetLogger(log) 71 | } 72 | 73 | p.ServerGateway.Log = log 74 | if err := p.ServerGateway.Start(srvChan, poolChan); err != nil { 75 | return err 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package bedprox 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "time" 8 | 9 | "github.com/go-logr/logr" 10 | "github.com/haveachin/bedprox/webhook" 11 | ) 12 | 13 | type Server interface { 14 | GetID() string 15 | GetDomains() []string 16 | GetWebhookIDs() []string 17 | ProcessConn(c net.Conn, webhooks []webhook.Webhook) (ConnTunnel, error) 18 | SetLogger(log logr.Logger) 19 | } 20 | 21 | type ServerGateway struct { 22 | GatewayIDServerIDs map[string][]string 23 | // ServerNotFoundMessages maps the GatewayID to server not found message 24 | ServerNotFoundMessages map[string]string 25 | Servers []Server 26 | Webhooks []webhook.Webhook 27 | Log logr.Logger 28 | 29 | // "GatewayID@Domain" mapped to server 30 | srvs map[string]Server 31 | // Server ID mapped to webhooks 32 | srvWhks map[string][]webhook.Webhook 33 | } 34 | 35 | func (sg *ServerGateway) indexServers() error { 36 | srvs := map[string]Server{} 37 | for _, srv := range sg.Servers { 38 | srvs[srv.GetID()] = srv 39 | } 40 | 41 | sg.srvs = map[string]Server{} 42 | for gID, sIDs := range sg.GatewayIDServerIDs { 43 | for _, sID := range sIDs { 44 | srv, ok := srvs[sID] 45 | if !ok { 46 | return fmt.Errorf("server with ID %q doesn't exist", sID) 47 | } 48 | 49 | for _, domain := range srv.GetDomains() { 50 | lowerDomain := strings.ToLower(domain) 51 | sgID := fmt.Sprintf("%s@%s", gID, lowerDomain) 52 | if _, exits := sg.srvs[sgID]; exits { 53 | return fmt.Errorf("duplicate server gateway ID %q", sgID) 54 | } 55 | sg.srvs[sgID] = srv 56 | } 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | // indexWebhooks indexes the webhooks that servers use. 63 | func (sg *ServerGateway) indexWebhooks() error { 64 | whks := map[string]webhook.Webhook{} 65 | for _, w := range sg.Webhooks { 66 | whks[w.ID] = w 67 | } 68 | 69 | sg.srvWhks = map[string][]webhook.Webhook{} 70 | for _, srv := range sg.Servers { 71 | ww := make([]webhook.Webhook, len(srv.GetWebhookIDs())) 72 | for n, id := range srv.GetWebhookIDs() { 73 | w, ok := whks[id] 74 | if !ok { 75 | return fmt.Errorf("webhook with ID %q doesn't exist", id) 76 | } 77 | ww[n] = w 78 | } 79 | sg.srvWhks[srv.GetID()] = ww 80 | } 81 | return nil 82 | } 83 | 84 | func (sg ServerGateway) executeTemplate(msg string, pc ProcessedConn) string { 85 | tmpls := map[string]string{ 86 | "username": pc.Username(), 87 | "now": time.Now().Format(time.RFC822), 88 | "remoteAddress": pc.RemoteAddr().String(), 89 | "localAddress": pc.LocalAddr().String(), 90 | "serverAddress": pc.ServerAddr(), 91 | "gatewayID": pc.GatewayID(), 92 | } 93 | 94 | for k, v := range tmpls { 95 | msg = strings.Replace(msg, fmt.Sprintf("{{%s}}", k), v, -1) 96 | } 97 | 98 | return msg 99 | } 100 | 101 | func (sg ServerGateway) Start(srvChan <-chan ProcessedConn, poolChan chan<- ConnTunnel) error { 102 | if err := sg.indexServers(); err != nil { 103 | return err 104 | } 105 | 106 | if err := sg.indexWebhooks(); err != nil { 107 | return err 108 | } 109 | 110 | for { 111 | pc, ok := <-srvChan 112 | if !ok { 113 | break 114 | } 115 | 116 | srvAddrLower := strings.ToLower(pc.ServerAddr()) 117 | sgID := fmt.Sprintf("%s@%s", pc.GatewayID(), srvAddrLower) 118 | srv, ok := sg.srvs[sgID] 119 | if !ok { 120 | sg.Log.Info("invalid server", 121 | "serverAddress", pc.ServerAddr(), 122 | "remoteAddress", pc.RemoteAddr(), 123 | ) 124 | msg := sg.ServerNotFoundMessages[pc.GatewayID()] 125 | msg = sg.executeTemplate(msg, pc) 126 | _ = pc.Disconnect(msg) 127 | continue 128 | } 129 | 130 | sg.Log.Info("connecting client", 131 | "serverId", sgID, 132 | "remoteAddress", pc.RemoteAddr(), 133 | ) 134 | 135 | whks := sg.srvWhks[srv.GetID()] 136 | ct, err := srv.ProcessConn(pc, whks) 137 | if err != nil { 138 | ct.Close() 139 | continue 140 | } 141 | 142 | // Shallow copy webhooks to mitigate race conditions 143 | whksCopy := make([]webhook.Webhook, len(whks)) 144 | _ = copy(whksCopy, whks) 145 | ct.Webhooks = whksCopy 146 | 147 | poolChan <- ct 148 | } 149 | 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /webhook/event.go: -------------------------------------------------------------------------------- 1 | package webhook 2 | 3 | const ( 4 | EventTypeError string = "Error" 5 | EventTypePlayerJoin string = "PlayerJoin" 6 | EventTypePlayerLeave string = "PlayerLeave" 7 | EventTypeContainerStart string = "ContainerStart" 8 | EventTypeContainerStop string = "ContainerStop" 9 | ) 10 | 11 | type Event interface { 12 | EventType() string 13 | } 14 | 15 | type EventError struct { 16 | Error string `json:"error"` 17 | ProxyUID string `json:"proxyUid"` 18 | } 19 | 20 | func (event EventError) EventType() string { 21 | return EventTypeError 22 | } 23 | 24 | type EventPlayerJoin struct { 25 | Username string `json:"username"` 26 | RemoteAddress string `json:"remoteAddress"` 27 | TargetAddress string `json:"targetAddress"` 28 | ProxyUID string `json:"proxyUid"` 29 | } 30 | 31 | func (event EventPlayerJoin) EventType() string { 32 | return EventTypePlayerJoin 33 | } 34 | 35 | type EventPlayerLeave struct { 36 | Username string `json:"username"` 37 | RemoteAddress string `json:"remoteAddress"` 38 | TargetAddress string `json:"targetAddress"` 39 | ProxyUID string `json:"proxyUid"` 40 | } 41 | 42 | func (event EventPlayerLeave) EventType() string { 43 | return EventTypePlayerLeave 44 | } 45 | 46 | type EventContainerStart struct { 47 | ProxyUID string `json:"proxyUid"` 48 | } 49 | 50 | func (event EventContainerStart) EventType() string { 51 | return EventTypeContainerStart 52 | } 53 | 54 | type EventContainerStop struct { 55 | ProxyUID string `json:"proxyUid"` 56 | } 57 | 58 | func (event EventContainerStop) EventType() string { 59 | return EventTypeContainerStop 60 | } 61 | -------------------------------------------------------------------------------- /webhook/event_test.go: -------------------------------------------------------------------------------- 1 | package webhook_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/haveachin/bedprox/webhook" 7 | ) 8 | 9 | func TestErrorEvent_EventType(t *testing.T) { 10 | tt := []struct { 11 | event webhook.Event 12 | eventType string 13 | }{ 14 | { 15 | event: webhook.EventError{}, 16 | eventType: webhook.EventTypeError, 17 | }, 18 | { 19 | event: webhook.EventPlayerJoin{}, 20 | eventType: webhook.EventTypePlayerJoin, 21 | }, 22 | { 23 | event: webhook.EventPlayerLeave{}, 24 | eventType: webhook.EventTypePlayerLeave, 25 | }, 26 | { 27 | event: webhook.EventContainerStart{}, 28 | eventType: webhook.EventTypeContainerStart, 29 | }, 30 | { 31 | event: webhook.EventContainerStop{}, 32 | eventType: webhook.EventTypeContainerStop, 33 | }, 34 | } 35 | 36 | for _, tc := range tt { 37 | if tc.event.EventType() != tc.eventType { 38 | t.Fail() 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /webhook/webhook.go: -------------------------------------------------------------------------------- 1 | package webhook 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | var ( 12 | ErrEventNotAllowed = errors.New("event not allowed") 13 | ) 14 | 15 | // HTTPClient represents an interface for the Webhook to send events with. 16 | type HTTPClient interface { 17 | Do(req *http.Request) (*http.Response, error) 18 | } 19 | 20 | // EventLog is the struct that will be send to the Webhook.URL 21 | type EventLog struct { 22 | EventType string `json:"eventType"` 23 | Timestamp time.Time `json:"timestamp"` 24 | Event Event `json:"event"` 25 | } 26 | 27 | // Webhook can send a Event via POST Request to a specified URL. 28 | // There are two ways to use a Webhook. You can directly call 29 | // DispatchEvent or Serve to attach a channel to the Webhook. 30 | type Webhook struct { 31 | ID string 32 | HTTPClient HTTPClient 33 | URL string 34 | EventTypes []string 35 | } 36 | 37 | // hasEvent checks if Webhook.EventTypes contain the given event's type. 38 | func (webhook Webhook) hasEvent(event Event) bool { 39 | for _, eventType := range webhook.EventTypes { 40 | if eventType == event.EventType() { 41 | return true 42 | } 43 | } 44 | return false 45 | } 46 | 47 | // DispatchEvent wraps the given Event in an EventLog and marshals it into JSON 48 | // before sending it in a POST Request to the Webhook.URL. 49 | func (webhook Webhook) DispatchEvent(event Event) error { 50 | if !webhook.hasEvent(event) { 51 | return ErrEventNotAllowed 52 | } 53 | 54 | eventLog := EventLog{ 55 | EventType: event.EventType(), 56 | Timestamp: time.Now(), 57 | Event: event, 58 | } 59 | 60 | bb, err := json.Marshal(eventLog) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | request, err := http.NewRequest(http.MethodPost, webhook.URL, bytes.NewReader(bb)) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | resp, err := webhook.HTTPClient.Do(request) 71 | if err != nil { 72 | return err 73 | } 74 | // We don't care about the client's response, but we should still close the client's body if it exists. 75 | // If not closed the underlying connection cannot be reused for further requests. 76 | // See https://pkg.go.dev/net/http#Client.Do for more details. 77 | if resp != nil && resp.Body != nil { 78 | _ = resp.Body.Close() 79 | } 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /webhook/webhook_test.go: -------------------------------------------------------------------------------- 1 | package webhook_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/haveachin/bedprox/webhook" 10 | ) 11 | 12 | var errHTTPRequestFailed = errors.New("request failed") 13 | 14 | type mockHTTPClient struct { 15 | *testing.T 16 | targetURL string 17 | expectedBody *bytes.Buffer 18 | requestShouldFail bool 19 | } 20 | 21 | func (mock *mockHTTPClient) Do(req *http.Request) (*http.Response, error) { 22 | if req.Method != http.MethodPost { 23 | mock.Fail() 24 | } 25 | 26 | if req.URL.String() != mock.targetURL { 27 | mock.Fail() 28 | } 29 | 30 | _, err := mock.expectedBody.ReadFrom(req.Body) 31 | if err != nil { 32 | mock.Error(err) 33 | } 34 | 35 | if mock.requestShouldFail { 36 | return nil, errHTTPRequestFailed 37 | } 38 | 39 | return nil, nil 40 | } 41 | 42 | func TestWebhook_DispatchEvent(t *testing.T) { 43 | tt := []struct { 44 | name string 45 | webhook webhook.Webhook 46 | event webhook.Event 47 | shouldDispatch bool 48 | httpRequestShouldFail bool 49 | }{ 50 | { 51 | name: "WithExactlyTheAllowedEvent", 52 | webhook: webhook.Webhook{ 53 | URL: "https://example.com", 54 | EventTypes: []string{webhook.EventTypeError}, 55 | }, 56 | event: webhook.EventError{ 57 | Error: "my error message", 58 | ProxyUID: "example.com@1.2.3.4:25565", 59 | }, 60 | shouldDispatch: true, 61 | httpRequestShouldFail: false, 62 | }, 63 | { 64 | name: "WithOneOfTheAllowedEvents", 65 | webhook: webhook.Webhook{ 66 | URL: "https://example.com", 67 | EventTypes: []string{webhook.EventTypePlayerJoin, webhook.EventTypePlayerLeave}, 68 | }, 69 | event: webhook.EventPlayerJoin{ 70 | Username: "notch", 71 | RemoteAddress: "1.2.3.4", 72 | TargetAddress: "1.2.3.4", 73 | ProxyUID: "example.com@1.2.3.4:25565", 74 | }, 75 | shouldDispatch: true, 76 | httpRequestShouldFail: false, 77 | }, 78 | { 79 | name: "ErrorsWithOneDeniedEvent", 80 | webhook: webhook.Webhook{ 81 | URL: "https://example.com", 82 | EventTypes: []string{webhook.EventTypeError, webhook.EventTypePlayerLeave}, 83 | }, 84 | event: webhook.EventPlayerJoin{ 85 | Username: "notch", 86 | RemoteAddress: "1.2.3.4", 87 | TargetAddress: "1.2.3.4", 88 | ProxyUID: "example.com@1.2.3.4:25565", 89 | }, 90 | shouldDispatch: false, 91 | httpRequestShouldFail: false, 92 | }, 93 | { 94 | name: "ErrorsWithFailedHTTPRequest", 95 | webhook: webhook.Webhook{ 96 | URL: "https://example.com", 97 | EventTypes: []string{webhook.EventTypeError}, 98 | }, 99 | event: webhook.EventError{ 100 | Error: "my error message", 101 | ProxyUID: "example.com@1.2.3.4:25565", 102 | }, 103 | shouldDispatch: true, 104 | httpRequestShouldFail: true, 105 | }, 106 | } 107 | 108 | for _, tc := range tt { 109 | t.Run(tc.name, func(t *testing.T) { 110 | var body bytes.Buffer 111 | tc.webhook.HTTPClient = &mockHTTPClient{ 112 | T: t, 113 | targetURL: tc.webhook.URL, 114 | expectedBody: &body, 115 | requestShouldFail: tc.httpRequestShouldFail, 116 | } 117 | 118 | if err := tc.webhook.DispatchEvent(tc.event); err != nil { 119 | if errors.Is(err, webhook.ErrEventNotAllowed) && !tc.shouldDispatch || 120 | errors.Is(err, errHTTPRequestFailed) && tc.httpRequestShouldFail { 121 | return 122 | } 123 | t.Error(err) 124 | } 125 | }) 126 | } 127 | } 128 | --------------------------------------------------------------------------------