commit a74df99adbfddcce9e0e80c9aff7d11e620731ed Author: Steffen Vogel Date: Sun Aug 1 15:28:25 2021 +0200 initial commit Signed-off-by: Steffen Vogel diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..8270b7d7 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,76 @@ +on: +- push +- pull_request + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # The "build" workflow + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + # Setup Go + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: '1.16.6' + + # Install all the dependencies + - name: Install dependencies + run: go get -u golang.org/x/lint/golint + + # Run build of the application + - name: Run build + run: make + + # Run vet on the code + - name: Run vet + run: go vet ./wice + + # Run lint on the code + - name: Run lint + run: golint ./wice + + # Run testing on the code + - name: Run testing + run: go test -v ./wice + + # Run static check + - uses: reviewdog/action-staticcheck@v1 + with: + github_token: ${{ secrets.github_token }} + reporter: github-pr-review + filter_mode: nofilter + fail_on_error: true + + # The "deploy" workflow + deploy: + # The type of runner that the job will run on + runs-on: ubuntu-latest + needs: [build] # Only run this workflow when "build" workflow succeeds + if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }} # Only run this workflow if it is master branch on push event + steps: + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + push: true + tags: user/app:latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..c7314a6a --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,23 @@ + +on: + release: + types: + - created + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16.6 + - name: Make Directory with kgctl Binaries to Be Released + run: make release + - name: Publish Release + uses: skx/github-action-publish-binaries@master + env: + GITHUB_TOKEN: ${{ secrets.github_token }} + with: + args: 'build/wice-*' diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6f31401f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.vscode/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..814430a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.16-alpine AS builder + +WORKDIR /app +COPY go.mod ./ +COPY go.sum ./ +RUN go mod download + +COPY wice/ ./wice/ +RUN go build -o build/wice ./wice + +FROM scratch + +COPY --from=builder /app/build/wice / + +CMD [ "/wice" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..499db034 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +export GO111MODULE=on + +TOOLS := $(notdir $(wildcard cmd/*)) + +OS ?= $(shell go env GOOS) +ARCH ?= $(shell go env GOARCH) +ALL_ARCH := amd64 arm arm64 +ALL_OS := linux freebsd openbsd darwin windows + +BINS := $(foreach X,$(ALL_OS),$(foreach Y,$(ALL_ARCH),wice-$X-$Y)) + +temp = $(subst -, ,$@) +cmd = $(word 1, $(temp)) +os = $(word 2, $(temp)) +arch = $(word 3, $(temp)) + +all: wice-$(OS)-$(ARCH) + +release: $(PLATFORMS) + +$(BINS): + GOOS=$(os) \ + GOARCH=$(arch) \ + go build -o 'build/$(cmd)-$(os)-$(arch)' ./cmd/$(cmd) + +.PHONY: release $(PLATFORMS) diff --git a/README.md b/README.md new file mode 100644 index 00000000..2cb757a6 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# WICE - Wireguard Interactive Connectivity Establishment + +[![Go Reference](https://pkg.go.dev/badge/github.com/stv0g/wice.svg)](https://pkg.go.dev/github.com/stv0g/wice) +![](https://img.shields.io/snyk/vulnerabilities/github/stv0g/wice) +[![](https://img.shields.io/github/checks-status/stv0g/wice/master)](https://github.com/stv0g/wice/actions) +[![](https://img.shields.io/librariesio/release/stv0g/wice)](https://libraries.io/github/stv0g/wice) +[![GitHub](https://img.shields.io/github/license/stv0g/wice)](https://github.com/stv0g/wice/blob/master/LICENSE) +![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/stv0g/wice) + +WICE is a userspace daemon managing Wireguard interfaces to establish peer-to-peer connections in harsh network environments. + +It relies on the [awesome](https://github.com/pion/awesome-pion) [pion/ice] package for the interactive connectivity establishment as well as bundles the Go userspace implementation of Wiguard in a single binary for environments in which Wireguard kernel support has not landed yet. + +## Getting started + +To use WICE you first need to setup a signalling server: + +1. Install WICE: `go get riasc.eu/wice` +2. Run the signalling server on a publicly accessible node: `wice-signal-http -port 8080` + +Afterwards perform the following steps on each node which should join the mesh: + +1. Install WICE: `go get riasc.eu/wice` +2. Configure your Wireguard interfaces using `wg`, `wg-quick` or [NetworkManager](https://blogs.gnome.org/thaller/2019/03/15/wireguard-in-networkmanager/) +3. Start the WICE daemon by running: `sudo wice -backend http://signalling-server:8080` + +The WICE daemons will now attempt to discover valid endpoint addresses using the ICE protocol (e.g. contacting STUN servers). +These _ICE candidates_ are then exchanged via the signalling server and WICE will update the endpoint addresses of the Wireguard peers accordingly. +Once this has been done, the WICE logs should show `Connected to peer`. + +## Documentation + +Documentation of WICE can be found in the [`docs/`](./docs) directory. + +## Authors + +- Steffen Vogel ([@stv0g](https://github.com/stv0g), Institute for Automation of Complex Power Systems, RWTH Aachen University) + +## Funding acknowledment + +![](https://erigrid2.eu/wp-content/uploads/2020/03/europa_flag_low.jpg) The development of [WICE] has been supported by the [ERIGrid 2.0] project of the H2020 Programme under [Grant Agreement No. 870620](https://cordis.europa.eu/project/id/870620) + + +[Wireguard]: https://wireguard.com +[wireguard-go]: https://git.zx2c4.com/wireguard-go +[pion/ice]: https://github.com/pion/ice +[ICE]: https://datatracker.ietf.org/doc/html/rfc8445 +[ICE-PAC]: https://datatracker.ietf.org/doc/html/rfc8863 +[ICE-TCP]: https://datatracker.ietf.org/doc/html/rfc6544 +[Trickle ICE]: https://datatracker.ietf.org/doc/html/rfc8838 +[ICE-SDP]: https://datatracker.ietf.org/doc/html/rfc8839 +[TURN-TCP]: https://datatracker.ietf.org/doc/html/rfc6062 +[TURN-STUN]: https://datatracker.ietf.org/doc/html/rfc8656 +[STUN]: https://datatracker.ietf.org/doc/html/rfc8489 +[SDP]: https://datatracker.ietf.org/doc/html/rfc8866 +[SDP-Offer-Answer]: https://datatracker.ietf.org/doc/html/rfc3264 +[JWS]: https://datatracker.ietf.org/doc/html/rfc7515 +[JWS-CT]: https://tools.ietf.org/id/draft-jordan-jws-ct-02.html +[JCS]: https://datatracker.ietf.org/doc/html/rfc8785 +[WICE]: https://github.com/stv0g/wice +[ERIGrid 2.0]: https://erigrid2.eu +[NetworkManager]: https://github.com/max-moser/network-manager-wireguard +[systemd-networkd]: https://www.freedesktop.org/software/systemd/man/systemd.netdev.html#%5BWireGuard%5D%20Section%20Options +[wg-quick]: https://manpages.debian.org/unstable/wireguard-tools/wg-quick.8.en.html +[kilo]: https://kilo.squat.ai +[Nftables]: https://www.netfilter.org/projects/nftables/manpage.html +[XEdDSA]: https://signal.org/docs/specifications/xeddsa/ + +https://riyazali.net/posts/berkeley-packet-filter-in-golang/ +https://squidarth.com/networking/systems/rc/2018/05/28/using-raw-sockets.html diff --git a/cmd/test-filter-udp-conn/main.go b/cmd/test-filter-udp-conn/main.go new file mode 100644 index 00000000..f2779a8e --- /dev/null +++ b/cmd/test-filter-udp-conn/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "encoding/hex" + "fmt" + "net" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + netx "riasc.eu/wice/internal/net" +) + +const ( + StunMagicCookie uint32 = 0x2112A442 +) + +func main() { + la := net.UDPAddr{ + IP: net.IPv4zero, + Port: 12345, + } + + spec := ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + License: "GPL", + Instructions: asm.Instructions{ + asm.Mov.Reg(asm.R6, asm.R1), // LDABS requires ctx in R6 + asm.LoadAbs(-0x100000+22, asm.Half), + asm.JNE.Imm(asm.R0, int32(la.Port), "skip"), + asm.LoadAbs(-0x100000+32, asm.Word), + asm.JNE.Imm(asm.R0, int32(StunMagicCookie), "skip"), + asm.Mov.Imm(asm.R0, -1).Sym("exit"), + asm.Return(), + asm.Mov.Imm(asm.R0, 0).Sym("skip"), + asm.Return(), + }, + } + + fmt.Printf("Instructions:\n%v\n", spec.Instructions) + + prog, err := ebpf.NewProgramWithOptions(&spec, ebpf.ProgramOptions{ + LogLevel: 6, // TODO take configured log-level from args + }) + if err != nil { + panic(err) + } + + fuc, err := netx.NewFilteredUDPConn(la) + if err != nil { + panic(err) + } + + err = fuc.ApplyFilter(prog) + if err != nil { + panic(err) + } + + buf := make([]byte, 1024) + for { + n, ra, err := fuc.ReadFrom(buf) + if err != nil { + panic(err) + } + + fmt.Printf("Bytes: %d\n", n) + fmt.Printf("RA: %+v\n", ra) + fmt.Printf("Bytes: %s\n", hex.EncodeToString(buf[:n])) + fmt.Println() + } +} diff --git a/cmd/test-send-stun/main.go b/cmd/test-send-stun/main.go new file mode 100644 index 00000000..f57f235d --- /dev/null +++ b/cmd/test-send-stun/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + + "github.com/pion/stun" +) + +func main() { + // Creating a "connection" to STUN server. + c, err := stun.Dial("udp", "127.0.0.1:12345") + if err != nil { + panic(err) + } + // Building binding request with random transaction id. + message := stun.MustBuild(stun.TransactionID, stun.BindingRequest) + // Sending request to STUN server, waiting for response message. + if err := c.Do(message, func(res stun.Event) { + if res.Error != nil { + panic(res.Error) + } + // Decoding XOR-MAPPED-ADDRESS attribute from message. + var xorAddr stun.XORMappedAddress + if err := xorAddr.GetFrom(res.Message); err != nil { + panic(err) + } + fmt.Println("your IP is", xorAddr.IP) + }); err != nil { + panic(err) + } +} diff --git a/cmd/wice-signal-http/main.go b/cmd/wice-signal-http/main.go new file mode 100644 index 00000000..5d1f1b34 --- /dev/null +++ b/cmd/wice-signal-http/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + + log "github.com/sirupsen/logrus" + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/crypto" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" +) + +var offers map[crypto.Key]map[crypto.Key]backend.Offer + +func candidateHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + ourPKStr := vars["ours"] + theirPKStr := vars["theirs"] + + ourPKStrUnescaped, _ := url.PathUnescape(ourPKStr) + theirPKStrUnescaped, _ := url.PathUnescape(theirPKStr) + + var err error + var ourPK, theirPK crypto.Key + + ourPK, err = crypto.ParseKey(ourPKStrUnescaped) + if err != nil { + http.Error(w, fmt.Sprintf("failed to parse key: %s", err), http.StatusBadRequest) + } + + theirPK, err = crypto.ParseKey(theirPKStrUnescaped) + if err != nil { + http.Error(w, fmt.Sprintf("failed to parse key: %s", err), http.StatusBadRequest) + } + + // Create missing maps and slice + if _, ok := offers[ourPK]; !ok { + offers[ourPK] = make(map[crypto.Key]backend.Offer) + } + + if r.Method == "POST" { + dec := json.NewDecoder(r.Body) + var offer backend.Offer + err := dec.Decode(&offer) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + offers[ourPK][theirPK] = offer + } else if r.Method == "GET" { + offer, ok := offers[ourPK][theirPK] + if !ok { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + enc := json.NewEncoder(w) + enc.Encode(offer) + + } else if r.Method == "DELETE" { + _, ok := offers[ourPK][theirPK] + if !ok { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + delete(offers[ourPK], theirPK) + } +} + +func main() { + r := mux.NewRouter() + r.HandleFunc("/offers/{ours}/{theirs}", candidateHandler) + + lr := handlers.LoggingHandler(os.Stdout, r) + + http.Handle("/", lr) + + offers = make(map[crypto.Key]map[crypto.Key]backend.Offer) + + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/cmd/wice-signal-ws/client.go b/cmd/wice-signal-ws/client.go new file mode 100644 index 00000000..9461c1ea --- /dev/null +++ b/cmd/wice-signal-ws/client.go @@ -0,0 +1,137 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "log" + "net/http" + "time" + + "github.com/gorilla/websocket" +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 512 +) + +var ( + newline = []byte{'\n'} + space = []byte{' '} +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +// Client is a middleman between the websocket connection and the hub. +type Client struct { + hub *Hub + + // The websocket connection. + conn *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte +} + +// readPump pumps messages from the websocket connection to the hub. +// +// The application runs readPump in a per-connection goroutine. The application +// ensures that there is at most one reader on a connection by executing all +// reads from this goroutine. +func (c *Client) readPump() { + defer func() { + c.hub.unregister <- c + c.conn.Close() + }() + c.conn.SetReadLimit(maxMessageSize) + c.conn.SetReadDeadline(time.Now().Add(pongWait)) + c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("error: %v", err) + } + break + } + message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) + c.hub.broadcast <- message + } +} + +// writePump pumps messages from the hub to the websocket connection. +// +// A goroutine running writePump is started for each connection. The +// application ensures that there is at most one writer to a connection by +// executing all writes from this goroutine. +func (c *Client) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.conn.Close() + }() + for { + select { + case message, ok := <-c.send: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + // The hub closed the channel. + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write(message) + + // Add queued chat messages to the current websocket message. + n := len(c.send) + for i := 0; i < n; i++ { + w.Write(newline) + w.Write(<-c.send) + } + + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } + } + } +} + +// serveWs handles websocket requests from the peer. +func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} + client.hub.register <- client + + // Allow collection of memory referenced by the caller by doing all work in + // new goroutines. + go client.writePump() + go client.readPump() +} diff --git a/cmd/wice-signal-ws/hub.go b/cmd/wice-signal-ws/hub.go new file mode 100644 index 00000000..bb5c0e3b --- /dev/null +++ b/cmd/wice-signal-ws/hub.go @@ -0,0 +1,53 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Hub maintains the set of active clients and broadcasts messages to the +// clients. +type Hub struct { + // Registered clients. + clients map[*Client]bool + + // Inbound messages from the clients. + broadcast chan []byte + + // Register requests from the clients. + register chan *Client + + // Unregister requests from clients. + unregister chan *Client +} + +func newHub() *Hub { + return &Hub{ + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + clients: make(map[*Client]bool), + } +} + +func (h *Hub) run() { + for { + select { + case client := <-h.register: + h.clients[client] = true + case client := <-h.unregister: + if _, ok := h.clients[client]; ok { + delete(h.clients, client) + close(client.send) + } + case message := <-h.broadcast: + for client := range h.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(h.clients, client) + } + } + } + } +} diff --git a/cmd/wice-signal-ws/main.go b/cmd/wice-signal-ws/main.go new file mode 100644 index 00000000..04413f4d --- /dev/null +++ b/cmd/wice-signal-ws/main.go @@ -0,0 +1,26 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "log" + "net/http" +) + +var addr = flag.String("addr", ":8080", "http service address") + +func main() { + flag.Parse() + hub := newHub() + go hub.run() + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + serveWs(hub, w, r) + }) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/cmd/wice/main.go b/cmd/wice/main.go new file mode 100644 index 00000000..44e9de07 --- /dev/null +++ b/cmd/wice/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "math/rand" + "os" + "os/signal" + "syscall" + "time" + + "github.com/bombsimon/logrusr" + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/wgctrl" + "k8s.io/klog/v2" + + "riasc.eu/wice/pkg/args" + be "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/intf" + + _ "riasc.eu/wice/pkg/backend/http" + _ "riasc.eu/wice/pkg/backend/k8s" + _ "riasc.eu/wice/pkg/backend/p2p" +) + +func setupLogging() { + klogger := log.StandardLogger() + klogr := logrusr.NewLogger(klogger) + + klog.SetLogger(klogr.WithName("k8s")) + + log.SetFormatter(&log.TextFormatter{ + ForceColors: true, + DisableQuote: true, + }) +} + +func setupRand() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func setupSignals() chan os.Signal { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + + return ch +} + +func main() { + setupLogging() + setupRand() + signals := setupSignals() + + args, err := args.Parse(os.Args[0], os.Args[1:]) + if err != nil { + log.WithError(err).Fatal("Failed to parse arguments") + } + if log.GetLevel() > log.DebugLevel { + args.DumpConfig(os.Stdout) + } + + // Create backend + backend, err := be.NewBackend(args.Backend, args.BackendOptions) + if err != nil { + log.WithError(err).Fatal("Failed to initialize backend") + } + + // Create Wireguard netlink socket + client, err := wgctrl.New() + if err != nil { + log.Fatal(err) + } + + // Create interfaces + interfaces := &intf.Interfaces{} + defer interfaces.CloseAll() + + interfaces.CreateFromArgs(client, backend, args) + + events, errors, err := intf.WatchWireguardInterfaces() + if err != nil { + log.WithError(err).Error("Failed to watch interfaces") + return + } + + log.Debug("Starting initial interface sync") + interfaces.SyncAll(client, backend, args) + + ticker := time.NewTicker(args.WatchInterval) + +out: + for { + select { + // We still a need periodic sync we can not (yet) monitor Wireguard interfaces + // for changes via a netlink socket (patch is pending) + case <-ticker.C: + log.Trace("Starting periodic interface sync") + interfaces.SyncAll(client, backend, args) + + case event := <-events: + log.Trace("Received interface event: %s", event) + interfaces.SyncAll(client, backend, args) + + case err := <-errors: + log.WithError(err).Error("Failed to watch for interface changes") + + case sig := <-signals: + log.WithField("signal", sig).Debug("Received signal") + switch sig { + case syscall.SIGUSR1: + interfaces.SyncAll(client, backend, args) + default: + break out + } + } + } +} diff --git a/cmd/wicectl/main.go b/cmd/wicectl/main.go new file mode 100644 index 00000000..0db886e8 --- /dev/null +++ b/cmd/wicectl/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + // TODO +} diff --git a/docs/Backends.md b/docs/Backends.md new file mode 100644 index 00000000..d1369979 --- /dev/null +++ b/docs/Backends.md @@ -0,0 +1,8 @@ +# Backends + +WICE can support multiple backends for signalling session information such as session IDs, ICE candidates, public keys and STUN credentials. + +## Available backends + +Currently HTTP REST, Kubernetes and libp2p are supported as backends. +Checkout the `Backend` interface in `wice/backend/backend.go` for implementing your own backend. diff --git a/docs/Design.md b/docs/Design.md new file mode 100644 index 00000000..42ba60be --- /dev/null +++ b/docs/Design.md @@ -0,0 +1,29 @@ +# Design + +## Objectives + +- Support [Trickle ICE] +- Support ICE restart +- Support [ICE-TCP] +- Sign and verify ICE offers with Wireguard keys (via [XEdDSA] signature scheme for Curve25519 key pairs) +- Seamless switch between ICE candidates and relays +- Zero configuration + - Eleviate users of exchaging endpoint IPs & ports +- Enables direct communication of Wireguard peers behind NAT / UDP-blocking firewalls +- Single-binary, zero dependency installation + - Bundled ICE agent & Wireguard userspace daemon + - Portablilty +- Support for user and kernel-space Wireguard implementations +- Zero performance impact + - Kernel-side filtering / redirection of Wireguard traffic + - Fallback to userspace proxying only if no Kernel features are available +- Minimized attack surface + - Drop privileges after inital configuration +- Compatible with existing Wireguard configuration utilities like: + - [NetworkManager] + - [systemd-networkd] + - [wg-quick] + - [kilo] +- Monitoring for new Wireguard interfaces and peers + - Inotify for new UAPI sockets in /var/run/wireguard + - Netlink subscription for link updates diff --git a/docs/Proxying.md b/docs/Proxying.md new file mode 100644 index 00000000..5e1e59f9 --- /dev/null +++ b/docs/Proxying.md @@ -0,0 +1,64 @@ +# Proxying + +WICE implements multiple ways of running an ICE agent alongside Wireguard on the same UDP ports. + +## Kernel Wireguard module + +### Userspace Proxy + +For each WG peer a new local UDP socket is opened. +WICE will update the endpoint address of the peer to this the local address of the new sockets. + +Wireguard traffic is proxied by WICE between the local UDP and the ICE socket. + +### RAW Sockets + BPF filter (Kernel) + +We allocate a single RAW socket and assign a BPF filter to this socket which will only match STUN traffic to a specific UDP port. +UDP headers are parsed/produced by WICE. +WICE uses a UDPMux to mux all peers ICE Agents over this single RAW socket. + +### NFtables port-redirection (Kernel) + +Two [Netfilter] (nft) rules are added to filter input & output chains respectivly. +The input rule will match all non-STUN traffic directed at the local port of the ICE candidate and rewrites the UDP destination port to the local listen port of the Wireguard interface. +The output rule will mach all traffic originating from the listen port of the WG interface and directed to the port of the remote cadidate and rewrites the source port to the port of the local ICE candidate. + +Wireguard traffic passes only through the Netfilter chains and remains inside the kernel. +Only STUN binding requests are passed to WICE. + +```bash +$ sudo nft list ruleset +table inet wice { + chain ingress { + type filter hook input priority raw; policy accept; + udp dport 37281 @th,96,32 != 554869826 notrack udp dport set 1001 + } + + chain egress { + type filter hook output priority raw; policy accept; + udp sport 1001 udp dport 38767 notrack udp sport set 37281 + } +} +``` + +## IPTables port-redirection + +Similar to NFTables port-natting by using the legacy IPTables API. + +## Userspace Wireguard implementation + +### Userspace Proxy + +Just like for the Kernel Wireguard module, a dedicated UDP socket for each WG peer is created. +WICE will update the endpoint address of the peer to this the local address of the new sockets. + +Wireguard traffic is proxied by WICE between the local UDP and the ICE socket. + +### In-process socket + +WICE implements wireguard-go's `conn.Bind` interface to handle Wireguard's network IO. + +Wireguard traffic is passed directly between `conn.Bind` and Pion's `ice.Conn`. +No round-trip through the kernel stack is required. + +**Note:** This variant only works for the compiled-in version of wireguard-go in WICE. diff --git a/docs/Signalling.md b/docs/Signalling.md new file mode 100644 index 00000000..d14828bc --- /dev/null +++ b/docs/Signalling.md @@ -0,0 +1,69 @@ +# Session signalling + +Lets assume two Wireguard peers `Pa` & `Pb` are seeking to establish a ICE session. + +The smaller public key (PK) of the two peers takes the role of the controlling agent. +In this example PA is the controlling agent: PK(PA) < PK(PB). + +``` +PA PB + + --- initial offer --> id=SID_Pa, version=0, candidates=[], eoc=false + <-- initial offer --- id=SID_Pb, version=0, candidates=[], eoc=false + + --- subsequent offers --> id=SID_Pa, version=1, candidates=[C1_Pa], eoc=false + <-- subsequent offers --- id=SID_Pb, version=1, candidates=[C1_Pb], eoc=false + + --- subsequent offers --> id=SID_Pa, version=2, candidates=[C1_Pa, C2_Pa], eoc=false + <-- subsequent offers --- id=SID_Pb, version=2, candidates=[C1_Pb, C2_Pb], eoc=false + + --- eoc. offer --> id=SID_Pa, version=3, candidates=[C1_Pa, C2_Pa], eoc=true + <-- eoc. offer --- id=SID_Pb, version=3, candidates=[C1_Pb, C2_Pb], eoc=true +``` + +## Restart + +Agent will restart + - if + - `last_recv.id` has been set + - `recv.id!=last_recv.id` + - `recv.version==0` + + - then + - set + - `local.id=rand()` + - `local.version=0` + - `local.candidates=[]` + - publish new offer + - wait for first offer including candidates from remote + - (re)start agent + - add first received + - start gathering candidates + - send an offers for each candidate `c`: + - `candidates=local.candidates.append(c)` + - `id=local.id` + - `rid=local.rid` + - `version=local.version++` + +## Offer + +Offers are encoded as JSON: + +```json +{ + "id": 1232353452, // Unique session id + "version": 0, // Session version, incremented with each updated offer + "cands": [ // List of ICE candidates + { + "type": "host", + "foundation": "1742129347", + "component": 1, + "network": "udp4", + "priority": 2130706431, + "address": "10.2.0.11", + "port": 37518 + } + ], + "eoc": false // Flag to indicate that all candidates have been gathered (ICE trickle) +} +``` diff --git a/docs/ToDo.md b/docs/ToDo.md new file mode 100644 index 00000000..632ab6ee --- /dev/null +++ b/docs/ToDo.md @@ -0,0 +1,35 @@ +# TODOs + +- [ ] Sign published candidates with XEdDSA signatures +- [ ] Add peer discovery +- [ ] Add libp2p backend +- Separate code into multiple repos: + - [ ] XEdDSA +- Contribute code into existing packages + - [ ] Watch for interfaces in wgctrl +- [ ] Single socket per Wireguard interface / ICE Agent + - Pass traffic in-process between userspace Wireguard and ICE sockets + - Use Wireguard-go's conn.Bind interface +- [ ] Single eBPF program per network NS to steer STUN traffic to ICE Agents + - https://ebpf.io/summit-2020-slides/eBPF_Summit_2020-Lightning-Jakub_Sitnicki-Steering_connections_to_sockets_with_BPF_socke_lookup_hook.pdf +- [ ] Use in-process pipe for wireguard-go's UAPI +- [ ] Update proxy instances instead of recreating them. + - Avoids possible packet loss during change of candidate pairs +- [ ] Use pion/ice's udpmux for creating a RAW socket sharing + - Sharing the same port as Wireguard kernel interface + - Use BPF filters for filtering STUN-only traffic +- [ ] Add better proxy implementations for OpenBSD, FreeBSD, Android and Windows +- [ ] Test co-existance of multipe `wice` instances + - nft tables might collide +- [ ] Use netlink multicast subscription for notification of Wireguard peer changes + - https://lore.kernel.org/patchwork/patch/1366219/ +- [ ] Use netlink multicast group RTMGRP_LINK to for notification of new Wireguard interfaces +- [ ] Add links to code in README +- [ ] Add `XEdDSA` and `VXEdDSA` signature schemes to [JOSE IANA alg registry](https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms) +- [ ] Add wicectl command for controlling `wice` deaemon: + - `wicectl show [[INTF] [PEER]]` + - `wicectl add INTF` + - `wicectl del INTF` + - `wicectl discover INTF GROUP` + - `wicectl sync [INTF]` + - `wicectl restart INTF PEER` diff --git a/docs/Usage.md b/docs/Usage.md new file mode 100644 index 00000000..6ea55f01 --- /dev/null +++ b/docs/Usage.md @@ -0,0 +1,13 @@ +# Usage + +## Daemon + +TODO + +## HTTP Signalling Server + +TODO + +## WebSocket Signalling Server + +TODO diff --git a/docs/UseCases.md b/docs/UseCases.md new file mode 100644 index 00000000..7cd514eb --- /dev/null +++ b/docs/UseCases.md @@ -0,0 +1,15 @@ +# Use-cases + +## Zero-configuration + +**Invocation:** `wice` + +## Start user-space wireguard daemon + +**Invocation:** `wice wg1` + +## Peer discovery + +**Note:** Not implemented yet + +**Invocation:** `wice -discover -backend p2p://my_rendezvouz_phrase` diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..c80915b7 --- /dev/null +++ b/go.mod @@ -0,0 +1,161 @@ +module riasc.eu/wice + +go 1.17 + +require ( + github.com/Scratch-net/vxeddsa v0.0.0-20180216190124-07c00d1c9bf7 + github.com/bombsimon/logrusr v1.1.0 + github.com/cilium/ebpf v0.6.2 + github.com/fsnotify/fsnotify v1.4.9 + github.com/google/gopacket v1.1.19 + github.com/google/nftables v0.0.0-20210514154851-a285acebcad3 + github.com/gorilla/handlers v1.5.1 + github.com/gorilla/mux v1.8.0 + github.com/gorilla/websocket v1.4.2 + github.com/ipfs/go-cid v0.0.7 + github.com/libp2p/go-libp2p v0.14.4 + github.com/libp2p/go-libp2p-core v0.8.5 + github.com/libp2p/go-libp2p-kad-dht v0.12.2 + github.com/multiformats/go-multiaddr v0.3.3 + github.com/multiformats/go-multihash v0.0.15 + github.com/pion/dtls/v2 v2.0.9 + github.com/pion/ice/v2 v2.1.8 + github.com/pion/logging v0.2.2 + github.com/pion/stun v0.3.5 + github.com/pion/transport v0.12.3 + github.com/sirupsen/logrus v1.8.1 + github.com/ucarion/jcs v0.1.2 + github.com/vishvananda/netlink v1.1.0 + github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 + golang.org/x/net v0.0.0-20210504132125-bbd867fde50d + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c + golang.zx2c4.com/wireguard v0.0.0-20210624150102-15b24b6179e0 + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210506160403-92e472f520a5 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + k8s.io/api v0.21.3 + k8s.io/apimachinery v0.21.3 + k8s.io/client-go v0.21.3 + k8s.io/klog/v2 v2.8.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/briandowns/simple-httpd v0.5.0 // indirect + github.com/btcsuite/btcd v0.21.0-beta // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/flynn/noise v1.0.0 // indirect + github.com/go-logr/logr v0.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.4.3 // indirect + github.com/google/go-cmp v0.5.5 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.2.0 // indirect + github.com/googleapis/gnostic v0.4.1 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/huin/goupnp v1.0.0 // indirect + github.com/imdario/mergo v0.3.5 // indirect + github.com/ipfs/go-datastore v0.4.5 // indirect + github.com/ipfs/go-ipfs-util v0.0.2 // indirect + github.com/ipfs/go-ipns v0.0.2 // indirect + github.com/ipfs/go-log v1.0.5 // indirect + github.com/ipfs/go-log/v2 v2.1.3 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect + github.com/jbenet/goprocess v0.1.4 // indirect + github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect + github.com/json-iterator/go v1.1.10 // indirect + github.com/klauspost/cpuid/v2 v2.0.4 // indirect + github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d // indirect + github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d // indirect + github.com/libp2p/go-addr-util v0.0.2 // indirect + github.com/libp2p/go-buffer-pool v0.0.2 // indirect + github.com/libp2p/go-cidranger v1.1.0 // indirect + github.com/libp2p/go-conn-security-multistream v0.2.1 // indirect + github.com/libp2p/go-eventbus v0.2.1 // indirect + github.com/libp2p/go-flow-metrics v0.0.3 // indirect + github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 // indirect + github.com/libp2p/go-libp2p-autonat v0.4.2 // indirect + github.com/libp2p/go-libp2p-blankhost v0.2.0 // indirect + github.com/libp2p/go-libp2p-circuit v0.4.0 // indirect + github.com/libp2p/go-libp2p-discovery v0.5.0 // indirect + github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect + github.com/libp2p/go-libp2p-mplex v0.4.1 // indirect + github.com/libp2p/go-libp2p-nat v0.0.6 // indirect + github.com/libp2p/go-libp2p-noise v0.2.0 // indirect + github.com/libp2p/go-libp2p-peerstore v0.2.7 // indirect + github.com/libp2p/go-libp2p-pnet v0.2.0 // indirect + github.com/libp2p/go-libp2p-record v0.1.3 // indirect + github.com/libp2p/go-libp2p-swarm v0.5.0 // indirect + github.com/libp2p/go-libp2p-tls v0.1.3 // indirect + github.com/libp2p/go-libp2p-transport-upgrader v0.4.2 // indirect + github.com/libp2p/go-libp2p-yamux v0.5.4 // indirect + github.com/libp2p/go-maddr-filter v0.1.0 // indirect + github.com/libp2p/go-mplex v0.3.0 // indirect + github.com/libp2p/go-msgio v0.0.6 // indirect + github.com/libp2p/go-nat v0.0.5 // indirect + github.com/libp2p/go-netroute v0.1.6 // indirect + github.com/libp2p/go-openssl v0.0.7 // indirect + github.com/libp2p/go-reuseport v0.0.2 // indirect + github.com/libp2p/go-reuseport-transport v0.0.4 // indirect + github.com/libp2p/go-sockaddr v0.1.1 // indirect + github.com/libp2p/go-stream-muxer-multistream v0.3.0 // indirect + github.com/libp2p/go-tcp-transport v0.2.4 // indirect + github.com/libp2p/go-ws-transport v0.4.0 // indirect + github.com/libp2p/go-yamux/v2 v2.2.0 // indirect + github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mdlayher/genetlink v1.0.0 // indirect + github.com/mdlayher/netlink v1.4.0 // indirect + github.com/miekg/dns v1.1.41 // indirect + github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect + github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect + github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.0.3 // indirect + github.com/multiformats/go-base36 v0.1.0 // indirect + github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect + github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect + github.com/multiformats/go-multiaddr-net v0.2.0 // indirect + github.com/multiformats/go-multibase v0.0.3 // indirect + github.com/multiformats/go-multistream v0.2.2 // indirect + github.com/multiformats/go-varint v0.0.6 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pion/mdns v0.0.5 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/turn/v2 v2.0.5 // indirect + github.com/pion/udp v0.1.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.10.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.18.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect + github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect + go.opencensus.io v0.23.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.16.0 // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/appengine v1.6.5 // indirect + google.golang.org/protobuf v1.25.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..bda7b0f6 --- /dev/null +++ b/go.sum @@ -0,0 +1,1492 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Scratch-net/vxeddsa v0.0.0-20180216190124-07c00d1c9bf7 h1:uIoKcV3dhX/iud2XD82DLKJa7fgoIWTBUnPk8odVMts= +github.com/Scratch-net/vxeddsa v0.0.0-20180216190124-07c00d1c9bf7/go.mod h1:e/eah4KoWvDvhSSm5bu/LNqR+mwgbr9qt4twvLz0w7s= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bombsimon/logrusr v1.1.0 h1:Y03FI4Z/Shyrc9jF26vuaUbnPxC5NMJnTtJA/3Lihq8= +github.com/bombsimon/logrusr v1.1.0/go.mod h1:Jq0nHtvxabKE5EMwAAdgTaz7dfWE8C4i11NOltxGQpc= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/briandowns/simple-httpd v0.5.0 h1:56XGwsRa0XZgjGW01Q2Dw3cgtKmb7KxzUoiQRVSWABQ= +github.com/briandowns/simple-httpd v0.5.0/go.mod h1:hPItlyy3CLDZWrZPZVDQdlgHm0Am/C333yNsFgj+1MY= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.6.2 h1:iHsfF/t4aW4heW2YKfeHrVPGdtYTL4C4KocpM8KTSnI= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= +github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= +github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/nftables v0.0.0-20210514154851-a285acebcad3 h1:jv+t8JqcvaSeB0r4u3356q7RE5tagFbVC0Bi1x13YFc= +github.com/google/nftables v0.0.0-20210514154851-a285acebcad3/go.mod h1:cfspEyr/Ap+JDIITA+N9a0ernqG0qZ4W1aqMRgDZa1g= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= +github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= +github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= +github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.5 h1:cwOUcGMLdLPWgu3SlrCckCMznaGADbPqE0r8h768/Dg= +github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= +github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= +github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= +github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= +github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= +github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= +github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= +github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= +github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= +github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= +github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= +github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= +github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= +github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-ipns v0.0.2 h1:oq4ErrV4hNQ2Eim257RTYRgfOSV/s8BDaf9iIl4NwFs= +github.com/ipfs/go-ipns v0.0.2/go.mod h1:WChil4e0/m9cIINWLxZe1Jtf77oz5L05rO2ei/uKJ5U= +github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= +github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= +github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= +github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= +github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= +github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= +github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= +github.com/ipfs/go-log/v2 v2.1.3 h1:1iS3IU7aXRlbgUpN8yTTpJ53NXYjAe37vcI5+5nYrzk= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= +github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= +github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc= +github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= +github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= +github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA= +github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw= +github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs= +github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA= +github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do= +github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d h1:MFX8DxRnKMY/2M3H61iSsVbo/n3h0MWGmWNN1UViOU0= +github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d/go.mod h1:QHb4k4cr1fQikUahfcRVPcEXiUgFsdIstGqlurL0XL4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= +github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= +github.com/libp2p/go-addr-util v0.0.2 h1:7cWK5cdA5x72jX0g8iLrQWm5TRJZ6CzGdPEhWj7plWU= +github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= +github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= +github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= +github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= +github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= +github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= +github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= +github.com/libp2p/go-conn-security-multistream v0.2.1 h1:ft6/POSK7F+vl/2qzegnHDaXFU0iWB4yVTYrioC6Zy0= +github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= +github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= +github.com/libp2p/go-eventbus v0.2.1 h1:VanAdErQnpTioN2TowqNcOijf6YwhuODe4pPKSDpxGc= +github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= +github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= +github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= +github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= +github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= +github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= +github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= +github.com/libp2p/go-libp2p v0.13.0/go.mod h1:pM0beYdACRfHO1WcJlp65WXyG2A6NqYM+t2DTVAJxMo= +github.com/libp2p/go-libp2p v0.14.4 h1:QCJE+jGyqxWdrSPuS4jByXCzosgaIg4SJTLCRplJ53w= +github.com/libp2p/go-libp2p v0.14.4/go.mod h1:EIRU0Of4J5S8rkockZM7eJp2S0UrCyi55m2kJVru3rM= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 h1:BM7aaOF7RpmNn9+9g6uTjGJ0cTzWr5j9i9IKeun2M8U= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= +github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= +github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= +github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= +github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= +github.com/libp2p/go-libp2p-autonat v0.4.0/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= +github.com/libp2p/go-libp2p-autonat v0.4.2 h1:YMp7StMi2dof+baaxkbxaizXjY1RPvU71CXfxExzcUU= +github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= +github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= +github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= +github.com/libp2p/go-libp2p-blankhost v0.2.0 h1:3EsGAi0CBGcZ33GwRuXEYJLLPoVWyXJ1bcJzAJjINkk= +github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= +github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= +github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= +github.com/libp2p/go-libp2p-circuit v0.4.0 h1:eqQ3sEYkGTtybWgr6JLqJY6QLtPWRErvFjFDfAOO1wc= +github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= +github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= +github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= +github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= +github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= +github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= +github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA= +github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= +github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= +github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= +github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.5 h1:aEgbIcPGsKy6zYcC+5AJivYFedhYa4sW7mIpWpUaLKw= +github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= +github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= +github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= +github.com/libp2p/go-libp2p-discovery v0.5.0 h1:Qfl+e5+lfDgwdrXdu4YNCWyEo3fWuP+WgN9mN0iWviQ= +github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= +github.com/libp2p/go-libp2p-kad-dht v0.12.2 h1:INBYK7pEPzka5TrAWB2II+PYLeEaRlu6RWIoukfEBFQ= +github.com/libp2p/go-libp2p-kad-dht v0.12.2/go.mod h1:mznpWRg0Nbkr9PB2Dm9XWN24V2BChE3FT1dHmwaDVws= +github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= +github.com/libp2p/go-libp2p-kbucket v0.4.7 h1:spZAcgxifvFZHBD8tErvppbnNiKA5uokDu3CV7axu70= +github.com/libp2p/go-libp2p-kbucket v0.4.7/go.mod h1:XyVo99AfQH0foSf176k4jY1xUJ2+jUJIZCSDm7r2YKk= +github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= +github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= +github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= +github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= +github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= +github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= +github.com/libp2p/go-libp2p-mplex v0.4.1 h1:/pyhkP1nLwjG3OM+VuaNJkQT/Pqq73WzB3aDN3Fx1sc= +github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= +github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= +github.com/libp2p/go-libp2p-nat v0.0.6 h1:wMWis3kYynCbHoyKLPBEMu4YRLltbm8Mk08HGSfvTkU= +github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= +github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= +github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= +github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= +github.com/libp2p/go-libp2p-noise v0.2.0 h1:wmk5nhB9a2w2RxMOyvsoKjizgJOEaJdfAakr0jN8gds= +github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= +github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= +github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= +github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= +github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= +github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= +github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.7 h1:83JoLxyR9OYTnNfB5vvFqvMUv/xDNa6NoPHnENhBsGw= +github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= +github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= +github.com/libp2p/go-libp2p-quic-transport v0.10.0 h1:koDCbWD9CCHwcHZL3/WEvP2A+e/o5/W5L3QS/2SPMA0= +github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= +github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= +github.com/libp2p/go-libp2p-record v0.1.3 h1:R27hoScIhQf/A8XJZ8lYpnqh9LatJ5YbHs28kCIfql0= +github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4= +github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= +github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= +github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= +github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= +github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= +github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= +github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= +github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= +github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= +github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.4.0/go.mod h1:XVFcO52VoLoo0eitSxNQWYq4D6sydGOweTOAjJNraCw= +github.com/libp2p/go-libp2p-swarm v0.5.0 h1:HIK0z3Eqoo8ugmN8YqWAhD2RORgR+3iNXYG4U2PFd1E= +github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= +github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= +github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= +github.com/libp2p/go-libp2p-testing v0.4.0 h1:PrwHRi0IGqOwVQWR3xzgigSlhlLfxgfXgkHxr77EghQ= +github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= +github.com/libp2p/go-libp2p-tls v0.1.3 h1:twKMhMu44jQO+HgQK9X8NHO5HkeJu2QbhLzLJpa8oNM= +github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= +github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= +github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= +github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.0/go.mod h1:J4ko0ObtZSmgn5BX5AmegP+dK3CSnU2lMCKsSq/EY0s= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.2 h1:4JsnbfJzgZeRS9AWN7B9dPqn/LY/HoQTlO9gtdJTIYM= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= +github.com/libp2p/go-libp2p-xor v0.0.0-20200501025846-71e284145d58/go.mod h1:AYjOiqJIdcmI4SXE2ouKQuFrUbE5myv8txWaB2pl4TI= +github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= +github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= +github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= +github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= +github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= +github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= +github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= +github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= +github.com/libp2p/go-libp2p-yamux v0.5.4 h1:/UOPtT/6DHPtr3TtKXBHa6g0Le0szYuI33Xc/Xpd7fQ= +github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= +github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= +github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= +github.com/libp2p/go-maddr-filter v0.1.0 h1:4ACqZKw8AqiuJfwFGq1CYDFugfXTOos+qQ3DETkhtCE= +github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= +github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= +github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= +github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-mplex v0.3.0 h1:U1T+vmCYJaEoDJPV1aq31N56hS+lJgb397GsylNSgrU= +github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.6 h1:lQ7Uc0kS1wb1EfRxO2Eir/RJoHkHn7t6o+EiwsYIKJA= +github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= +github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= +github.com/libp2p/go-nat v0.0.5 h1:qxnwkco8RLKqVh1NmjQ+tJ8p8khNLFxuElYG/TwqW4Q= +github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= +github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= +github.com/libp2p/go-netroute v0.1.6 h1:ruPJStbYyXVYGQ81uzEDzuvbYRLKRrLvTYd33yomC38= +github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= +github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= +github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= +github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= +github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU= +github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= +github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= +github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= +github.com/libp2p/go-reuseport-transport v0.0.4 h1:OZGz0RB620QDGpv300n1zaOcKGGAoGVf8h9txtt/1uM= +github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= +github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-sockaddr v0.1.1 h1:yD80l2ZOdGksnOyHrhxDdTDFrf7Oy+v3FMVArIRgZxQ= +github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= +github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= +github.com/libp2p/go-stream-muxer-multistream v0.3.0 h1:TqnSHPJEIqDEO7h1wZZ0p3DXdvDSiLHQidKKUGZtiOY= +github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= +github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= +github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= +github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= +github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M= +github.com/libp2p/go-tcp-transport v0.2.4 h1:IL5ZAQrkLftufe24mWrmGtTV6drGi6BiXWMTLEM9PBE= +github.com/libp2p/go-tcp-transport v0.2.4/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= +github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= +github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= +github.com/libp2p/go-ws-transport v0.4.0 h1:9tvtQ9xbws6cA5LvqdE6Ne3vcmGB4f1z9SByggk4s0k= +github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= +github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI= +github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= +github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= +github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= +github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= +github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= +github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= +github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY= +github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= +github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= +github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v0.0.0-20191009155606-de872b0d824b/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8= +github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= +github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= +github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys= +github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0= +github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= +github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= +github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= +github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= +github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= +github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= +github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= +github.com/multiformats/go-multiaddr v0.3.3 h1:vo2OTSAqnENB2rLk79pLtr+uhj+VAzSe3uef5q0lRSs= +github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= +github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= +github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= +github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= +github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= +github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= +github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= +github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.2.0 h1:MSXRGN0mFymt6B1yo/6BPnIRpLPEnKgQNvVfCX5VDJk= +github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= +github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= +github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= +github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.15 h1:hWOPdrNqDjwHDx82vsYGSDZNyktOJJ2dzZJzFkOV1jM= +github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= +github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= +github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= +github.com/multiformats/go-multistream v0.2.0/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= +github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= +github.com/multiformats/go-multistream v0.2.2 h1:TCYu1BHTDr1F/Qm75qwYISQdzGcRdC21nFgQW7l7GBo= +github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= +github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8= +github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho= +github.com/pion/ice/v2 v2.1.8 h1:3kV4XaB2C3z1gDUXZmwSB/B0PSdZ7GFFC3w4iUX9prs= +github.com/pion/ice/v2 v2.1.8/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= +github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw= +github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= +github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA= +github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= +github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o= +github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ucarion/jcs v0.1.2 h1:QXKHIA1K7SysMfgf3Q6yyM0jQkpGXoknZiO/eljV7iA= +github.com/ucarion/jcs v0.1.2/go.mod h1:y+kohV2KVa/vR01rJrw7z3Ka/H3kFo+5Nl/bi773ojk= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= +github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= +github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= +github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= +github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= +go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM= +golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= +golang.zx2c4.com/wireguard v0.0.0-20210624150102-15b24b6179e0 h1:qINUmOnDCCF7i14oomDDkGmlda7BSDTGfge77/aqdfk= +golang.zx2c4.com/wireguard v0.0.0-20210624150102-15b24b6179e0/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210506160403-92e472f520a5 h1:LpEwXnbN4q2EIPkqbG9KHBUrducJYDOOdL+eMcJAlFo= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210506160403-92e472f520a5/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= +gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ= +k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= +k8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII= +k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= +k8s.io/client-go v0.21.3 h1:J9nxZTOmvkInRDCzcSNQmPJbDYN/PjlxXT9Mos3HcLg= +k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/internal/ice/filter_udp_mux.go b/internal/ice/filter_udp_mux.go new file mode 100644 index 00000000..1d0dbbdd --- /dev/null +++ b/internal/ice/filter_udp_mux.go @@ -0,0 +1,286 @@ +package ice + +// Based on https://github.com/pion/ice/blob/v2.1.8/udp_mux.go + +import ( + "io" + "net" + "os" + "strings" + "sync" + + "github.com/pion/stun" + + log "github.com/sirupsen/logrus" + netx "riasc.eu/wice/internal/net" +) + +const ( + receiveMTU = 8192 +) + +// UDPMuxDefault is an implementation of the interface +type FilteredUDPMux struct { + params FilteredUDPMuxParams + + closedChan chan struct{} + closeOnce sync.Once + + // conns is a map of all udpMuxedConn indexed by ufrag|network|candidateType + conns map[string]*udpMuxedConn + + addressMapMu sync.RWMutex + addressMap map[string]*udpMuxedConn + + // buffer pool to recycle buffers for net.UDPAddr encodes/decodes + pool *sync.Pool + + mu sync.Mutex +} + +const maxAddrSize = 512 + +// UDPMuxParams are parameters for UDPMux. +type FilteredUDPMuxParams struct { + Logger *log.Entry + Conn *netx.FilteredUDPConn +} + +// NewUDPMuxDefault creates an implementation of UDPMux +func NewFilteredUDPMux(params FilteredUDPMuxParams) *FilteredUDPMux { + if params.Logger == nil { + params.Logger = log.WithField("logger", "ice-mux") + } + + m := &FilteredUDPMux{ + addressMap: map[string]*udpMuxedConn{}, + params: params, + conns: make(map[string]*udpMuxedConn), + closedChan: make(chan struct{}, 1), + pool: &sync.Pool{ + New: func() interface{} { + // big enough buffer to fit both packet and address + return newBufferHolder(receiveMTU + maxAddrSize) + }, + }, + } + + go m.connWorker() + + return m +} + +// LocalAddr returns the listening address of this UDPMuxDefault +func (m *FilteredUDPMux) LocalAddr() net.Addr { + return m.params.Conn.LocalAddr() +} + +// GetConn returns a PacketConn given the connection's ufrag and network +// creates the connection if an existing one can't be found +func (m *FilteredUDPMux) GetConn(ufrag string) (net.PacketConn, error) { + m.mu.Lock() + defer m.mu.Unlock() + + if m.IsClosed() { + return nil, io.ErrClosedPipe + } + + if c, ok := m.conns[ufrag]; ok { + return c, nil + } + + c := m.createMuxedConn(ufrag) + go func() { + <-c.CloseChannel() + m.removeConn(ufrag) + }() + m.conns[ufrag] = c + return c, nil +} + +// RemoveConnByUfrag stops and removes the muxed packet connection +func (m *FilteredUDPMux) RemoveConnByUfrag(ufrag string) { + m.mu.Lock() + removedConns := make([]*udpMuxedConn, 0) + for key := range m.conns { + if key != ufrag { + continue + } + + c := m.conns[key] + delete(m.conns, key) + if c != nil { + removedConns = append(removedConns, c) + } + } + // keep lock section small to avoid deadlock with conn lock + m.mu.Unlock() + + m.addressMapMu.Lock() + defer m.addressMapMu.Unlock() + + for _, c := range removedConns { + addresses := c.getAddresses() + for _, addr := range addresses { + delete(m.addressMap, addr) + } + } +} + +// IsClosed returns true if the mux had been closed +func (m *FilteredUDPMux) IsClosed() bool { + select { + case <-m.closedChan: + return true + default: + return false + } +} + +// Close the mux, no further connections could be created +func (m *FilteredUDPMux) Close() error { + m.params.Logger.Info("Closing mux") + + var err error + m.closeOnce.Do(func() { + m.mu.Lock() + defer m.mu.Unlock() + + for _, c := range m.conns { + _ = c.Close() + } + m.conns = make(map[string]*udpMuxedConn) + close(m.closedChan) + }) + return err +} + +func (m *FilteredUDPMux) removeConn(key string) { + m.mu.Lock() + c := m.conns[key] + delete(m.conns, key) + // keep lock section small to avoid deadlock with conn lock + m.mu.Unlock() + + if c == nil { + return + } + + m.addressMapMu.Lock() + defer m.addressMapMu.Unlock() + + addresses := c.getAddresses() + for _, addr := range addresses { + delete(m.addressMap, addr) + } +} + +func (m *FilteredUDPMux) writeTo(buf []byte, raddr net.Addr) (n int, err error) { + return m.params.Conn.WriteTo(buf, raddr) +} + +func (m *FilteredUDPMux) registerConnForAddress(conn *udpMuxedConn, addr string) { + if m.IsClosed() { + return + } + + m.addressMapMu.Lock() + defer m.addressMapMu.Unlock() + + existing, ok := m.addressMap[addr] + if ok { + existing.removeAddress(addr) + } + m.addressMap[addr] = conn + + m.params.Logger.Debugf("Registered %s for %s", addr, conn.params.Key) +} + +func (m *FilteredUDPMux) createMuxedConn(key string) *udpMuxedConn { + c := newUDPMuxedConn(&udpMuxedConnParams{ + Mux: m, + Key: key, + AddrPool: m.pool, + LocalAddr: m.LocalAddr(), + Logger: m.params.Logger, + }) + return c +} + +func (m *FilteredUDPMux) connWorker() { + logger := m.params.Logger + + defer func() { + _ = m.Close() + }() + + buf := make([]byte, receiveMTU) + for { + n, addr, err := m.params.Conn.ReadFrom(buf) + if m.IsClosed() { + return + } else if err != nil { + if os.IsTimeout(err) { + continue + } else if err != io.EOF { + logger.Errorf("could not read udp packet: %v", err) + } + + return + } + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + logger.Errorf("underlying PacketConn did not return a UDPAddr") + return + } + + // If we have already seen this address dispatch to the appropriate destination + m.addressMapMu.Lock() + destinationConn := m.addressMap[addr.String()] + m.addressMapMu.Unlock() + + // If we haven't seen this address before but is a STUN packet lookup by ufrag + if destinationConn == nil && stun.IsMessage(buf[:n]) { + msg := &stun.Message{ + Raw: append([]byte{}, buf[:n]...), + } + + if err = msg.Decode(); err != nil { + m.params.Logger.Warnf("Failed to handle decode ICE from %s: %v\n", addr.String(), err) + continue + } + + attr, stunAttrErr := msg.Get(stun.AttrUsername) + if stunAttrErr != nil { + m.params.Logger.Warnf("No Username attribute in STUN message from %s\n", addr.String()) + continue + } + + ufrag := strings.Split(string(attr), ":")[0] + + m.mu.Lock() + destinationConn = m.conns[ufrag] + m.mu.Unlock() + } + + if destinationConn == nil { + m.params.Logger.Tracef("Dropping packet from %s, addr: %s", udpAddr.String(), addr.String()) + continue + } + + if err = destinationConn.writePacket(buf[:n], udpAddr); err != nil { + m.params.Logger.Errorf("Could not write packet: %v", err) + } + } +} + +type bufferHolder struct { + buffer []byte +} + +func newBufferHolder(size int) *bufferHolder { + return &bufferHolder{ + buffer: make([]byte, size), + } +} diff --git a/internal/ice/log.go b/internal/ice/log.go new file mode 100644 index 00000000..3c00ee43 --- /dev/null +++ b/internal/ice/log.go @@ -0,0 +1,52 @@ +package ice + +import ( + "strings" + + "github.com/pion/logging" + log "github.com/sirupsen/logrus" +) + +type LoggerFactory struct { +} + +type Logger struct { + log.Entry +} + +func capitalize(msg string) string { + for i, v := range msg { + return strings.ToUpper(string(v)) + msg[i+1:] + } + return "" +} + +func (l *Logger) Debug(msg string) { + l.Entry.Debug(capitalize(msg)) +} + +func (l *Logger) Error(msg string) { + l.Entry.Error(capitalize(msg)) +} + +func (l *Logger) Info(msg string) { + l.Entry.Info(capitalize(msg)) +} + +func (l *Logger) Trace(msg string) { + l.Entry.Trace(capitalize(msg)) +} + +func (l *Logger) Warn(msg string) { + l.Entry.Warn(capitalize(msg)) +} + +func (f *LoggerFactory) NewLogger(scope string) logging.LeveledLogger { + logger := &Logger{ + Entry: *log.WithFields(log.Fields{ + "logger": scope, + }), + } + + return logger +} diff --git a/internal/ice/net.go b/internal/ice/net.go new file mode 100644 index 00000000..6be7dcce --- /dev/null +++ b/internal/ice/net.go @@ -0,0 +1,124 @@ +package ice + +import ( + "fmt" + "net" + "os" + + "github.com/pion/transport/vnet" +) + +// Net represents a local network stack euivalent to a set of layers from NIC +// up to the transport (UDP / TCP) layer. +type Net struct { + ifs []*vnet.Interface +} + +type UDPPacketConn struct { + net.PacketConn +} + +func (c *UDPPacketConn) Read(b []byte) (int, error) { + return 0, nil +} + +func (c *UDPPacketConn) RemoteAddr() net.Addr { + return &net.IPAddr{} +} + +func (c *UDPPacketConn) Write(b []byte) (int, error) { + return 0, nil +} + +func NewNet() *Net { + ifs := []*vnet.Interface{} + if orgIfs, err := net.Interfaces(); err == nil { + for _, orgIfc := range orgIfs { + ifc := vnet.NewInterface(orgIfc) + if addrs, err := orgIfc.Addrs(); err == nil { + for _, addr := range addrs { + ifc.AddAddr(addr) + } + } + + ifs = append(ifs, ifc) + } + } + + return &Net{ifs: ifs} +} + +// Interfaces returns a list of the system's network interfaces. +func (n *Net) Interfaces() ([]*vnet.Interface, error) { + return n.ifs, nil +} + +// InterfaceByName returns the interface specified by name. +func (n *Net) InterfaceByName(name string) (*vnet.Interface, error) { + for _, ifc := range n.ifs { + if ifc.Name == name { + return ifc, nil + } + } + + return nil, fmt.Errorf("interface %s: %w", name, os.ErrNotExist) +} + +// ListenPacket announces on the local network address. +func (n *Net) ListenPacket(network string, address string) (net.PacketConn, error) { + return net.ListenPacket(network, address) +} + +// ListenUDP acts like ListenPacket for UDP networks. +func (n *Net) ListenUDP(network string, locAddr *net.UDPAddr) (vnet.UDPPacketConn, error) { + return net.ListenUDP(network, locAddr) +} + +// Dial connects to the address on the named network. +func (n *Net) Dial(network, address string) (net.Conn, error) { + return net.Dial(network, address) +} + +// CreateDialer creates an instance of vnet.Dialer +func (n *Net) CreateDialer(dialer *net.Dialer) Dialer { + return &vDialer{ + dialer: dialer, + } +} + +// DialUDP acts like Dial for UDP networks. +func (n *Net) DialUDP(network string, laddr, raddr *net.UDPAddr) (vnet.UDPPacketConn, error) { + conn, err := net.DialUDP(network, laddr, raddr) + if err != nil { + return nil, err + } + + return &UDPPacketConn{ + PacketConn: conn, + }, nil +} + +// ResolveUDPAddr returns an address of UDP end point. +func (n *Net) ResolveUDPAddr(network, address string) (*net.UDPAddr, error) { + return net.ResolveUDPAddr(network, address) +} + +// IsVirtual tests if the virtual network is enabled. +func (n *Net) IsVirtual() bool { + return false +} + +// Dialer is identical to net.Dialer excepts that its methods +// (Dial, DialContext) are overridden to use virtual network. +// Use vnet.CreateDialer() to create an instance of this Dialer. +type Dialer interface { + Dial(network, address string) (net.Conn, error) +} + +type vDialer struct { + dialer *net.Dialer +} + +func (d *vDialer) Dial(network, address string) (net.Conn, error) { + return d.dialer.Dial(network, address) +} diff --git a/internal/ice/udp_muxed_conn.go b/internal/ice/udp_muxed_conn.go new file mode 100644 index 00000000..045e3052 --- /dev/null +++ b/internal/ice/udp_muxed_conn.go @@ -0,0 +1,249 @@ +package ice + +// Copied from https://github.com/pion/ice/blob/v2.1.8/udp_muxed_conn.go + +import ( + "encoding/binary" + "io" + "net" + "sync" + "time" + + "github.com/pion/transport/packetio" + + log "github.com/sirupsen/logrus" +) + +type udpMuxedConnParams struct { + Mux *FilteredUDPMux + AddrPool *sync.Pool + Key string + LocalAddr net.Addr + Logger *log.Entry +} + +// udpMuxedConn represents a logical packet conn for a single remote as identified by ufrag +type udpMuxedConn struct { + params *udpMuxedConnParams + // remote addresses that we have sent to on this conn + addresses []string + + // channel holding incoming packets + buffer *packetio.Buffer + closedChan chan struct{} + closeOnce sync.Once + mu sync.Mutex +} + +func newUDPMuxedConn(params *udpMuxedConnParams) *udpMuxedConn { + p := &udpMuxedConn{ + params: params, + buffer: packetio.NewBuffer(), + closedChan: make(chan struct{}), + } + + return p +} + +func (c *udpMuxedConn) ReadFrom(b []byte) (n int, raddr net.Addr, err error) { + buf := c.params.AddrPool.Get().(*bufferHolder) + defer c.params.AddrPool.Put(buf) + + // read address + total, err := c.buffer.Read(buf.buffer) + if err != nil { + return 0, nil, err + } + + dataLen := int(binary.LittleEndian.Uint16(buf.buffer[:2])) + if dataLen > total || dataLen > len(b) { + return 0, nil, io.ErrShortBuffer + } + + // read data and then address + offset := 2 + copy(b, buf.buffer[offset:offset+dataLen]) + offset += dataLen + + // read address len & decode address + addrLen := int(binary.LittleEndian.Uint16(buf.buffer[offset : offset+2])) + offset += 2 + + if raddr, err = decodeUDPAddr(buf.buffer[offset : offset+addrLen]); err != nil { + return 0, nil, err + } + + return dataLen, raddr, nil +} + +func (c *udpMuxedConn) WriteTo(buf []byte, raddr net.Addr) (n int, err error) { + if c.isClosed() { + return 0, io.ErrClosedPipe + } + // each time we write to a new address, we'll register it with the mux + addr := raddr.String() + if !c.containsAddress(addr) { + c.addAddress(addr) + } + + return c.params.Mux.writeTo(buf, raddr) +} + +func (c *udpMuxedConn) LocalAddr() net.Addr { + return c.params.LocalAddr +} + +func (c *udpMuxedConn) SetDeadline(tm time.Time) error { + return nil +} + +func (c *udpMuxedConn) SetReadDeadline(tm time.Time) error { + return nil +} + +func (c *udpMuxedConn) SetWriteDeadline(tm time.Time) error { + return nil +} + +func (c *udpMuxedConn) CloseChannel() <-chan struct{} { + return c.closedChan +} + +func (c *udpMuxedConn) Close() error { + var err error + c.closeOnce.Do(func() { + err = c.buffer.Close() + close(c.closedChan) + }) + c.mu.Lock() + defer c.mu.Unlock() + c.addresses = nil + return err +} + +func (c *udpMuxedConn) isClosed() bool { + select { + case <-c.closedChan: + return true + default: + return false + } +} + +func (c *udpMuxedConn) getAddresses() []string { + c.mu.Lock() + defer c.mu.Unlock() + addresses := make([]string, len(c.addresses)) + copy(addresses, c.addresses) + return addresses +} + +func (c *udpMuxedConn) addAddress(addr string) { + c.mu.Lock() + c.addresses = append(c.addresses, addr) + c.mu.Unlock() + + // map it on mux + c.params.Mux.registerConnForAddress(c, addr) +} + +func (c *udpMuxedConn) removeAddress(addr string) { + c.mu.Lock() + defer c.mu.Unlock() + + newAddresses := make([]string, 0, len(c.addresses)) + for _, a := range c.addresses { + if a != addr { + newAddresses = append(newAddresses, a) + } + } + + c.addresses = newAddresses +} + +func (c *udpMuxedConn) containsAddress(addr string) bool { + c.mu.Lock() + defer c.mu.Unlock() + for _, a := range c.addresses { + if addr == a { + return true + } + } + return false +} + +func (c *udpMuxedConn) writePacket(data []byte, addr *net.UDPAddr) error { + // write two packets, address and data + buf := c.params.AddrPool.Get().(*bufferHolder) + defer c.params.AddrPool.Put(buf) + + // format of buffer | data len | data bytes | addr len | addr bytes | + if len(buf.buffer) < len(data)+maxAddrSize { + return io.ErrShortBuffer + } + // data len + binary.LittleEndian.PutUint16(buf.buffer, uint16(len(data))) + offset := 2 + + // data + copy(buf.buffer[offset:], data) + offset += len(data) + + // write address first, leaving room for its length + n, err := encodeUDPAddr(addr, buf.buffer[offset+2:]) + if err != nil { + return nil + } + total := offset + n + 2 + + // address len + binary.LittleEndian.PutUint16(buf.buffer[offset:], uint16(n)) + + if _, err := c.buffer.Write(buf.buffer[:total]); err != nil { + return err + } + return nil +} + +func encodeUDPAddr(addr *net.UDPAddr, buf []byte) (int, error) { + ipdata, err := addr.IP.MarshalText() + if err != nil { + return 0, err + } + total := 2 + len(ipdata) + 2 + len(addr.Zone) + if total > len(buf) { + return 0, io.ErrShortBuffer + } + + binary.LittleEndian.PutUint16(buf, uint16(len(ipdata))) + offset := 2 + n := copy(buf[offset:], ipdata) + offset += n + binary.LittleEndian.PutUint16(buf[offset:], uint16(addr.Port)) + offset += 2 + copy(buf[offset:], addr.Zone) + return total, nil +} + +func decodeUDPAddr(buf []byte) (*net.UDPAddr, error) { + addr := net.UDPAddr{} + + offset := 0 + ipLen := int(binary.LittleEndian.Uint16(buf[:2])) + offset += 2 + // basic bounds checking + if ipLen+offset > len(buf) { + return nil, io.ErrShortBuffer + } + if err := addr.IP.UnmarshalText(buf[offset : offset+ipLen]); err != nil { + return nil, err + } + offset += ipLen + addr.Port = int(binary.LittleEndian.Uint16(buf[offset : offset+2])) + offset += 2 + zone := make([]byte, len(buf[offset:])) + copy(zone, buf[offset:]) + addr.Zone = string(zone) + + return &addr, nil +} diff --git a/internal/net/filter_udp_conn.go b/internal/net/filter_udp_conn.go new file mode 100644 index 00000000..c61b74f9 --- /dev/null +++ b/internal/net/filter_udp_conn.go @@ -0,0 +1,140 @@ +package ice + +import ( + "encoding/hex" + "fmt" + "net" + + "github.com/cilium/ebpf" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "golang.org/x/net/bpf" + + "syscall" + + log "github.com/sirupsen/logrus" +) + +const ( + SO_ATTACH_BPF int = 50 + SO_ATTACH_FILTER int = 26 +) + +// Filter represents a classic BPF filter program that can be applied to a socket +type Filter []bpf.Instruction + +type FilteredUDPConn struct { + fd int + + localAddr net.UDPAddr +} + +func (fuc *FilteredUDPConn) LocalAddr() net.Addr { + + return &fuc.localAddr +} + +func (fuc *FilteredUDPConn) ReadFrom(buf []byte) (n int, addr net.Addr, err error) { + n, rAddr, err := syscall.Recvfrom(fuc.fd, buf, 0) + if err != nil { + return -1, nil, err + } + + rAddrIn4, ok := rAddr.(*syscall.SockaddrInet4) + if !ok { + return -1, nil, fmt.Errorf("invalid address type") + } + + packet := gopacket.NewPacket(buf[:n], layers.LayerTypeIPv4, gopacket.DecodeOptions{ + Lazy: true, + NoCopy: true, + }) + + transport := packet.TransportLayer() + if transport == nil { + return -1, nil, fmt.Errorf("failed to decode packet") + } + udp, ok := transport.(*layers.UDP) + if !ok { + return -1, nil, fmt.Errorf("invalid layer type") + } + payload := packet.ApplicationLayer() + + rUDPAddr := &net.UDPAddr{ + IP: rAddrIn4.Addr[:], + Port: int(udp.SrcPort), + } + + n = len(payload.Payload()) + + copy(buf[:n], payload.Payload()[:]) + + log.Tracef("ReadFrom: ra=%s, len=%d, buf=%s", rUDPAddr, n, hex.EncodeToString(buf[:n])) + + return n, rUDPAddr, nil +} + +func (fuc *FilteredUDPConn) WriteTo(buf []byte, rAddr net.Addr) (n int, err error) { + rUDPAddr, ok := rAddr.(*net.UDPAddr) + if !ok { + return -1, fmt.Errorf("invalid address type") + } + + rSockAddr := &syscall.SockaddrInet4{ + Port: 0, + } + copy(rSockAddr.Addr[:], rUDPAddr.IP.To4()) + + buffer := gopacket.NewSerializeBuffer() + payload := gopacket.Payload(buf) + ip := &layers.IPv4{ + Version: 4, + TTL: 64, + SrcIP: fuc.localAddr.IP, + DstIP: rUDPAddr.IP, + Protocol: layers.IPProtocolUDP, + } + udp := &layers.UDP{ + SrcPort: layers.UDPPort(fuc.localAddr.Port), + DstPort: layers.UDPPort(rUDPAddr.Port), + } + udp.SetNetworkLayerForChecksum(ip) + seropts := gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + } + if err := gopacket.SerializeLayers(buffer, seropts, udp, payload); err != nil { + return -1, fmt.Errorf("failed serialize packet: %s", err) + } + + syscall.Sendto(fuc.fd, buffer.Bytes(), 0, rSockAddr) + + return 0, nil +} + +func (fuc *FilteredUDPConn) ApplyFilter(prog *ebpf.Program) error { + // Attach filter program + if err := syscall.SetsockoptInt(fuc.fd, syscall.SOL_SOCKET, SO_ATTACH_BPF, prog.FD()); err != nil { + return fmt.Errorf("failed setsockopt(fd, SOL_SOCKET, SO_ATTACH_BPF): %w", err) + } + + return nil +} + +func (fuc *FilteredUDPConn) Close() error { + return nil // TODO +} + +func NewFilteredUDPConn(lAddr net.UDPAddr) (fuc *FilteredUDPConn, err error) { + fuc = &FilteredUDPConn{ + localAddr: lAddr, + } + + // Open a raw socket + fuc.fd, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_UDP) + if err != nil { + panic(err) + } + + return fuc, nil +} diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 00000000..5dbb7805 --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,68 @@ +package util + +import ( + "bytes" + "encoding/base64" + "math/rand" + "net" + "syscall" + "unsafe" +) + +type Less func(i, j int) bool + +func CmpEndpoint(a, b *net.UDPAddr) int { + if a == nil && b == nil { + return 0 + } + if (a != nil && b == nil) || (a == nil && b != nil) { + return 1 + } + if !a.IP.Equal(b.IP) || a.Port != b.Port || a.Zone != b.Zone { + return 1 + } + return 0 +} + +func CmpNet(a, b *net.IPNet) int { + cmp := bytes.Compare(a.Mask, b.Mask) + if cmp != 0 { + return cmp + } + + return bytes.Compare(a.IP, b.IP) +} + +// func lessNets(nets []net.IPNet) Less { +// return func(i, j int) bool { return cmpNet(&nets[i], &nets[j]) < 0 } +// } + +// GenerateRandomBytes returns securely generated random bytes. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + // Note that err == nil only if we read len(b) bytes. + if err != nil { + return nil, err + } + + return b, nil +} + +// GenerateRandomString returns a URL-safe, base64 encoded +// securely generated random string. +func GenerateRandomString(s int) (string, error) { + b, err := GenerateRandomBytes(s) + return base64.URLEncoding.EncodeToString(b), err +} + +func SetsockoptBytes(fd int, level int, opt int, b []byte) syscall.Errno { + _, _, errno := syscall.Syscall6(syscall.SYS_SETSOCKOPT, + uintptr(fd), uintptr(level), uintptr(opt), + uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0) + + return errno +} diff --git a/internal/util/util_test.go b/internal/util/util_test.go new file mode 100644 index 00000000..fed52aa4 --- /dev/null +++ b/internal/util/util_test.go @@ -0,0 +1,73 @@ +package util_test + +import ( + "net" + "testing" + + "riasc.eu/wice/internal/util" +) + +func TestCmpEndpointEqual(t *testing.T) { + a := net.UDPAddr{ + IP: net.ParseIP("1.1.1.1"), + Port: 1, + } + + if util.CmpEndpoint(&a, &a) != 0 { + t.Fail() + } +} + +func TestCmpEndpointUnequal(t *testing.T) { + a := net.UDPAddr{ + IP: net.ParseIP("1.1.1.1"), + Port: 1, + } + + b := net.UDPAddr{ + IP: net.ParseIP("2.2.2.2"), + Port: 1, + } + + if util.CmpEndpoint(&a, &b) == 0 { + t.Fail() + } +} + +func TestGenerateRandomBytes(t *testing.T) { + r, err := util.GenerateRandomBytes(16) + if err != nil { + t.Fail() + } + + if len(r) != 16 { + t.Fail() + } +} + +func TestCmpNetEqual(t *testing.T) { + _, a, err := net.ParseCIDR("1.1.1.1/0") + if err != nil { + t.Fail() + } + + if util.CmpNet(a, a) != 0 { + t.Fail() + } +} + +func TestCmpNetUnequal(t *testing.T) { + _, a, err := net.ParseCIDR("1.1.1.1/0") + if err != nil { + t.Fail() + } + + _, b, err := net.ParseCIDR("1.1.1.1/1") + if err != nil { + t.Fail() + } + + if util.CmpNet(a, b) == 0 { + t.Fail() + } +} diff --git a/internal/wg/bind.go b/internal/wg/bind.go new file mode 100644 index 00000000..59e78c9a --- /dev/null +++ b/internal/wg/bind.go @@ -0,0 +1,121 @@ +package wg + +import ( + "net" + + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/conn" +) + +type IceBind struct { + // Interface *intf.Interface +} + +type IceEndpoint struct { + net.UDPAddr + + // Peer *intf.Peer + + String string +} + +// clears the source address +func (ep *IceEndpoint) ClearSrc() { + log.Debugf("EP %s ClearSrc()", ep.String) +} + +// returns the local source address (ip:port) +func (ep *IceEndpoint) SrcToString() string { + log.Debugf("EP %s SrcToString()", ep.String) + + return ep.String + ":src" +} + +// returns the destination address (ip:port) +func (ep *IceEndpoint) DstToString() string { + log.Debugf("EP %s DstToString()", ep.String) + + return ep.String + ":dst" +} + +// used for mac2 cookie calculations +func (ep *IceEndpoint) DstToBytes() []byte { + log.Debugf("EP %s DstToBytes()", ep.String) + + return []byte(ep.String) +} + +func (ep *IceEndpoint) DstIP() net.IP { + log.Debugf("EP %s DstIP()", ep.String) + + return ep.IP +} + +func (ep *IceEndpoint) SrcIP() net.IP { + log.Debugf("EP %s SrcIP()", ep.String) + + return ep.IP +} + +func NewIceBind() conn.Bind { + return &IceBind{ + // Interface: i, + } +} + +// Open puts the Bind into a listening state on a given port and reports the actual +// port that it bound to. Passing zero results in a random selection. +// fns is the set of functions that will be called to receive packets. +func (b *IceBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) { + log.Debugf("Bind Open(port=%d)", port) + + fns = append(fns, b.receive) + + return fns, 0, nil +} + +// Close closes the Bind listener. +// All fns returned by Open must return net.ErrClosed after a call to Close. +func (b *IceBind) Close() error { + log.Debug("Bind Close()") + + return nil +} + +// SetMark sets the mark for each packet sent through this Bind. +// This mark is passed to the kernel as the socket option SO_MARK. +func (b *IceBind) SetMark(mark uint32) error { + log.Debugf("Bind SetMark(mark=%d)", mark) + + return nil // Stub +} + +// Send writes a packet b to address ep. +func (b *IceBind) Send(buf []byte, ep conn.Endpoint) error { + log.Debugf("Bind Send(len=%d, ep=%s)", len(buf), ep.(*IceEndpoint).String) + + return nil +} + +// ParseEndpoint creates a new endpoint from a string. +func (b *IceBind) ParseEndpoint(s string) (ep conn.Endpoint, err error) { + log.Debugf("Bind ParseEndpoints(%s)", s) + + addr, err := net.ResolveUDPAddr("udp", s) + if err != nil { + return &IceEndpoint{}, err + } + + return &IceEndpoint{ + UDPAddr: *addr, + String: s, + }, nil +} + +func (b *IceBind) receive(buf []byte) (n int, ep conn.Endpoint, err error) { + log.Debug("Bind receive()") + + buf[0] = 1 + + return 1, &IceEndpoint{}, nil +} diff --git a/internal/wg/compare.go b/internal/wg/compare.go new file mode 100644 index 00000000..734b4298 --- /dev/null +++ b/internal/wg/compare.go @@ -0,0 +1,17 @@ +package wg + +import ( + "bytes" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type Less func(i, j int) bool + +func CmpPeers(a, b *wgtypes.Peer) int { + return bytes.Compare(a.PublicKey[:], b.PublicKey[:]) +} + +func LessPeers(peers []wgtypes.Peer) Less { + return func(i, j int) bool { return CmpPeers(&peers[i], &peers[j]) < 0 } +} diff --git a/internal/wg/compare_test.go b/internal/wg/compare_test.go new file mode 100644 index 00000000..fa234460 --- /dev/null +++ b/internal/wg/compare_test.go @@ -0,0 +1,27 @@ +package wg_test + +import ( + "testing" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "riasc.eu/wice/internal/wg" +) + +func TestCmpPeersEqual(t *testing.T) { + a := wgtypes.Peer{} + b := wgtypes.Peer{} + + if wg.CmpPeers(&a, &a) != 0 { + t.Fail() + } + + var err error + b.PublicKey, err = wgtypes.GenerateKey() + if err != nil { + t.Fail() + } + + if wg.CmpPeers(&a, &b) >= 0 { + t.Fail() + } +} diff --git a/pkg/args/args.go b/pkg/args/args.go new file mode 100644 index 00000000..ab130276 --- /dev/null +++ b/pkg/args/args.go @@ -0,0 +1,340 @@ +package args + +import ( + "flag" + "fmt" + "io" + "net/url" + "os" + "regexp" + "strings" + "time" + + log "github.com/sirupsen/logrus" + + pice "riasc.eu/wice/internal/ice" + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/proxy" + + "github.com/pion/ice/v2" +) + +// Copied from pion/ice/agent_config.go +const ( + // defaultCheckInterval is the interval at which the agent performs candidate checks in the connecting phase + defaultCheckInterval = 200 * time.Millisecond + + // keepaliveInterval used to keep candidates alive + defaultKeepaliveInterval = 2 * time.Second + + // defaultDisconnectedTimeout is the default time till an Agent transitions disconnected + defaultDisconnectedTimeout = 5 * time.Second + + // defaultFailedTimeout is the default time till an Agent transitions to failed after disconnected + defaultFailedTimeout = 25 * time.Second + + // max binding request before considering a pair failed + defaultMaxBindingRequests = 7 +) + +type arrayFlags []string + +func (i *arrayFlags) String() string { + return strings.Join(*i, ",") +} + +func (i *arrayFlags) Set(value string) error { + *i = append(*i, value) + return nil +} + +type Args struct { + Backend *url.URL + BackendOptions map[string]string + User bool + ProxyType proxy.ProxyType + // Discover bool + ConfigSync bool + ConfigPath string + WatchInterval time.Duration + RestartInterval time.Duration + + InterfaceRegex *regexp.Regexp + IceInterfaceRegex *regexp.Regexp + AgentConfig ice.AgentConfig + + Interfaces []string +} + +var yesno = map[bool]string{ + true: "yes", + false: "no", +} + +func showUsage() { + fmt.Fprintf(os.Stderr, "usage: %s [OPTIONS] [IFACES ...]\n", os.Args[0]) + fmt.Println() + fmt.Println(" IFACES is a list of Wireguard interfaces") + fmt.Println(" (defaults to all available Wireguard interfaces)") + fmt.Println("") + fmt.Println(("Available OPTIONS are:")) + flag.PrintDefaults() + fmt.Println() + fmt.Println(" ** These options can be specified multiple times") + fmt.Println() + fmt.Println("Available backends types are:") + for name, plugin := range backend.Backends { + fmt.Printf(" %-7s %s\n", name, plugin.Description) + } +} + +func (a *Args) DumpConfig(wr io.Writer) { + fmt.Fprintln(wr, "Options:") + fmt.Fprintln(wr, " URLs:") + for _, u := range a.AgentConfig.Urls { + fmt.Fprintf(wr, " %s\n", u.String()) + } + + fmt.Fprintln(wr, " Interfaces:") + for _, d := range a.Interfaces { + fmt.Fprintf(wr, " %s\n", d) + } + + fmt.Fprintf(wr, " User: %s\n", yesno[a.User]) + fmt.Fprintf(wr, " ProxyType: %s\n", a.ProxyType.String()) + + fmt.Fprintf(wr, " Backend: %s\n", a.Backend.String()) + fmt.Fprintln(wr, " Backend options:") + for k := range a.BackendOptions { + fmt.Fprintf(wr, " %s=%s\n", k, a.BackendOptions[k]) + } +} + +func candidateTypeFromString(t string) (ice.CandidateType, error) { + switch t { + case "host": + return ice.CandidateTypeHost, nil + case "srflx": + return ice.CandidateTypeServerReflexive, nil + case "prflx": + return ice.CandidateTypePeerReflexive, nil + case "relay": + return ice.CandidateTypeRelay, nil + default: + return ice.CandidateTypeUnspecified, fmt.Errorf("unknown candidate type: %s", t) + } +} + +func networkTypeFromString(t string) (ice.NetworkType, error) { + switch t { + case "udp4": + return ice.NetworkTypeUDP4, nil + case "udp6": + return ice.NetworkTypeUDP6, nil + case "tcp4": + return ice.NetworkTypeTCP4, nil + case "tcp6": + return ice.NetworkTypeTCP6, nil + default: + return ice.NetworkTypeTCP4, fmt.Errorf("unknown network type: %s", t) + } +} + +func Parse(progname string, argv []string) (*Args, error) { + var uri string + var err error + + var iceURLs, iceCandidateTypes, iceNetworkTypes, iceNat1to1IPs arrayFlags + + flags := flag.NewFlagSet(progname, flag.ContinueOnError) + + flags.Usage = showUsage + + logLevel := flags.String("log-level", "info", "log level (one of \"panic\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\")") + // discover := flag.Bool("discover", false, "discover peers using the backend") + backend := flags.String("backend", "http://localhost:8080", "backend URL") + backendOpts := flags.String("backend-opts", "", "comma-separated list of additional backend options (e.g. \"key1=val1,key2-val2\")") + user := flags.Bool("user", false, "start userspace Wireguard daemon") + proxyType := flags.String("proxy", "auto", "proxy type to use") + interfaceFilter := flags.String("interface-filter", ".*", "regex for filtering Wireguard interfaces (e.g. \"wg-.*\")") + configSync := flags.Bool("config-sync", false, "sync Wireguard interface with configuration file (see \"wg synconf\"") + configPath := flags.String("config-path", "/etc/wireguard", "base path to search for Wireguard configuration files") + watchInterval := flags.Duration("watch-interval", 2*time.Second, "interval at which we are polling the kernel for updates on the Wireguard interfaces") + + // ice.AgentConfig fields + flags.Var(&iceURLs, "url", "STUN and/or TURN server address (**)") + flags.Var(&iceCandidateTypes, "ice-candidate-type", "usable candidate types (**, one of \"host\", \"srflx\", \"prflx\", \"relay\")") + flags.Var(&iceNetworkTypes, "ice-network-type", "usable network types (**, select from \"udp4\", \"udp6\", \"tcp4\", \"tcp6\")") + flags.Var(&iceNat1to1IPs, "ice-nat-1to1-ips", "list of IP addresses which will be added as local server reflexive candidates (**)") + + icePortMin := flags.Uint("ice-port-min", 0, "minimum port for allocation policy (range: 0-65535)") + icePortMax := flags.Uint("ice-port-max", 0, "maximum port for allocation policy (range: 0-65535)") + iceLite := flags.Bool("ice-lite", false, "lite agents do not perform connectivity check and only provide host candidates") + iceMdns := flags.Bool("ice-mdns", false, "enable local Multicast DNS discovery") + iceMaxBindingRequests := flags.Int("ice-max-binding-requests", defaultMaxBindingRequests, "maximum number of binding request before considering a pair failed") + iceInsecureSkipVerify := flags.Bool("ice-insecure-skip-verify", false, "skip verification of TLS certificates for secure STUN/TURN servers") + iceInterfaceFilter := flags.String("ice-interface-filter", ".*", "regex for filtering local interfaces for ICE candidate gathering (e.g. \"eth[0-9]+\")") + iceDisconnectedTimeout := flags.Duration("ice-disconnected-timout", defaultDisconnectedTimeout, "time till an Agent transitions disconnected") + iceFailedTimeout := flags.Duration("ice-failed-timeout", defaultFailedTimeout, "time until an Agent transitions to failed after disconnected") + iceKeepaliveInterval := flags.Duration("ice-keepalive-interval", defaultKeepaliveInterval, "interval netween STUN keepalives") + iceCheckInterval := flags.Duration("ice-check-interval", defaultCheckInterval, "interval at which the agent performs candidate checks in the connecting phase") + iceRestartInterval := flags.Duration("ice-restart-interval", defaultDisconnectedTimeout, "time to wait before ICE restart") + iceUsername := flags.String("ice-user", "", "username for STUN/TURN credentials") + icePassword := flags.String("ice-pass", "", "password for STUN/TURN credentials") + // iceMaxBindingRequestTimeout := flag.Duration("ice-max-binding-request-timeout", maxBindingRequestTimeout, "wait time before binding requests can be deleted") + + flags.Parse(argv) + + args := &Args{ + User: *user, + ProxyType: proxy.ProxyTypeFromString(*proxyType), + BackendOptions: make(map[string]string), + // Discover: *discover, + ConfigSync: *configSync, + ConfigPath: *configPath, + WatchInterval: *watchInterval, + RestartInterval: *iceRestartInterval, + Interfaces: flag.Args(), + AgentConfig: ice.AgentConfig{ + PortMin: uint16(*icePortMin), + PortMax: uint16(*icePortMax), + Lite: *iceLite, + InsecureSkipVerify: *iceInsecureSkipVerify, + }, + } + + // Find best proxy method + if args.ProxyType == proxy.ProxyTypeAuto { + args.ProxyType = proxy.AutoProxy() + } + + // Check proxy type + if args.ProxyType == proxy.ProxyTypeInvalid { + return nil, fmt.Errorf("invalid proxy type: %s", *proxyType) + } + + // Compile interface regex + args.InterfaceRegex, err = regexp.Compile(*interfaceFilter) + if err != nil { + return nil, fmt.Errorf("invalid interface filter: %w", err) + } + + // Parse log level + if lvl, err := log.ParseLevel(*logLevel); err != nil { + return nil, fmt.Errorf("invalid log level: %s", *logLevel) + } else { + log.SetLevel(lvl) + } + + // Parse backend URI + if !strings.Contains(*backend, ":") { + *backend += ":" + } + if args.Backend, err = url.Parse(*backend); err != nil { + return nil, fmt.Errorf("invalid URI: %w", err) + } + + // Parse additional backend options + if *backendOpts != "" { + opts := strings.Split(*backendOpts, ",") + for _, opt := range opts { + kv := strings.SplitN(opt, "=", 2) + if len(kv) < 2 { + return nil, fmt.Errorf("invalid backend option: %s", opt) + } + + key := kv[0] + value := kv[1] + + args.BackendOptions[key] = value + } + } + + if *iceMaxBindingRequests >= 0 { + maxBindingReqs := uint16(*iceMaxBindingRequests) + args.AgentConfig.MaxBindingRequests = &maxBindingReqs + } + if *iceMdns { + args.AgentConfig.MulticastDNSMode = ice.MulticastDNSModeQueryAndGather + } + if *iceDisconnectedTimeout > 0 { + args.AgentConfig.DisconnectedTimeout = iceDisconnectedTimeout + } + if *iceFailedTimeout > 0 { + args.AgentConfig.FailedTimeout = iceFailedTimeout + } + if *iceKeepaliveInterval > 0 { + args.AgentConfig.KeepaliveInterval = iceKeepaliveInterval + } + if *iceCheckInterval > 0 { + args.AgentConfig.CheckInterval = iceCheckInterval + } + if len(iceNat1to1IPs) > 0 { + args.AgentConfig.NAT1To1IPCandidateType = ice.CandidateTypeServerReflexive + args.AgentConfig.NAT1To1IPs = iceNat1to1IPs + } + + args.IceInterfaceRegex, err = regexp.Compile(*iceInterfaceFilter) + if err != nil { + return nil, fmt.Errorf("failed to compile interface regex: %w", err) + } + + // Parse candidate types + for _, c := range iceCandidateTypes { + candType, err := candidateTypeFromString(c) + if err != nil { + return nil, err + } + args.AgentConfig.CandidateTypes = append(args.AgentConfig.CandidateTypes, candType) + } + + // Parse network types + if len(iceNetworkTypes) == 0 { + args.AgentConfig.NetworkTypes = []ice.NetworkType{ + ice.NetworkTypeUDP4, + ice.NetworkTypeUDP6, + } + } else { + for _, n := range iceNetworkTypes { + netType, err := networkTypeFromString(n) + if err != nil { + return nil, err + } + args.AgentConfig.NetworkTypes = append(args.AgentConfig.NetworkTypes, netType) + } + } + + // Parse ICE urls + for _, uri = range iceURLs { + iceUrl, err := ice.ParseURL(uri) + if err != nil { + return nil, fmt.Errorf("failed to parse url %s: %w", uri, err) + } + + if *iceUsername != "" { + iceUrl.Username = *iceUsername + } + if *icePassword != "" { + iceUrl.Password = *icePassword + } + + args.AgentConfig.Urls = append(args.AgentConfig.Urls, iceUrl) + } + + // Add default STUN server + if len(args.AgentConfig.Urls) == 0 { + url := &ice.URL{ + Scheme: ice.SchemeTypeSTUN, + Host: "stun.l.google.com", + Port: 19302, + Username: "", + Password: "", + Proto: ice.ProtoTypeUDP, + } + args.AgentConfig.Urls = append(args.AgentConfig.Urls, url) + } + + args.AgentConfig.LoggerFactory = &pice.LoggerFactory{} + + return args, nil +} diff --git a/pkg/args/args_test.go b/pkg/args/args_test.go new file mode 100644 index 00000000..cd39a953 --- /dev/null +++ b/pkg/args/args_test.go @@ -0,0 +1,113 @@ +package args_test + +import ( + "testing" + + "github.com/pion/ice/v2" + "riasc.eu/wice/pkg/args" +) + +func TestParseArgsUser(t *testing.T) { + config, err := args.Parse("prog", []string{"-user"}) + if err != nil { + t.Errorf("err got %v, want nil", err) + } + + if !config.User { + t.Fail() + } +} + +func TestParseArgsBackend(t *testing.T) { + config, err := args.Parse("prog", []string{"-backend", "k8s", "-backend-opts", "key=value"}) + if err != nil { + t.Errorf("err got %v, want nil", err) + } + + if config.Backend.Scheme != "k8s" { + t.Fail() + } + + if config.BackendOptions["key"] != "value" { + t.Fail() + } +} + +func TestParseArgsUrls(t *testing.T) { + config, err := args.Parse("prog", []string{"-url", "stun:stun.riasc.eu", "-url", "turn:turn.riasc.eu"}) + if err != nil { + t.Errorf("err got %v, want nil", err) + } + + if len(config.AgentConfig.Urls) != 2 { + t.Fail() + } + + if config.AgentConfig.Urls[0].Host != "stun.riasc.eu" { + t.Fail() + } + + if config.AgentConfig.Urls[0].Scheme != ice.SchemeTypeSTUN { + t.Fail() + } + + if config.AgentConfig.Urls[1].Host != "turn.riasc.eu" { + t.Fail() + } + + if config.AgentConfig.Urls[1].Scheme != ice.SchemeTypeTURN { + t.Fail() + } +} + +func TestParseArgsCandidateTypes(t *testing.T) { + config, err := args.Parse("prog", []string{"-ice-candidate-type", "host", "-ice-candidate-type", "relay"}) + if err != nil { + t.Errorf("err got %v, want nil", err) + } + + if len(config.AgentConfig.CandidateTypes) != 2 { + t.Fail() + } + + if config.AgentConfig.CandidateTypes[0] != ice.CandidateTypeHost { + t.Fail() + } + + if config.AgentConfig.CandidateTypes[1] != ice.CandidateTypeRelay { + t.Fail() + } +} + +func TestParseArgsInterfaceFilter(t *testing.T) { + config, err := args.Parse("prog", []string{"-interface-filter", "eth\\d+"}) + if err != nil { + t.Errorf("err got %v, want nil", err) + } + + if !config.InterfaceRegex.Match([]byte("eth0")) { + t.Fail() + } + + if config.InterfaceRegex.Match([]byte("wifi0")) { + t.Fail() + } +} + +func TestParseArgsInterfaceFilterFail(t *testing.T) { + _, err := args.Parse("prog", []string{"-interface-filter", "eth("}) + if err == nil { + t.Fail() + } +} + +func TestParseArgsDefault(t *testing.T) { + config, err := args.Parse("prog", []string{}) + if err != nil { + t.Fail() + } + + if len(config.AgentConfig.Urls) != 1 { + t.Fail() + } +} diff --git a/pkg/backend/backend.go b/pkg/backend/backend.go new file mode 100644 index 00000000..59ae77f7 --- /dev/null +++ b/pkg/backend/backend.go @@ -0,0 +1,37 @@ +package backend + +import ( + "fmt" + "io" + "net/url" + + "riasc.eu/wice/pkg/crypto" +) + +var ( + Backends = map[BackendType]*BackendPlugin{} +) + +type Backend interface { + PublishOffer(kp crypto.PublicKeyPair, offer Offer) error + SubscribeOffer(kp crypto.PublicKeyPair) (chan Offer, error) + WithdrawOffer(kp crypto.PublicKeyPair) error + + io.Closer +} + +func NewBackend(uri *url.URL, options map[string]string) (Backend, error) { + typ := BackendType(uri.Scheme) + + p, ok := Backends[typ] + if !ok { + return nil, fmt.Errorf("unknown backend type: %s", typ) + } + + be, err := p.New(uri, options) + if err != nil { + return nil, fmt.Errorf("failed to create backend: %w", err) + } + + return be, nil +} diff --git a/pkg/backend/backend_test.go b/pkg/backend/backend_test.go new file mode 100644 index 00000000..387d342c --- /dev/null +++ b/pkg/backend/backend_test.go @@ -0,0 +1,26 @@ +package backend_test + +import ( + "net/url" + "testing" + + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/backend/http" + _ "riasc.eu/wice/pkg/backend/http" +) + +func TestNewBackend(t *testing.T) { + uri, err := url.Parse("http://example.com") + if err != nil { + t.Fail() + } + + b, err := backend.NewBackend(uri, map[string]string{}) + if err != nil { + t.Fail() + } + + if _, ok := b.(*http.Backend); !ok { + t.Fail() + } +} diff --git a/pkg/backend/base/backend.go b/pkg/backend/base/backend.go new file mode 100644 index 00000000..111c5e2c --- /dev/null +++ b/pkg/backend/base/backend.go @@ -0,0 +1,58 @@ +package base + +import ( + "net/url" + + log "github.com/sirupsen/logrus" + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/crypto" +) + +type Backend struct { + Offers map[crypto.PublicKeyPair]chan backend.Offer + + Logger log.FieldLogger + Type string +} + +func NewBackend(uri *url.URL, options map[string]string) Backend { + logFields := log.Fields{ + "logger": "backend", + "backend": uri.Scheme, + } + + b := Backend{ + Offers: make(map[crypto.PublicKeyPair]chan backend.Offer), + Logger: log.WithFields(logFields), + } + + return b +} + +func (b *Backend) Close() error { + return nil +} + +func (b *Backend) SubscribeOffers(kp crypto.PublicKeyPair) chan backend.Offer { + b.Logger.WithField("kp", kp).Info("Subscribe to offers from peer") + + ch, ok := b.Offers[kp] + if !ok { + ch = make(chan backend.Offer, 100) + b.Offers[kp] = ch + } + + return ch +} + +func (b *Backend) PublishOffer(kp crypto.PublicKeyPair, offer backend.Offer) error { + b.Logger.WithField("kp", kp).WithField("offer", offer).Debug("Published offer") + + return nil +} + +func (b *Backend) WithdrawOffer(kp crypto.PublicKeyPair) error { + b.Logger.WithField("kp", kp).Debug("Withdrawed offer") + + return nil +} diff --git a/pkg/backend/base/config.go b/pkg/backend/base/config.go new file mode 100644 index 00000000..397e3402 --- /dev/null +++ b/pkg/backend/base/config.go @@ -0,0 +1,13 @@ +package base + +import "net/url" + +type BackendConfig struct { + URI *url.URL +} + +func (c *BackendConfig) Parse(uri *url.URL, options map[string]string) error { + c.URI = uri + + return nil +} diff --git a/pkg/backend/http/backend.go b/pkg/backend/http/backend.go new file mode 100644 index 00000000..03594950 --- /dev/null +++ b/pkg/backend/http/backend.go @@ -0,0 +1,166 @@ +package http + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/backend/base" + "riasc.eu/wice/pkg/crypto" +) + +type Backend struct { + base.Backend + config BackendConfig + + client *http.Client +} + +func init() { + p := backend.BackendPlugin{ + New: NewBackend, + Description: "Simple HTTP/HTTPs REST API server", + } + + backend.Backends["http"] = &p + backend.Backends["https"] = &p +} + +func NewBackend(uri *url.URL, options map[string]string) (backend.Backend, error) { + b := &Backend{ + Backend: base.NewBackend(uri, options), + } + + b.config.Parse(uri, options) + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: b.config.InsecureSkipVerify, + }, + } + b.client = &http.Client{ + Transport: tr, + Timeout: b.config.Timeout, + } + + go b.pollOffers() + + return b, nil +} + +func (b *Backend) SubscribeOffer(kp crypto.PublicKeyPair) (chan backend.Offer, error) { + ch := b.Backend.SubscribeOffers(kp) + + // Get initial offer without waiting for poller + o, err := b.getOffer(kp) + if err != nil { + return nil, fmt.Errorf("failed to get offer: %w", err) + } + + if o.ID != 0 { + ch <- o + } + + return ch, nil +} + +// pollOffers periodically fetches offers from the HTTP API and feeds them into the subscribption channels +func (b *Backend) pollOffers() { + b.Logger.Info("Start polling for new offers") + + ticker := time.NewTicker(b.config.PollInterval) + for range ticker.C { + for kp, ch := range b.Offers { + o, err := b.getOffer(kp) + if err != nil { + b.Logger.WithError(err).Error("Failed to fetch offer") + continue + } + + if o.ID != 0 { + ch <- o + } + } + } +} + +// PublishOffer POSTs the Offer to the HTTP API +func (b *Backend) PublishOffer(kp crypto.PublicKeyPair, offer backend.Offer) error { + buf, err := json.Marshal(offer) + if err != nil { + return fmt.Errorf("failed to encode offer: %w", err) + } + + resp, err := b.client.Post(b.offerUrl(kp, false), "application/json", bytes.NewBuffer(buf)) + if err != nil { + return fmt.Errorf("failed HTTP request: %w", err) + } else if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed HTTP request: %s", resp.Status) + } + + return b.Backend.PublishOffer(kp, offer) +} + +func (b *Backend) getOffer(kp crypto.PublicKeyPair) (backend.Offer, error) { + + b.Logger.WithField("kp", kp).Trace("Fetching offer") + + resp, err := b.client.Get(b.offerUrl(kp, true)) + if err != nil { + return backend.Offer{}, fmt.Errorf("failed HTTP request: %w", err) + } else if resp.StatusCode != http.StatusOK { + if resp.StatusCode == http.StatusNotFound { + return backend.Offer{}, nil + } else { + return backend.Offer{}, fmt.Errorf("failed HTTP request: %s", resp.Status) + } + } + + var offer backend.Offer + dec := json.NewDecoder(resp.Body) + err = dec.Decode(&offer) + if err != nil { + return backend.Offer{}, err + } + + b.Logger.WithField("offer", offer).Debug("Fetched offer") + + return offer, nil +} + +func (b *Backend) WithdrawOffer(kp crypto.PublicKeyPair) error { + + req, err := http.NewRequest(http.MethodDelete, b.offerUrl(kp, false), nil) + if err != nil { + return err + } + resp, err := b.client.Do(req) + if err != nil { + return fmt.Errorf("failed HTTP request: %w", err) + } else if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed HTTP request: %s", resp.Status) + } + + return b.Backend.WithdrawOffer(kp) +} + +func (b *Backend) Close() error { + b.client.CloseIdleConnections() + + return nil // TODO +} + +func (b *Backend) offerUrl(kp crypto.PublicKeyPair, sub bool) string { + u := *b.config.URI + if sub { + u.Path += "/offers/" + url.PathEscape(kp.Theirs.String()) + "/" + url.PathEscape(kp.Ours.String()) + } else { + u.Path += "/offers/" + url.PathEscape(kp.Ours.String()) + "/" + url.PathEscape(kp.Theirs.String()) + } + return u.String() +} diff --git a/pkg/backend/http/config.go b/pkg/backend/http/config.go new file mode 100644 index 00000000..8209026a --- /dev/null +++ b/pkg/backend/http/config.go @@ -0,0 +1,53 @@ +package http + +import ( + "fmt" + "net/url" + "time" + + "riasc.eu/wice/pkg/backend/base" +) + +const ( + defaultPollInterval = 5 * time.Second + defaultTimeout = 10 * time.Second +) + +type BackendConfig struct { + base.BackendConfig + + PollInterval time.Duration + Timeout time.Duration + InsecureSkipVerify bool +} + +func (c *BackendConfig) Parse(uri *url.URL, options map[string]string) error { + err := c.BackendConfig.Parse(uri, options) + if err != nil { + return err + } + + if skip, ok := options["insecure_skip_verify"]; ok { + c.InsecureSkipVerify = skip == "true" + } + + if interval, ok := options["interval"]; ok { + c.PollInterval, err = time.ParseDuration(interval) + if err != nil { + return fmt.Errorf("invalid interval: %s", interval) + } + } else { + c.PollInterval = defaultPollInterval + } + + if timeout, ok := options["timeout"]; ok { + c.Timeout, err = time.ParseDuration(timeout) + if err != nil { + return fmt.Errorf("invalid timeout: %s", timeout) + } + } else { + c.Timeout = defaultTimeout + } + + return nil +} diff --git a/pkg/backend/k8s/backend.go b/pkg/backend/k8s/backend.go new file mode 100644 index 00000000..ff4cecf7 --- /dev/null +++ b/pkg/backend/k8s/backend.go @@ -0,0 +1,190 @@ +package k8s + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" + + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/backend/base" + "riasc.eu/wice/pkg/crypto" +) + +const ( + annotationPrefix string = "wice.riasc.eu" + defaultAnnotationOffers string = annotationPrefix + "/offers" + defaultAnnotationPublicKey string = annotationPrefix + "/public-key" +) + +type Backend struct { + base.Backend + config BackendConfig + + clientSet *kubernetes.Clientset + informer cache.SharedInformer + + term chan struct{} + updates chan NodeCallback +} + +func init() { + backend.Backends["k8s"] = &backend.BackendPlugin{ + New: NewBackend, + Description: "Exchange candidates via annotation in Kubernetes Node resource", + } +} + +func NewBackend(uri *url.URL, options map[string]string) (backend.Backend, error) { + b := Backend{ + Backend: base.NewBackend(uri, options), + term: make(chan struct{}), + updates: make(chan NodeCallback), + } + + err := b.config.Parse(uri, options) + if err != nil { + return nil, fmt.Errorf("failed to parse configuration: %w", err) + } + + kubeconfig := uri.Path + var config *rest.Config + if kubeconfig == "" { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + // if you want to change the loading rules (which files in which order), you can do so here + + configOverrides := &clientcmd.ConfigOverrides{} + // if you want to change override values or bind them to flags, there are methods to help you + + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + config, err = kubeConfig.ClientConfig() + if err != nil { + return nil, fmt.Errorf("failed to load config: %w", err) + } + } else if kubeconfig == "incluster" { + config, err = rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("failed to get incluster configuration: %w", err) + } + } else { + config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, fmt.Errorf("failed to get configuration from flags: %w", err) + } + } + + // Create the clientset + b.clientSet, err = kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to create clientset: %w", err) + } + + // Create the shared informer factory and use the client to connect to + // Kubernetes + factory := informers.NewSharedInformerFactoryWithOptions(b.clientSet, 0, + informers.WithTweakListOptions(func(options *metav1.ListOptions) { + // options.LabelSelector = b.config.AnnotationPublicKey + })) + + // Get the informer for the right resource, in this case a Pod + b.informer = factory.Core().V1().Nodes().Informer() + + b.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: b.onNodeAdd, + UpdateFunc: b.onNodeUpdate, + DeleteFunc: b.onNodeDelete, + }) + + go b.informer.Run(b.term) + b.Logger.Debug("Started watching node resources") + + go b.applyUpdates() + b.Logger.Debug("Started batched updates") + + return &b, nil +} + +func (b *Backend) SubscribeOffer(kp crypto.PublicKeyPair) (chan backend.Offer, error) { + ch := b.Backend.SubscribeOffers(kp) + + // Process the node annotation at least once before we rely on the informer + node, err := b.getNodeByPublicKey(kp.Theirs) + if err == nil { + b.processNode(node) + } + + return ch, nil +} + +func (b *Backend) PublishOffer(kp crypto.PublicKeyPair, offer backend.Offer) error { + b.updateNode(func(node *corev1.Node) error { + offerMapJson, ok := node.ObjectMeta.Annotations[b.config.AnnotationOffers] + + // Unmarshal + var om backend.OfferMap + if ok && offerMapJson != "" { + err := json.Unmarshal([]byte(offerMapJson), &om) + if err != nil { + return err + } + } else { + om = backend.OfferMap{} + } + + // Update + om[kp.Theirs] = offer + + // Marshal + offerMapJsonNew, err := json.Marshal(&om) + if err != nil { + return err + } + + node.ObjectMeta.Annotations[b.config.AnnotationOffers] = string(offerMapJsonNew) + node.ObjectMeta.Annotations[b.config.AnnotationPublicKey] = kp.Ours.String() + + return nil + }) + + return b.Backend.PublishOffer(kp, offer) +} + +func (b *Backend) WithdrawOffer(kp crypto.PublicKeyPair) error { + b.updateNode(func(node *corev1.Node) error { + delete(node.ObjectMeta.Annotations, b.config.AnnotationOffers) + + return nil + }) + + return b.Backend.WithdrawOffer(kp) +} + +func (b *Backend) Close() error { + close(b.term) + + return nil // TODO +} + +func (b *Backend) getNodeByPublicKey(pk crypto.Key) (*corev1.Node, error) { + coreV1 := b.clientSet.CoreV1() + nodes, err := coreV1.Nodes().List(context.TODO(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", b.config.AnnotationPublicKey, pk), + }) + if err != nil { + return nil, err + } + + if len(nodes.Items) != 1 { + return nil, fmt.Errorf("could not find node with public key: %s", pk) + } + + return &nodes.Items[0], nil +} diff --git a/pkg/backend/k8s/backend_test.go b/pkg/backend/k8s/backend_test.go new file mode 100644 index 00000000..c36559f9 --- /dev/null +++ b/pkg/backend/k8s/backend_test.go @@ -0,0 +1,47 @@ +package k8s_test + +import ( + "log" + "net/url" + "testing" + + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/backend/k8s" + "riasc.eu/wice/pkg/crypto" +) + +func TestBackend(t *testing.T) { + opts := map[string]string{ + "nodename": "red", + } + + uri, err := url.Parse("k8s://") + if err != nil { + t.Errorf("failed to parse backend URL: %w", err) + } + + b, err := k8s.NewBackend(uri, opts) + if err != nil { + t.Errorf("failed to create backend: %w", err) + } + + ourSecretKey, _ := crypto.GeneratePrivateKey() + theirSecretKey, _ := crypto.GeneratePrivateKey() + + kp := crypto.PublicKeyPair{ + Ours: ourSecretKey.PublicKey(), + Theirs: theirSecretKey.PublicKey(), + } + + o := backend.NewOffer() + + ch, err := b.SubscribeOffer(kp) + if err != nil { + t.Errorf("failed to subscribe to offer") + } + + b.PublishOffer(kp, o) + + n := <-ch + log.Print(n) +} diff --git a/pkg/backend/k8s/config.go b/pkg/backend/k8s/config.go new file mode 100644 index 00000000..e6736ab7 --- /dev/null +++ b/pkg/backend/k8s/config.go @@ -0,0 +1,42 @@ +package k8s + +import ( + "errors" + "net/url" + + "riasc.eu/wice/pkg/backend/base" +) + +type BackendConfig struct { + base.BackendConfig + + NodeName string + AnnotationOffers string + AnnotationPublicKey string +} + +func (c *BackendConfig) Parse(uri *url.URL, options map[string]string) error { + var ok bool + + err := c.BackendConfig.Parse(uri, options) + if err != nil { + return err + } + + c.NodeName, ok = options["nodename"] + if !ok { + return errors.New("missing backend option: nodename") + } + + c.AnnotationOffers, ok = options["annotation-offers"] + if !ok { + c.AnnotationOffers = defaultAnnotationOffers + } + + c.AnnotationPublicKey, ok = options["annotation-public-key"] + if !ok { + c.AnnotationPublicKey = defaultAnnotationPublicKey + } + + return nil +} diff --git a/pkg/backend/k8s/handlers.go b/pkg/backend/k8s/handlers.go new file mode 100644 index 00000000..8654f77e --- /dev/null +++ b/pkg/backend/k8s/handlers.go @@ -0,0 +1,81 @@ +package k8s + +import ( + "encoding/json" + + corev1 "k8s.io/api/core/v1" + + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/crypto" +) + +func (b *Backend) onNodeAdd(obj interface{}) { + node := obj.(*corev1.Node) + + b.Logger.WithField("name", node.ObjectMeta.Name).Debug("Node added") + b.processNode(node) +} + +func (b *Backend) onNodeUpdate(_ interface{}, new interface{}) { + newNode := new.(*corev1.Node) + + b.Logger.WithField("name", newNode.ObjectMeta.Name).Debug("Node updated") + b.processNode(newNode) +} + +func (b *Backend) onNodeDelete(obj interface{}) { + node := obj.(*corev1.Node) + + b.Logger.WithField("name", node.ObjectMeta.Name).Debug("Node deleted") + b.processNode(node) +} + +func (b *Backend) processNode(node *corev1.Node) { + // Ignore ourself + if node.ObjectMeta.Name == b.config.NodeName { + b.Logger.WithField("node", node.ObjectMeta.Name).Trace("Ignoring ourself") + return + } + + // Check if required annotations are present + offersJson, ok := node.ObjectMeta.Annotations[b.config.AnnotationOffers] + if !ok { + b.Logger.WithField("node", node.ObjectMeta.Name).Trace("Missing candidate annotation") + return + } + + keyString, ok := node.ObjectMeta.Annotations[b.config.AnnotationPublicKey] + if !ok { + b.Logger.WithField("node", node.ObjectMeta.Name).Trace("Missing public key annotation") + return + } + + var err error + var theirPK crypto.Key + theirPK, err = crypto.ParseKey(keyString) + if err != nil { + b.Logger.WithError(err).Warn("Failed to parse public key") + } + + var om backend.OfferMap + err = json.Unmarshal([]byte(offersJson), &om) + if err != nil { + b.Logger.WithError(err).Warn("Failed to parse candidate annotation") + return + } + + for ourPK, o := range om { + kp := crypto.PublicKeyPair{ + Ours: ourPK, + Theirs: theirPK, + } + + ch, ok := b.Offers[kp] + if !ok { + b.Logger.WithField("kp", kp).Warn("Found candidates for unknown peer") + continue + } + + ch <- o + } +} diff --git a/pkg/backend/k8s/update.go b/pkg/backend/k8s/update.go new file mode 100644 index 00000000..7a8126d5 --- /dev/null +++ b/pkg/backend/k8s/update.go @@ -0,0 +1,62 @@ +package k8s + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/client-go/util/retry" +) + +type NodeCallback func(*corev1.Node) error +type AnnotationCallback func(string) (string, error) +type AnnotationCallbackMap map[string]AnnotationCallback + +func (b *Backend) applyUpdates() { + var cbs []NodeCallback + var timer time.Timer + + for { + select { + case cb := <-b.updates: + cbs = append(cbs, cb) + timer = *time.NewTimer(50 * time.Millisecond) + + case <-timer.C: + if len(cbs) > 0 { + b.Logger.Debugf("Applying %d batched updates", len(cbs)) + + nodes := b.clientSet.CoreV1().Nodes() + + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + node, err := nodes.Get(context.TODO(), b.config.NodeName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get latest version of node %s: %w", b.config.NodeName, err) + } + + for _, cb := range cbs { + if err := cb(node); err != nil { + return err + } + } + + _, err = nodes.Update(context.TODO(), node, metav1.UpdateOptions{}) + + return err + }) + if err != nil { + b.Logger.WithError(err).Error("Failed to update node") + } + + cbs = nil + } + } + } +} + +func (b *Backend) updateNode(cb NodeCallback) { + b.updates <- cb +} diff --git a/pkg/backend/p2p/backend.go b/pkg/backend/p2p/backend.go new file mode 100644 index 00000000..cd74c53c --- /dev/null +++ b/pkg/backend/p2p/backend.go @@ -0,0 +1,163 @@ +package p2p + +import ( + "context" + "fmt" + "net/url" + "sync" + + "github.com/ipfs/go-cid" + libp2p "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + dht "github.com/libp2p/go-libp2p-kad-dht" + multiaddr "github.com/multiformats/go-multiaddr" + "github.com/multiformats/go-multihash" + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/backend/base" + "riasc.eu/wice/pkg/crypto" + + log "github.com/sirupsen/logrus" +) + +const ( + ProtocolID = "/wice/rpc/0.1.0" + + // See: https://github.com/multiformats/multicodec/blob/master/table.csv#L85 + CodeX25519PublicKey = 0xec +) + +func init() { + backend.Backends["p2p"] = &backend.BackendPlugin{ + New: NewBackend, + Description: "LibP2P Kademlia DHT", + } +} + +type Backend struct { + base.Backend + config BackendConfig + + host host.Host + peers PeerList + + context context.Context + dht *dht.IpfsDHT +} + +func NewBackend(uri *url.URL, options map[string]string) (backend.Backend, error) { + var err error + b := Backend{ + Backend: base.NewBackend(uri, options), + } + + b.config.Parse(uri, options) + + b.context = context.Background() + + b.host, err = libp2p.New(b.context, libp2p.ListenAddrs([]multiaddr.Multiaddr(b.config.ListenAddresses)...)) + if err != nil { + return nil, fmt.Errorf("failed to create host: %w", err) + } + b.Logger.WithField("id", b.host.ID()).WithField("addrs", b.host.Addrs()).Info("Host created") + + b.host.SetStreamHandler(ProtocolID, b.handleStream) + + b.dht, err = dht.New(b.context, b.host) + if err != nil { + return nil, fmt.Errorf("failed to create DHT: %w", err) + } + + b.Logger.Debug("Bootstrapping the DHT") + if err = b.dht.Bootstrap(b.context); err != nil { + return nil, fmt.Errorf("failed to bootstrap DHT: %w", err) + } + + // Let's connect to the bootstrap nodes first. They will tell us about the + // other nodes in the network. + var wg sync.WaitGroup + for _, peerAddr := range b.config.BootstrapPeers { + peerinfo, _ := peer.AddrInfoFromP2pAddr(peerAddr) + wg.Add(1) + go func() { + defer wg.Done() + if err := b.host.Connect(b.context, *peerinfo); err != nil { + log.Warning(err) + } else { + b.Logger.WithField("peer", *peerinfo).Info("Connection established with bootstrap node") + } + }() + } + wg.Wait() + + // // We use a rendezvous point "meet me here" to announce our location. + // // This is like telling your friends to meet you at the Eiffel Tower. + // b.Logger.Info("Announcing ourselves...") + // routingDiscovery := discovery.NewRoutingDiscovery(b.dht) + + // discovery.Advertise(b.context, routingDiscovery, b.config.RendezvousString) + // b.Logger.Debug("Successfully announced!") + + // // Now, look for others who have announced + // // This is like your friend telling you the location to meet you. + // b.Logger.Debug("Searching for other peers...") + // peerChan, err := routingDiscovery.FindPeers(b.context, b.config.RendezvousString) + // if err != nil { + // return nil, fmt.Errorf("failed to find peers: %w", err) + // } + + // go b.handlePeers(peerChan) + + return &b, nil +} + +func (b *Backend) SubscribeOffer(kp crypto.PublicKeyPair) (chan backend.Offer, error) { + ch := b.Backend.SubscribeOffers(kp) + + cid := cidForPublicKey(kp.Ours) + err := b.dht.Provide(b.context, cid, true) + if err != nil { + return nil, err + } + + return ch, nil +} + +func (b *Backend) PublishOffer(kp crypto.PublicKeyPair, offer backend.Offer) error { + cid := cidForPublicKey(kp.Theirs) + + peerChan := b.dht.FindProvidersAsync(b.context, cid, 0) + go func() { + for pai := range peerChan { + peer, err := NewPeer(b, pai.ID) + if err != nil { + b.Logger.WithError(err).Error("Failed to create peer") + return + } + + om := backend.OfferMap{ + kp.Ours: offer, + } + + var ret bool + err = peer.Client.Call("candidates.Publish", &om, &ret) + if err != nil { + b.Logger.WithError(err).Error("Failed RPC call") + } + + b.peers = append(b.peers, peer) + } + }() + + return b.PublishOffer(kp, offer) +} + +func (b *Backend) Close() error { + return nil // TODO +} + +func cidForPublicKey(pk crypto.Key) cid.Cid { + mh, _ := multihash.Encode(pk[:], multihash.IDENTITY) + + return cid.NewCidV1(CodeX25519PublicKey, mh) +} diff --git a/pkg/backend/p2p/config.go b/pkg/backend/p2p/config.go new file mode 100644 index 00000000..a5b2a964 --- /dev/null +++ b/pkg/backend/p2p/config.go @@ -0,0 +1,64 @@ +package p2p + +import ( + "fmt" + "net/url" + "strings" + + "riasc.eu/wice/pkg/backend/base" + + dht "github.com/libp2p/go-libp2p-kad-dht" + maddr "github.com/multiformats/go-multiaddr" +) + +type addressList []maddr.Multiaddr + +type BackendConfig struct { + base.BackendConfig + + // Load some options + ListenAddresses addressList + BootstrapPeers addressList + RendezvousString string +} + +func (al addressList) Set(option string) error { + as := strings.Split(option, ":") + for _, a := range as { + ma, err := maddr.NewMultiaddr(a) + if err != nil { + return err + } + + al = append(al, ma) + } + + return nil +} + +func (c *BackendConfig) Parse(uri *url.URL, options map[string]string) error { + if rStr, ok := options["rendevouz-string"]; ok { + c.RendezvousString = rStr + } else { + c.RendezvousString = uri.Host + } + + if laStr, ok := options["listen-addresses"]; ok { + err := c.ListenAddresses.Set(laStr) + if err != nil { + return fmt.Errorf("failed to parse listen-address option: %w", err) + } + } + + if bpStr, ok := options["bootstrap-peers"]; ok { + if err := c.BootstrapPeers.Set(bpStr); err != nil { + return fmt.Errorf("failed to parse listen-address option: %w", err) + } + } + + if len(c.BootstrapPeers) == 0 { + c.BootstrapPeers = dht.DefaultBootstrapPeers + } + + return nil +} diff --git a/pkg/backend/p2p/handlers.go b/pkg/backend/p2p/handlers.go new file mode 100644 index 00000000..cce87a7f --- /dev/null +++ b/pkg/backend/p2p/handlers.go @@ -0,0 +1,42 @@ +package p2p + +import ( + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" +) + +func (b *Backend) handleStream(stream network.Stream) { + var err error + + peerID := stream.Conn().RemotePeer() + peer := b.peers.GetByPeerId(peerID) + if peer == nil { + peer, err = NewPeer(b, peerID) + if err != nil { + b.Logger.WithError(err).Fatal("Failed to create peer") + return + } + } + + peer.HandleStream(stream) +} + +func (b *Backend) handlePeers(peerChan <-chan peer.AddrInfo) { + var err error + + for peer := range peerChan { + if peer.ID == b.host.ID() { + continue + } + + p := b.peers.GetByPeerId(peer.ID) + if p == nil { + p, err = NewPeer(b, peer.ID) + if err != nil { + b.Logger.WithError(err).Error("Failed to create peer") + } + + b.peers = append(b.peers, p) + } + } +} diff --git a/pkg/backend/p2p/peer.go b/pkg/backend/p2p/peer.go new file mode 100644 index 00000000..21c2bb03 --- /dev/null +++ b/pkg/backend/p2p/peer.go @@ -0,0 +1,85 @@ +package p2p + +import ( + "fmt" + "net/rpc" + + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + log "github.com/sirupsen/logrus" + "riasc.eu/wice/pkg/crypto" +) + +type Peer struct { + ID peer.ID + Stream network.Stream + + Backend *Backend + PublicKey crypto.Key + + Client *rpc.Client + + logger *log.Entry +} + +type PeerList []*Peer + +func (pl *PeerList) GetByPeerId(id peer.ID) *Peer { + for _, peer := range *pl { + if peer.ID == id { + return peer + } + } + + return nil +} + +func (pl *PeerList) GetByPublicKey(pk crypto.Key) *Peer { + for _, peer := range *pl { + if peer.PublicKey == pk { + return peer + } + } + + return nil +} + +func NewPeer(b *Backend, id peer.ID) (*Peer, error) { + var err error + + p := Peer{ + ID: id, + Backend: b, + logger: b.Logger.WithField("peer", id), + } + + p.logger.Debug("Connecting to peer") + + p.Stream, err = b.host.NewStream(b.context, p.ID, ProtocolID) + if err != nil { + return nil, fmt.Errorf("failed to connect to peer: %w", err) + } + + p.logger.Info("Connected to peer") + + p.Client = rpc.NewClient(p.Stream) + + return &p, nil +} + +func (p *Peer) Close() error { + if p.Stream != nil { + return p.Stream.Close() + } + + return nil +} + +func (p *Peer) HandleStream(stream network.Stream) { + server := rpc.NewServer() + + server.RegisterName("candidates", NewCandidateService(p)) + server.RegisterName("peers", NewPeerService(p)) + + go server.ServeConn(stream) +} diff --git a/pkg/backend/p2p/rpc.go b/pkg/backend/p2p/rpc.go new file mode 100644 index 00000000..ff4db99e --- /dev/null +++ b/pkg/backend/p2p/rpc.go @@ -0,0 +1,55 @@ +package p2p + +import ( + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/crypto" +) + +type CandidateService struct { + Peer *Peer +} + +func NewCandidateService(p *Peer) *CandidateService { + return &CandidateService{ + Peer: p, + } +} + +func (svc *CandidateService) Publish(om backend.OfferMap, result *bool) error { + + for pk, o := range om { + kp := crypto.PublicKeyPair{ + Ours: svc.Peer.PublicKey, + Theirs: pk, + } + + ch, ok := svc.Peer.Backend.Offers[kp] + if ok { + ch <- o + } + } + + return nil +} + +func (svc *CandidateService) Remove(pk crypto.Key, result *bool) error { + return nil +} + +type PeerService struct { + Peer *Peer +} + +func NewPeerService(p *Peer) *PeerService { + return &PeerService{ + Peer: p, + } +} + +func (svc *PeerService) Add(p backend.Peer, result *bool) error { + return nil +} + +func (svc *PeerService) Remove(p backend.Peer, result *bool) error { + return nil +} diff --git a/pkg/backend/types.go b/pkg/backend/types.go new file mode 100644 index 00000000..48b7a0f31 --- /dev/null +++ b/pkg/backend/types.go @@ -0,0 +1,203 @@ +package backend + +import ( + "encoding/json" + "fmt" + "net" + "net/url" + "time" + + "riasc.eu/wice/pkg/crypto" + + "github.com/pion/ice/v2" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +// Backend + +type BackendType string // URL schemes +type BackendFactory func(*url.URL, map[string]string) (Backend, error) +type BackendPlugin struct { + New BackendFactory + Description string +} + +// Candidates + +type jsonCandidate struct { + Type string `json:"type"` + Foundation string `json:"foundation"` + Component int `json:"component"` + NetworkType string `json:"network"` + Priority int `json:"priority"` + Address string `json:"address"` + Port int `json:"port"` + TCPType *string `json:"tcp_type,omitempty"` + RelAddr *string `json:"related_address,omitempty"` + RelPort *int `json:"related_port,omitempty"` +} + +type Candidate struct { + ice.Candidate +} + +func (c *Candidate) MarshalJSON() ([]byte, error) { + jc := &jsonCandidate{ + Type: c.Type().String(), + Foundation: c.Foundation(), + Component: int(c.Component()), + NetworkType: c.NetworkType().String(), + Priority: int(c.Priority()), + Address: c.Address(), + Port: c.Port(), + } + + if c.TCPType() != ice.TCPTypeUnspecified { + t := c.TCPType().String() + jc.TCPType = &t + } + + if r := c.RelatedAddress(); r != nil && r.Address != "" && r.Port != 0 { + jc.RelAddr = &r.Address + jc.RelPort = &r.Port + } + + return json.Marshal(jc) +} + +func (c *Candidate) UnmarshalJSON(data []byte) error { + var jc jsonCandidate + err := json.Unmarshal(data, &jc) + if err != nil { + return err + } + + relAddr := "" + relPort := 0 + if jc.RelAddr != nil && jc.RelPort != nil { + relAddr = *jc.RelAddr + relPort = int(*jc.RelPort) + } + + tcpType := ice.TCPTypeUnspecified + if jc.TCPType != nil { + tcpType = ice.NewTCPType(*jc.TCPType) + } + + var ic ice.Candidate + switch jc.Type { + case "host": + ic, err = ice.NewCandidateHost(&ice.CandidateHostConfig{ + CandidateID: "", + Network: jc.NetworkType, + Address: jc.Address, + Port: int(jc.Port), + Component: uint16(jc.Component), + Priority: uint32(jc.Priority), + Foundation: jc.Foundation, + TCPType: tcpType}) + case "srflx": + ic, err = ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{ + CandidateID: "", + Network: jc.NetworkType, + Address: jc.Address, + Port: int(jc.Port), + Component: uint16(jc.Component), + Priority: uint32(jc.Priority), + Foundation: jc.Foundation, + RelAddr: relAddr, + RelPort: relPort, + }) + case "prflx": + ic, err = ice.NewCandidatePeerReflexive(&ice.CandidatePeerReflexiveConfig{ + CandidateID: "", + Network: jc.NetworkType, + Address: jc.Address, + Port: int(jc.Port), + Component: uint16(jc.Component), + Priority: uint32(jc.Priority), + Foundation: jc.Foundation, + RelAddr: relAddr, + RelPort: relPort, + }) + + case "relay": + ic, err = ice.NewCandidateRelay(&ice.CandidateRelayConfig{ + CandidateID: "", + Network: jc.NetworkType, + Address: jc.Address, + Port: int(jc.Port), + Component: uint16(jc.Component), + Priority: uint32(jc.Priority), + Foundation: jc.Foundation, + RelAddr: relAddr, + RelPort: relPort, + OnClose: nil, + }) + + default: + err = fmt.Errorf("unknown candidate type: %s", jc.Type) + } + if err != nil { + return nil + } + + c.Candidate = ic + + return nil +} + +// Peers + +type jsonPeer struct { + PublicKey crypto.Key `json:"public_key"` + AllowedIPs []net.IPNet `json:"allowed_ips,omitempty"` +} + +type Peer struct { + wgtypes.Peer +} + +func (p *Peer) PublicKey() crypto.Key { + return crypto.Key(p.Peer.PublicKey) +} + +func (p *Peer) MarshalJSON() ([]byte, error) { + jp := jsonPeer{ + PublicKey: p.PublicKey(), + AllowedIPs: p.AllowedIPs, + } + + return json.Marshal(jp) +} + +func (p *Peer) UnmarshalJSON(data []byte) error { + var jp jsonPeer + + return json.Unmarshal(data, &jp) +} + +// Offers + +type OfferMap map[crypto.Key]Offer + +// SDP-like session description +// See: https://www.rfc-editor.org/rfc/rfc8866.html +type Offer struct { + ID int64 `json:"id"` + Version int64 `json:"version"` + Candidates []Candidate `json:"cands,omitempty"` + EndOfCandidates bool `json:"eoc,omitempty"` + CleartextSignature string `json:"signature"` + // Ufrag string `json:"ufrag,omitempty"` + // Pwd string `json:"pwd,omitempty"` +} + +func NewOffer() Offer { + return Offer{ + ID: time.Now().UnixNano(), + Version: 0, + Candidates: []Candidate{}, + EndOfCandidates: false, + } +} diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go new file mode 100644 index 00000000..aa6c84f3 --- /dev/null +++ b/pkg/crypto/crypto.go @@ -0,0 +1,24 @@ +package crypto + +import ( + "math/big" + + "golang.org/x/crypto/curve25519" +) + +// endecrypt encrypts a 32-byte slice given the their public & our private private curve25519 keys via simple XOR with the shared secret +func Curve25519Crypt(privKey, pubKey Key, payloadBuf []byte) ([]byte, error) { + + // Perform static-static ECDH + keyBuf, err := curve25519.X25519(privKey[:], pubKey[:]) + if err != nil { + return nil, err + } + + var key, payload, enc big.Int + key.SetBytes(keyBuf) + payload.SetBytes(payloadBuf) + enc.Xor(&key, &payload) + + return enc.Bytes(), nil +} diff --git a/pkg/crypto/crypto_test.go b/pkg/crypto/crypto_test.go new file mode 100644 index 00000000..95b9d0c1 --- /dev/null +++ b/pkg/crypto/crypto_test.go @@ -0,0 +1,44 @@ +package crypto_test + +import ( + "bytes" + "testing" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "riasc.eu/wice/internal/util" + "riasc.eu/wice/pkg/crypto" +) + +func TestCurve25519Crypt(t *testing.T) { + keyA, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + keyB, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + pubA := keyA.PublicKey() + pubB := keyB.PublicKey() + + payload, err := util.GenerateRandomBytes(32) + if err != nil { + t.Fail() + } + + encPayload, err := crypto.Curve25519Crypt(keyA[:], pubB[:], payload) + if err != nil { + t.Fail() + } + + decPayload, err := crypto.Curve25519Crypt(keyB[:], pubA[:], encPayload) + if err != nil { + t.Fail() + } + + if !bytes.Equal(decPayload, payload) { + t.Fail() + } +} diff --git a/pkg/crypto/jws.go b/pkg/crypto/jws.go new file mode 100644 index 00000000..d6920cb7 --- /dev/null +++ b/pkg/crypto/jws.go @@ -0,0 +1,131 @@ +package crypto + +import ( + "crypto/rand" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/ucarion/jcs" +) + +type JWK struct { + KeyType string `json:"kty"` + Curve string `json:"crv"` + X string `json:"x"` +} + +type JWS struct { + Algorithm string `json:"alg"` + Key JWK `json:"jwk"` +} + +func jsonCanonicalize(obj interface{}) (string, error) { + var objIntf interface{} + + objJson, err := json.Marshal(obj) + if err != nil { + return "", err + } + + err = json.Unmarshal(objJson, &objIntf) + if err != nil { + return "", err + } + + msg, err := jcs.Format(objIntf) + if err != nil { + return "", err + } + + return msg, nil +} + +func JWSCTSign(obj interface{}, sk Key) (string, error) { + var pk Key + + hdr := JWS{ + Algorithm: "XEdDSA-25519", + Key: JWK{ + KeyType: "OKP", + Curve: "X25519", + X: pk.String(), + }, + } + + msg, err := jsonCanonicalize(obj) + if err != nil { + return "", err + } + + rnd := make([]byte, 32) + + _, err = rand.Reader.Read(rnd[:]) + if err != nil { + panic(err) + } + + sig := sk.Sign([]byte(msg), rnd) + + hdrBytes, err := json.Marshal(&hdr) + if err != nil { + return "", err + } + + hdrBase64 := base64.URLEncoding.EncodeToString(hdrBytes) + sigBase64 := base64.URLEncoding.EncodeToString(sig[:]) + plBase64 := "" // payload is always empty for JWS-CT + + return fmt.Sprintf("%s.%s.%s", hdrBase64, plBase64, sigBase64), nil +} + +func JWSCTVerify(obj interface{}, jwsStr string, pk Key) (bool, error) { + jwsStrParts := strings.Split(jwsStr, ".") + if len(jwsStrParts) != 3 { + return false, errors.New("invalid JWS format") + } + + hdrBase64 := jwsStrParts[0] + plBase64 := jwsStrParts[1] + sigBase64 := jwsStrParts[2] + + if plBase64 != "" { + return false, errors.New("payload field in JWS is not empty") + } + + hdrBytes, err := base64.URLEncoding.DecodeString(hdrBase64) + if err != nil { + return false, err + } + + sig, err := base64.URLEncoding.DecodeString(sigBase64) + if err != nil { + return false, err + } + + var hdr JWS + err = json.Unmarshal(hdrBytes, &hdr) + if err != nil { + return false, err + } + + if hdr.Key.KeyType != "OKP" { + return false, fmt.Errorf("Unsupported key type: %s", hdr.Key.KeyType) + } + + if hdr.Key.Curve != "X25519" { + return false, fmt.Errorf("Unsupported curve type: %s", hdr.Key.Curve) + } + + var ssig Signature + copy(ssig[:], sig) + + msg, err := jsonCanonicalize(obj) + if err != nil { + return false, err + } + + return pk.Verify([]byte(msg), ssig), nil +} diff --git a/pkg/crypto/jws_test.go b/pkg/crypto/jws_test.go new file mode 100644 index 00000000..7be45cb0 --- /dev/null +++ b/pkg/crypto/jws_test.go @@ -0,0 +1,68 @@ +package crypto_test + +import ( + "fmt" + "testing" + + "riasc.eu/wice/pkg/crypto" +) + +type Person struct { + Name string + Age int + Children []Person + Signature string +} + +func TestJWSCT(t *testing.T) { + einstein := Person{ + Name: "Albert Einstein", + Age: 66, + Children: []Person{ + { + Name: "Yoda", + Age: 9999, + }, + }, + } + + sk, err := crypto.ParseKey("GMHOtIxfUrGmncORjYK/slCSK/8V2TF9MjzzoPDTkEc=") + if err != nil { + panic(err) + } + + pk, err := crypto.ParseKey("Hxm0/KTFRGFirpOoTWO2iMde/gJX+oVswUXEzVN5En8=") + if err != nil { + panic(err) + } + + einstein.Signature, err = crypto.JWSCTSign(&einstein, sk) + if err != nil { + t.Errorf("Failed to sign: %w", err) + } + + sig := einstein.Signature + einstein.Signature = "" + + fmt.Printf("Signature: %s\n", sig) + + match, err := crypto.JWSCTVerify(&einstein, sig, pk) + if err != nil { + t.Errorf("Failed to verify: %w", err) + } + + if !match { + t.Errorf("Signature mismatch") + } + + einstein.Age = 67 + + match, err = crypto.JWSCTVerify(&einstein, sig, pk) + if err != nil { + t.Errorf("Failed to verify: %w", err) + } + + if match { + t.Errorf("Signature false positive") + } +} diff --git a/pkg/crypto/types.go b/pkg/crypto/types.go new file mode 100644 index 00000000..5fc12429 --- /dev/null +++ b/pkg/crypto/types.go @@ -0,0 +1,85 @@ +package crypto + +import ( + "crypto/hmac" + "encoding/base64" + + "github.com/pion/dtls/v2/pkg/crypto/hash" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +// Keys + +const ( + KeyLength = 32 + SignatureLength = 64 +) + +type Key [KeyLength]byte +type Signature [SignatureLength]byte + +type KeyPair struct { + Private Key + Public Key +} +type PublicKeyPair struct { + Ours Key + Theirs Key +} + +func GeneratePrivateKey() (Key, error) { + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return Key{}, err + } + + return Key(key), nil +} + +func ParseKey(str string) (Key, error) { + k, err := base64.StdEncoding.DecodeString(str) + if err != nil { + return Key{}, err + } + + var key Key + copy(key[:], k[:KeyLength]) + + return key, nil +} + +func (k Key) String() string { + return base64.StdEncoding.EncodeToString(k[:]) +} + +func (k Key) MarshalText() ([]byte, error) { + return []byte(k.String()), nil +} + +func (k *Key) UnmarshalText(text []byte) error { + var err error + *k, err = ParseKey(string(text)) + return err +} + +func (k Key) PublicKey() Key { + key := wgtypes.Key(k) + + return Key(key.PublicKey()) +} + +// Checks if the key is not zero +func (k Key) IsSet() bool { + return k != Key{} +} + +func (kp PublicKeyPair) ID(key []byte) string { + ctx := hmac.New(hash.SHA512.CryptoHash().HashFunc().New, key) + + ctx.Write(kp.Ours[:]) + ctx.Write(kp.Theirs[:]) + + mac := ctx.Sum(nil) + + return base64.URLEncoding.EncodeToString(mac) +} diff --git a/pkg/crypto/types_test.go b/pkg/crypto/types_test.go new file mode 100644 index 00000000..29f9f71b --- /dev/null +++ b/pkg/crypto/types_test.go @@ -0,0 +1,111 @@ +package crypto_test + +import ( + "encoding/json" + "testing" + + "riasc.eu/wice/pkg/crypto" +) + +func TestKeyString(t *testing.T) { + key1, err := crypto.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + keyString := key1.String() + + key2, err := crypto.ParseKey(keyString) + if err != nil { + t.Fail() + } + + if key1 != key2 { + t.Fail() + } +} + +func TestGeneratePrivateKey(t *testing.T) { + key1, err := crypto.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + var zeroKey crypto.Key + if key1 == zeroKey { + t.Fail() + } + + key2, err := crypto.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + if key1 == key2 { + t.Fail() + } +} + +type testObj struct { + Key crypto.Key +} + +func TestMarshal(t *testing.T) { + key, err := crypto.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + var obj1, obj2 testObj + + obj1 = testObj{ + Key: key, + } + + objJson, err := json.Marshal(&obj1) + if err != nil { + t.Fail() + } + + err = json.Unmarshal(objJson, &obj2) + if err != nil { + t.Fail() + } + + if obj1 != obj2 { + t.Fail() + } +} + +func TestPublicKey(t *testing.T) { + sk, err := crypto.ParseKey("GMHOtIxfUrGmncORjYK/slCSK/8V2TF9MjzzoPDTkEc=") + if err != nil { + t.Fail() + } + + pk, err := crypto.ParseKey("Hxm0/KTFRGFirpOoTWO2iMde/gJX+oVswUXEzVN5En8=") + if err != nil { + t.Fail() + } + + if sk.PublicKey() != pk { + t.Fail() + } +} + +func TestIsSet(t *testing.T) { + key, err := crypto.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + if !key.IsSet() { + t.Fail() + } + + key = crypto.Key{} + + if key.IsSet() { + t.Fail() + } +} diff --git a/pkg/crypto/xeddsa.go b/pkg/crypto/xeddsa.go new file mode 100644 index 00000000..24359a33 --- /dev/null +++ b/pkg/crypto/xeddsa.go @@ -0,0 +1,92 @@ +package crypto + +import ( + "crypto/sha512" + + "github.com/Scratch-net/vxeddsa/edwards25519" + "golang.org/x/crypto/ed25519" +) + +// sign signs the message with privateKey and returns a signature as a byte slice. +func (sk Key) Sign(msg []byte, rnd []byte) Signature { + + // Calculate Ed25519 public key from Curve25519 private key + var A edwards25519.ExtendedGroupElement + var pk [32]byte + + edwards25519.GeScalarMultBase(&A, (*[32]byte)(&sk)) + A.ToBytes(&pk) + + // Calculate r + diversifier := []byte{ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + + var r [64]byte + hash := sha512.New() + hash.Write(diversifier) + hash.Write(sk[:]) + hash.Write(msg) + hash.Write(rnd) + hash.Sum(r[:0]) + + // Calculate R + var rReduced [32]byte + edwards25519.ScReduce(&rReduced, &r) + var R edwards25519.ExtendedGroupElement + edwards25519.GeScalarMultBase(&R, &rReduced) + + var encr [32]byte + R.ToBytes(&encr) + + // Calculate S = r + SHA2-512(R || A_ed || msg) * a (mod L) + var hramDigest [64]byte + hash.Reset() + hash.Write(encr[:]) + hash.Write(pk[:]) + hash.Write(msg) + hash.Sum(hramDigest[:0]) + var hramDigestReduced [32]byte + edwards25519.ScReduce(&hramDigestReduced, &hramDigest) + + var s [32]byte + edwards25519.ScMulAdd(&s, &hramDigestReduced, (*[32]byte)(&sk), &rReduced) + + var sig Signature + copy(sig[:32], encr[:]) + copy(sig[32:], s[:]) + sig[63] |= pk[31] & 0x80 + + return sig +} + +// verify checks whether the message has a valid signature. +func (pk Key) Verify(msg []byte, sig Signature) bool { + pk[31] &= 0x7F + + /* Convert the Curve25519 public key into an Ed25519 public key. In + particular, convert Curve25519's "montgomery" x-coordinate into an + Ed25519 "edwards" y-coordinate: + ed_y = (mont_x - 1) / (mont_x + 1) + NOTE: mont_x=-1 is converted to ed_y=0 since fe_invert is mod-exp + Then move the sign bit into the pubkey from the signature. + */ + + var edY, one, montX, montXMinusOne, montXPlusOne edwards25519.FieldElement + edwards25519.FeFromBytes(&montX, (*[32]byte)(&pk)) + edwards25519.FeOne(&one) + edwards25519.FeSub(&montXMinusOne, &montX, &one) + edwards25519.FeAdd(&montXPlusOne, &montX, &one) + edwards25519.FeInvert(&montXPlusOne, &montXPlusOne) + edwards25519.FeMul(&edY, &montXMinusOne, &montXPlusOne) + + var A_ed [32]byte + edwards25519.FeToBytes(&A_ed, &edY) + + A_ed[31] |= sig[63] & 0x80 + sig[63] &= 0x7F + + return ed25519.Verify(A_ed[:], msg, sig[:]) +} diff --git a/pkg/crypto/xeddsa_test.go b/pkg/crypto/xeddsa_test.go new file mode 100644 index 00000000..637ab8eb --- /dev/null +++ b/pkg/crypto/xeddsa_test.go @@ -0,0 +1,49 @@ +package crypto_test + +import ( + "crypto/rand" + "fmt" + "testing" + + "riasc.eu/wice/pkg/crypto" +) + +func TestXEdDSA(t *testing.T) { + sk, err := crypto.GeneratePrivateKey() + if err != nil { + t.Fail() + } + + pk := sk.PublicKey() + + msg := make([]byte, 200) + rnd := make([]byte, 32) + + _, err = rand.Reader.Read(rnd[:]) + if err != nil { + t.Fail() + } + + _, err = rand.Reader.Read(msg[:]) + if err != nil { + t.Fail() + } + + signature := sk.Sign(msg, rnd) + + fmt.Printf("PrivateKey = %s\n", sk) + fmt.Printf("PublicKey = %s\n", pk) + fmt.Printf("Signature = %s\n", signature) + + res := pk.Verify(msg, signature) + if !res { + t.Error("Signature mismatch") + } + + msg[0] ^= 0xff + + res = pk.Verify(msg, signature) + if res { + t.Error("Signature false positive") + } +} diff --git a/pkg/intf/base.go b/pkg/intf/base.go new file mode 100644 index 00000000..a40cc223 --- /dev/null +++ b/pkg/intf/base.go @@ -0,0 +1,379 @@ +package intf + +import ( + "fmt" + "io" + "math/rand" + "os" + "os/exec" + "sort" + "strings" + "time" + + "github.com/pion/ice/v2" + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "riasc.eu/wice/internal/util" + "riasc.eu/wice/internal/wg" + "riasc.eu/wice/pkg/args" + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/crypto" +) + +const ( + PeerModifiedEndpoint = (1 << 0) + PeerModifiedKeepaliveInterval = (1 << 1) + PeerModifiedProtocolVersion = (1 << 2) + PeerModifiedAllowedIPs = (1 << 3) + PeerModifiedHandshakeTime = (1 << 4) +) + +type BaseInterface struct { + wgtypes.Device + + peers map[crypto.Key]Peer + + lastSync time.Time + + logger *log.Entry + + client *wgctrl.Client + mux ice.UDPMux + backend backend.Backend + args *args.Args +} + +func (i *BaseInterface) Name() string { + return i.Device.Name +} + +func (i *BaseInterface) PublicKey() crypto.Key { + return crypto.Key(i.Device.PublicKey) +} + +func (i *BaseInterface) PrivateKey() crypto.Key { + return crypto.Key(i.Device.PrivateKey) +} + +func (i *BaseInterface) Close() error { + i.logger.Info("Closing interface") + + for _, p := range i.peers { + err := p.Close() + if err != nil { + return err + } + } + + return nil +} + +func (i *BaseInterface) DumpConfig(wr io.Writer) { + fmt.Fprintf(wr, "[Interface] # %s\n", i.Name()) + if i.PublicKey().IsSet() { + fmt.Fprintf(wr, "PublicKey = %s\n", i.PublicKey) + } + if i.PrivateKey().IsSet() { + fmt.Fprintf(wr, "PrivateKey = %s\n", i.PrivateKey) + } + if i.ListenPort != 0 { + fmt.Fprintf(wr, "ListenPort = %d\n", i.ListenPort) + } + if i.FirewallMark != 0 { + fmt.Fprintf(wr, "FwMark = %#x\n", i.FirewallMark) + } + + for _, p := range i.Peers { + fmt.Fprintf(wr, "[Peer]\n") + if crypto.Key(p.PublicKey).IsSet() { + fmt.Fprintf(wr, "PublicKey = %s\n", p.PublicKey) + } + if crypto.Key(p.PresharedKey).IsSet() { + fmt.Fprintf(wr, "PresharedKey = %s\n", p.PresharedKey) + } + if !p.LastHandshakeTime.Equal(time.Time{}) { + fmt.Fprintf(wr, "LastHandshakeTime = %v\n", p.LastHandshakeTime) + } + if p.PersistentKeepaliveInterval.Seconds() != 0 { + fmt.Fprintf(wr, "PersistentKeepalive = %d # seconds\n", int(p.PersistentKeepaliveInterval.Seconds())) + } + if len(p.AllowedIPs) > 0 { + aIPs := []string{} + for _, aIP := range p.AllowedIPs { + aIPs = append(aIPs, aIP.String()) + } + fmt.Fprintf(wr, "AllowedIPs = %s\n", strings.Join(aIPs, ", ")) + } + if p.Endpoint != nil { + fmt.Fprintf(wr, "Endpoint = %s\n", p.Endpoint.String()) + } + } +} + +func (i *BaseInterface) syncPeer(oldPeer, newPeer *wgtypes.Peer) error { + modified := 0 + + // Compare peer properties + if util.CmpEndpoint(newPeer.Endpoint, oldPeer.Endpoint) != 0 { + modified |= PeerModifiedEndpoint + } + if newPeer.ProtocolVersion != oldPeer.ProtocolVersion { + modified |= PeerModifiedProtocolVersion + } + if newPeer.PersistentKeepaliveInterval != oldPeer.PersistentKeepaliveInterval { + modified |= PeerModifiedKeepaliveInterval + } + if newPeer.LastHandshakeTime != oldPeer.LastHandshakeTime { + modified |= PeerModifiedHandshakeTime + } + if len(newPeer.AllowedIPs) != len(oldPeer.AllowedIPs) { + modified |= PeerModifiedAllowedIPs + } else { + for i := 0; i < len(oldPeer.AllowedIPs); i++ { + if util.CmpNet(&oldPeer.AllowedIPs[i], &newPeer.AllowedIPs[i]) != 0 { + modified |= PeerModifiedAllowedIPs + break + } + } + } + + // Find changes in AllowedIP list + // sort.Slice(newPeer.AllowedIPs, lessNets(newPeer.AllowedIPs)) + // sort.Slice(oldPeer.AllowedIPs, lessNets(oldPeer.AllowedIPs)) + + // for i, j := 0, 0; i < len(oldPeer.AllowedIPs) && j < len(newPeer.AllowedIPs); { + // oldAllowedIP := &oldPeer.AllowedIPs[i] + // newAllowedIP := &newPeer.AllowedIPs[j] + + // cmp := cmpNet(oldAllowedIP, newAllowedIP) + // switch { + // case cmp < 0: // deleted + // d.onPeerAllowedIPDeleted(oldPeer) + + // case cmp > 0: // added + // d.onPeerAllowedIPAdded(newPeer) + + // default: // + // i++ + // j++ + // } + // } + + if modified != 0 { + i.logger.WithField("peer", oldPeer.PublicKey).WithField("modified", modified).Info("Peer modified") + i.onPeerModified(oldPeer, newPeer, modified) + } + + return nil +} + +func (i *BaseInterface) Sync(newDev *wgtypes.Device) error { + // Compare device properties + if newDev.Type != i.Type { + i.logger.WithField("old", i.Type).WithField("new", newDev.Type).Info("Type changed") + i.Device.Type = newDev.Type + } + if newDev.FirewallMark != i.FirewallMark { + i.logger.WithField("old", i.FirewallMark).WithField("new", newDev.FirewallMark).Info("FirewallMark changed") + i.Device.FirewallMark = newDev.FirewallMark + } + if newDev.PrivateKey != i.Device.PrivateKey { + i.logger.WithField("old", i.PrivateKey).WithField("new", newDev.PrivateKey).Info("PrivateKey changed") + i.Device.PrivateKey = newDev.PrivateKey + i.Device.PublicKey = newDev.PublicKey + } + if newDev.ListenPort != i.ListenPort { + i.logger.WithField("old", i.ListenPort).WithField("new", newDev.ListenPort).Info("ListenPort changed") + i.Device.ListenPort = newDev.ListenPort + } + + sort.Slice(newDev.Peers, wg.LessPeers(newDev.Peers)) + sort.Slice(i.Device.Peers, wg.LessPeers(i.Peers)) + + k, j := 0, 0 + for k < len(i.Peers) && j < len(newDev.Peers) { + oldPeer := &i.Peers[k] + newPeer := &newDev.Peers[j] + + cmp := wg.CmpPeers(oldPeer, newPeer) + switch { + case cmp < 0: // removed + i.logger.WithField("peer", oldPeer.PublicKey).Info("Peer removed") + i.onPeerRemoved(oldPeer) + k++ + + case cmp > 0: // added + i.logger.WithField("peer", oldPeer.PublicKey).Info("Peer added") + i.onPeerAdded(newPeer) + j++ + + default: // + i.syncPeer(oldPeer, newPeer) + k++ + j++ + } + } + + for k < len(i.Peers) { + oldPeer := &i.Peers[k] + i.logger.WithField("peer", oldPeer.PublicKey).Info("Peer removed") + i.onPeerRemoved(oldPeer) + k++ + } + + for j < len(newDev.Peers) { + newPeer := &newDev.Peers[j] + i.logger.WithField("peer", newPeer.PublicKey).Info("Peer added") + i.onPeerAdded(newPeer) + j++ + } + + i.Peers = newDev.Peers + i.lastSync = time.Now() + + return nil +} + +func (i *BaseInterface) SyncConfig(cfg string) error { + _, err := os.Stat(cfg) + if err != nil { + return err + } + + cmd := exec.Command("wg", "syncconf", i.Name(), cfg) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to sync configuration: %w\n%s", err, output) + } + + i.logger.WithField("config", cfg).Debug("Synced configuration") + + return nil +} + +func (i *BaseInterface) onPeerAdded(p *wgtypes.Peer) { + peer, err := NewPeer(p, i) + if err != nil { + i.logger.WithError(err).WithField("peer", peer.PublicKey).Fatal("Failed to setup peer") + } + + i.peers[peer.PublicKey()] = peer +} + +func (i *BaseInterface) onPeerRemoved(peer *wgtypes.Peer) { + p, ok := i.peers[crypto.Key(peer.PublicKey)] + if !ok { + i.logger.WithField("peer", peer.PublicKey).Warn("Failed to find matching peer") + } + + err := p.Close() + if err != nil { + i.logger.WithField("peer", peer.PublicKey).Warn("Failed to close peer") + } + + delete(i.peers, crypto.Key(peer.PublicKey)) +} + +func (i *BaseInterface) onPeerModified(old, new *wgtypes.Peer, modified int) { + p, ok := i.peers[crypto.Key(new.PublicKey)] + if ok { + p.OnModified(new, modified) + } else { + i.logger.Error("Failed to find modified peer") + } +} + +func (i *BaseInterface) AddPeer(pk wgtypes.Key) error { + cfg := wgtypes.Config{ + Peers: []wgtypes.PeerConfig{ + { + PublicKey: pk, + }, + }, + } + + return i.client.ConfigureDevice(i.Name(), cfg) +} + +func (i *BaseInterface) RemovePeer(pk wgtypes.Key) error { + cfg := wgtypes.Config{ + Peers: []wgtypes.PeerConfig{ + { + PublicKey: pk, + Remove: true, + }, + }, + } + + return i.client.ConfigureDevice(i.Name(), cfg) +} + +func NewInterface(dev *wgtypes.Device, client *wgctrl.Client, backend backend.Backend, args *args.Args) (BaseInterface, error) { + i := BaseInterface{ + Device: *dev, + client: client, + backend: backend, + args: args, + logger: log.WithFields(log.Fields{ + "intf": dev.Name, + "type": "kernel", + }), + peers: make(map[crypto.Key]Peer), + } + + i.logger.Info("Creating new interface") + + // Sync config + if i.args.ConfigSync { + cfg := fmt.Sprintf("%s/%s.conf", i.args.ConfigPath, i.Name()) + err := i.SyncConfig(cfg) + if err != nil { + return BaseInterface{}, fmt.Errorf("failed to sync interface configuration: %w", err) + } + } + + // Fixup device config + err := i.Fixup() + if err != nil { + return BaseInterface{}, fmt.Errorf("failed to fix interface configuration: %w", err) + } + + // We remove all peers here so that they get added by the following sync + i.Peers = nil + i.Sync(dev) + + return i, nil +} + +func (i *BaseInterface) Fixup() error { + var cfg wgtypes.Config + + if !i.PrivateKey().IsSet() { + if i.Type != wgtypes.Userspace { + i.logger.Warn("Device has no private key. Generating one..") + } + + key, _ := wgtypes.GeneratePrivateKey() + + cfg.PrivateKey = &key + } + + if i.ListenPort == 0 { + i.logger.Warn("Device has no listen port. Setting a random one..") + + // Ephemeral Port Range (RFC6056 Sect. 2.1) + portMin := (1 << 15) + (1 << 14) + portMax := (1 << 16) + port := portMin + rand.Intn(portMax-portMin) + + cfg.ListenPort = &port + } + + err := i.client.ConfigureDevice(i.Name(), cfg) + if err != nil { + return fmt.Errorf("failed to configure device: %w", err) + } + + return nil +} diff --git a/pkg/intf/credentials.go b/pkg/intf/credentials.go new file mode 100644 index 00000000..00f84fb0 --- /dev/null +++ b/pkg/intf/credentials.go @@ -0,0 +1,34 @@ +package intf + +import ( + "encoding/base64" + + "riasc.eu/wice/pkg/crypto" +) + +// LocalCredentials returns local ufrag, pwd of a peer +// ufrag is base64 encoded public key of the peer which wants to connect +// pwd is the base64 encoded and encrypted public key of our interface +// for encryption the public key of the peer is used +func (p *Peer) LocalCreds() (string, string, error) { + pl := p.Interface.PublicKey() + enc, err := crypto.Curve25519Crypt(p.Interface.PrivateKey(), p.PublicKey(), pl[:]) + if err != nil { + return "", "", err + } + + return base64.StdEncoding.EncodeToString(p.Peer.PublicKey[:]), + base64.StdEncoding.EncodeToString(enc), nil +} + +// RemoteCredentials returns remote ufrag, pwd of a peer +func (p *Peer) RemoteCredentials() (string, string, error) { + pl := p.PublicKey() + enc, err := crypto.Curve25519Crypt(p.Interface.PrivateKey(), p.PublicKey(), pl[:]) + if err != nil { + return "", "", err + } + + return p.Interface.PublicKey().String(), + base64.StdEncoding.EncodeToString(enc), nil +} diff --git a/pkg/intf/devices.go b/pkg/intf/devices.go new file mode 100644 index 00000000..7ff1be59 --- /dev/null +++ b/pkg/intf/devices.go @@ -0,0 +1,15 @@ +package intf + +import "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + +type Devices []*wgtypes.Device + +func (devs *Devices) GetByName(name string) *wgtypes.Device { + for _, dev := range *devs { + if dev.Name == name { + return dev + } + } + + return nil +} diff --git a/pkg/intf/interface.go b/pkg/intf/interface.go new file mode 100644 index 00000000..1aab96f8 --- /dev/null +++ b/pkg/intf/interface.go @@ -0,0 +1,20 @@ +package intf + +import ( + "io" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type Interface interface { + io.Closer + + DumpConfig(io.Writer) + + AddPeer(peer wgtypes.Key) error + RemovePeer(peer wgtypes.Key) error + + Sync(*wgtypes.Device) error + + Name() string +} diff --git a/pkg/intf/interfaces.go b/pkg/intf/interfaces.go new file mode 100644 index 00000000..03a67ade --- /dev/null +++ b/pkg/intf/interfaces.go @@ -0,0 +1,120 @@ +package intf + +import ( + "fmt" + "os" + + log "github.com/sirupsen/logrus" + "riasc.eu/wice/pkg/args" + be "riasc.eu/wice/pkg/backend" + + "golang.zx2c4.com/wireguard/wgctrl" +) + +type Interfaces []Interface + +func (interfaces *Interfaces) GetByName(name string) Interface { + for _, intf := range *interfaces { + if intf.Name() == name { + return intf + } + } + + return nil +} + +func (interfaces *Interfaces) CloseAll() { + for _, intf := range *interfaces { + intf.Close() + } +} + +func (interfaces *Interfaces) SyncAll(client *wgctrl.Client, backend be.Backend, args *args.Args) error { + devices, err := client.Devices() + if err != nil { + log.WithError(err).Fatal("Failed to list Wireguard interfaces") + } + + syncedInterfaces := Interfaces{} + keepInterfaces := Interfaces{} + + for _, device := range devices { + if !args.InterfaceRegex.Match([]byte(device.Name)) { + continue // Skip interfaces which dont match the filter + } + + // Find matching interface + intf := interfaces.GetByName(device.Name) + if intf == nil { // new interface + log.WithField("intf", device.Name).Info("Adding new interface") + + i, err := NewInterface(device, client, backend, args) + if err != nil { + log.WithField("intf", device.Name).WithError(err).Fatalf("Failed to create new interface") + } + + intf = &i + + *interfaces = append(*interfaces, &i) + } else { // existing interface + log.WithField("intf", intf.Name()).Trace("Sync existing interface") + err := intf.Sync(device) + if err != nil { + log.WithError(err).Fatal("Failed to sync interface %s", intf.Name()) + } + } + + syncedInterfaces = append(syncedInterfaces, intf) + } + + for _, intf := range *interfaces { + i := syncedInterfaces.GetByName(intf.Name()) + if i == nil { + log.WithField("intf", intf.Name()).Info("Removing vanished interface") + err := intf.Close() + if err != nil { + log.WithError(err).Fatal("Failed to close interface") + } + } else { + keepInterfaces = append(keepInterfaces, intf) + } + } + *interfaces = keepInterfaces + + return nil +} + +func (interfaces *Interfaces) CreateFromArgs(client *wgctrl.Client, backend be.Backend, args *args.Args) error { + var devs Devices + devs, err := client.Devices() + if err != nil { + return err + } + + for _, i := range args.Interfaces { + dev := devs.GetByName(i) + if dev != nil { + log.WithField("intf", i).Warn("Interface already exists. Skipping..") + continue + } + + var intf Interface + if args.User { + intf, err = CreateUserInterface(i, client, backend, args) + } else { + intf, err = CreateKernelInterface(i, client, backend, args) + } + if err != nil { + return fmt.Errorf("failed to create Wireguard device: %w", err) + } + + if log.GetLevel() >= log.DebugLevel { + log.Debug("Intialized interface:") + intf.DumpConfig(os.Stdout) + } + + *interfaces = append(*interfaces, intf) + } + + return nil +} diff --git a/pkg/intf/kernel.go b/pkg/intf/kernel.go new file mode 100644 index 00000000..291e9983 --- /dev/null +++ b/pkg/intf/kernel.go @@ -0,0 +1,109 @@ +package intf + +import ( + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + "golang.zx2c4.com/wireguard/wgctrl" + "riasc.eu/wice/pkg/args" + "riasc.eu/wice/pkg/backend" + nl "riasc.eu/wice/pkg/netlink" +) + +type KernelInterface struct { + BaseInterface + + created bool + + link netlink.Link +} + +func (i *KernelInterface) Close() error { + err := i.BaseInterface.Close() + if err != nil { + return err + } + + if i.created { + err := i.Delete() + if err != nil { + return err + } + } + + return nil +} + +func (i *KernelInterface) Delete() error { + i.logger.Debug("Deleting kernel device") + + l := &nl.Wireguard{ + LinkAttrs: netlink.NewLinkAttrs(), + } + l.LinkAttrs.Name = i.Name() + + err := netlink.LinkDel(l) + if err != nil { + return fmt.Errorf("failed to delete Wireguard device: %w", err) + } + + return nil +} + +func (i *KernelInterface) SetMTU(mtu int) error { + i.logger.Debug("Set link MTU") + return netlink.LinkSetMTU(i.link, mtu) +} + +func (i *KernelInterface) SetUp() error { + i.logger.Debug("Set link up") + return netlink.LinkSetUp(i.link) +} + +func (i *KernelInterface) SetDown(mtu int) error { + i.logger.Debug("Set link down") + return netlink.LinkSetDown(i.link) +} + +func CreateKernelInterface(name string, client *wgctrl.Client, backend backend.Backend, args *args.Args) (Interface, error) { + log.WithField("intf", name).Debug("Creating new kernel interface") + + l := &nl.Wireguard{ + LinkAttrs: netlink.NewLinkAttrs(), + } + l.LinkAttrs.Name = name + err := netlink.LinkAdd(l) + if err != nil { + return nil, fmt.Errorf("failed to create Wireguard interface: %w", err) + } + + link, err := netlink.LinkByName(name) + if err != nil { + return nil, fmt.Errorf("failed to get link details: %w", err) + } + + // Connect to UAPI + wgDev, err := client.Device(name) + if err != nil { + return nil, err + } + + baseDev, err := NewInterface(wgDev, client, backend, args) + if err != nil { + return nil, err + } + + i := &KernelInterface{ + BaseInterface: baseDev, + created: true, + link: link, + } + + err = i.SetUp() + if err != nil { + return nil, fmt.Errorf("failed to bring link %s up: %w", name, err) + } + + return i, nil +} diff --git a/pkg/intf/peer.go b/pkg/intf/peer.go new file mode 100644 index 00000000..b410c93c --- /dev/null +++ b/pkg/intf/peer.go @@ -0,0 +1,395 @@ +package intf + +import ( + "context" + "encoding/base64" + "fmt" + "math/big" + "net" + "os" + "runtime" + "strings" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/pion/ice/v2" + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "riasc.eu/wice/pkg/args" + "riasc.eu/wice/pkg/backend" + "riasc.eu/wice/pkg/crypto" + "riasc.eu/wice/pkg/proxy" +) + +type Peer struct { + wgtypes.Peer + + Interface *BaseInterface + + ICEAgent *ice.Agent + ICEConn *ice.Conn + + localOffer backend.Offer + lastRemoteOfferID int64 + remoteOffers chan backend.Offer + + selectedCandidatePairs chan *ice.CandidatePair + + LastHandshake time.Time + + logger *log.Entry + + client *wgctrl.Client + args *args.Args + backend backend.Backend +} + +func (p *Peer) Close() error { + err := p.backend.WithdrawOffer(p.PublicKeyPair()) + if err != nil { + return err + } + + p.ICEAgent.Close() + + p.logger.Info("Closing peer") + + return nil +} + +func (p *Peer) String() string { + return p.PublicKey().String() +} + +func (p *Peer) UpdateEndpoint(addr *net.UDPAddr) error { + + peerCfg := wgtypes.PeerConfig{ + PublicKey: p.Peer.PublicKey, + UpdateOnly: true, + ReplaceAllowedIPs: false, + Endpoint: addr, + } + + cfg := wgtypes.Config{ + Peers: []wgtypes.PeerConfig{peerCfg}, + } + + return p.client.ConfigureDevice(p.Interface.Name(), cfg) +} + +func (p *Peer) colorizeConnectionState(state ice.ConnectionState) string { + if runtime.GOOS != "windows" { + var color int + switch state { + case ice.ConnectionStateNew: + fallthrough + case ice.ConnectionStateCompleted: + fallthrough + case ice.ConnectionStateClosed: + color = 37 + case ice.ConnectionStateDisconnected: + fallthrough + case ice.ConnectionStateChecking: + color = 33 + case ice.ConnectionStateFailed: + color = 31 + case ice.ConnectionStateConnected: + color = 32 + } + return fmt.Sprintf("\x1b[%d;1m%s\x1b[0m", color, state) + } else { + return state.String() + } +} + +func (p *Peer) isControlling() bool { + var pkOur, pkTheir big.Int + pkOur.SetBytes(p.Interface.Device.PublicKey[:]) + pkTheir.SetBytes(p.Peer.PublicKey[:]) + + return pkOur.Cmp(&pkTheir) == -1 // the smaller PK is controlling +} + +func (p *Peer) OnModified(new *wgtypes.Peer, modified int) { + if modified&PeerModifiedHandshakeTime > 0 { + p.LastHandshake = new.LastHandshakeTime + p.logger.WithField("time", new.LastHandshakeTime).Debug("New handshake") + } +} + +func (p *Peer) onCandidate(c ice.Candidate) { + if c == nil { + p.logger.Info("Candidate gathering completed") + + p.localOffer.EndOfCandidates = true + } else { + p.logger.WithField("candidate", c).Info("Found new local candidate") + + p.localOffer.Candidates = append(p.localOffer.Candidates, backend.Candidate{ + Candidate: c, + }) + } + + p.localOffer.Version++ + + if err := p.backend.PublishOffer(p.PublicKeyPair(), p.localOffer); err != nil { + p.logger.WithError(err).Warn("Failed to publish offer") + os.Exit(-1) + } +} + +func (p *Peer) onConnectionStateChange(state ice.ConnectionState) { + + p.logger.WithField("state", strings.ToLower(state.String())).Infof("Connection state changed: %s", p.colorizeConnectionState(state)) + + if state == ice.ConnectionStateFailed { + go p.restartLocal() + } +} + +func (p *Peer) onSelectedCandidatePairChange(a, b ice.Candidate) { + cp, err := p.ICEAgent.GetSelectedCandidatePair() + if err != nil { + p.logger.Warn("Failed to get selected candidate pair") + } + + p.logger.WithField("pair", cp).Info("Selected new candidate pair") + + p.selectedCandidatePairs <- cp +} + +func (p *Peer) onOffer(o backend.Offer) { + if p.lastRemoteOfferID > 0 && p.lastRemoteOfferID != o.ID { // && o.Version == 0 { + p.restartRemote(o) + } + + for _, c := range o.Candidates { + err := p.ICEAgent.AddRemoteCandidate(c) + if err != nil { + p.logger.WithError(err).Fatal("Failed to add remote candidate") + } + p.logger.WithField("candidate", c).Debug("Add remote candidate") + } + + p.lastRemoteOfferID = o.ID +} + +func (p *Peer) restartLocal() { + p.logger.WithField("id", p.localOffer.ID).Infof("Restarting session triggered locally in %s", p.args.RestartInterval) + + time.Sleep(p.args.RestartInterval) + + p.localOffer = backend.NewOffer() + + p.backend.PublishOffer(p.PublicKeyPair(), p.localOffer) + + offer := <-p.remoteOffers // wait for remote answer + p.lastRemoteOfferID = offer.ID + + p.restart(offer) +} + +func (p *Peer) restartRemote(offer backend.Offer) { + p.logger.WithField("id", p.localOffer.ID).Info("Restarting session triggered locally") + + id := p.localOffer.ID + p.localOffer = backend.NewOffer() + p.localOffer.ID = id // we keep our offer ID when restart is triggered by remote + + p.restart(offer) +} + +// Performs an ICE restart +// +// This restart can either be triggered by a failed +// ICE connection state (Peer.onConnectionState()) +// or by a remote offer which indicates a restart (Peer.onOffer()) +func (p *Peer) restart(offer backend.Offer) { + var err error + var localUfrag, localPwd, remoteUfrag, remotePwd string + + if remoteUfrag, remotePwd, err = p.RemoteCredentials(); err != nil { + p.logger.WithError(err).Error("Failed to get remote credentials") + return + } + if localUfrag, localPwd, err = p.LocalCreds(); err != nil { + p.logger.WithError(err).Error("Failed to get remote credentials") + return + } + + if err := p.ICEAgent.Restart(localUfrag, localPwd); err != nil { + p.logger.WithError(err).Error("Failed to restart ICE session") + return + } + + for _, cand := range offer.Candidates { + err = p.ICEAgent.AddRemoteCandidate(cand.Candidate) + if err != nil { + p.logger.WithError(err).Error("Failed to add remote candidate") + } + } + + if err := p.ICEAgent.GatherCandidates(); err != nil { + p.logger.WithError(err).Error("Failed to gather candidates") + return + } + + if err := p.ICEAgent.SetRemoteCredentials(remoteUfrag, remotePwd); err != nil { + p.logger.WithError(err).Error("Failed to set remote creds") + return + } +} + +func (p *Peer) start(remoteUfrag, remotePwd string) { + var err error + + p.logger.WithField("id", p.localOffer.ID).Info("Starting new session") + + p.remoteOffers, err = p.backend.SubscribeOffer(p.PublicKeyPair()) + if err != nil { + p.logger.WithError(err).Fatal("Failed to subscribe to offers") + } + + // Wait for first offer from remote agent before creating ICE connection + o := <-p.remoteOffers + p.onOffer(o) + + // Start the ICE Agent. One side must be controlled, and the other must be controlling + if p.isControlling() { + p.ICEConn, err = p.ICEAgent.Dial(context.TODO(), remoteUfrag, remotePwd) + } else { + p.ICEConn, err = p.ICEAgent.Accept(context.TODO(), remoteUfrag, remotePwd) + } + if err != nil { + p.logger.WithError(err).Fatal("Failed to establish ICE connection") + } + + // Wait until we are ready + var currentProxy proxy.Proxy = nil + for { + select { + + // New remote candidate + case offer := <-p.remoteOffers: + p.onOffer(offer) + + // New selected candidate pair + case cp := <-p.selectedCandidatePairs: + pt := p.args.ProxyType + + // p.logger.Infof("Conntype: %+v", reflect.ValueOf(cp.Local).Elem().Type()) + + isTCPRelayCandidate := cp.Local.Type() == ice.CandidateTypeRelay + if isTCPRelayCandidate { + pt = proxy.ProxyTypeUser + } + + if currentProxy != nil && proxy.Type(currentProxy) == pt { + // Update endpoint of existing proxy + addr := p.ICEConn.RemoteAddr().(*net.UDPAddr) + currentProxy.UpdateEndpoint(addr) + } else { + // Stop old proxy + if currentProxy != nil { + currentProxy.Close() + } + + ident := base64.StdEncoding.EncodeToString(p.Peer.PublicKey[:16]) + + // Replace proxy + if currentProxy, err = proxy.NewProxy(pt, ident, p.Interface.ListenPort, p.UpdateEndpoint, p.ICEConn); err != nil { + p.logger.WithError(err).Fatal("Failed to setup proxy") + } + } + + } + } +} + +func (p *Peer) PublicKey() crypto.Key { + return crypto.Key(p.Peer.PublicKey) +} + +func (p *Peer) PublicKeyPair() crypto.PublicKeyPair { + return crypto.PublicKeyPair{ + Ours: p.Interface.PublicKey(), + Theirs: p.PublicKey(), + } +} + +func NewPeer(wgp *wgtypes.Peer, i *BaseInterface) (Peer, error) { + var err error + + p := Peer{ + Interface: i, + Peer: *wgp, + client: i.client, + backend: i.backend, + lastRemoteOfferID: -1, + localOffer: backend.NewOffer(), + args: i.args, + selectedCandidatePairs: make(chan *ice.CandidatePair), + logger: log.WithFields(log.Fields{ + "intf": i.Name, + "peer": wgp.PublicKey.String(), + }), + } + + agentConfig := p.args.AgentConfig + + agentConfig.InterfaceFilter = func(name string) bool { + _, err := p.client.Device(name) + return p.args.IceInterfaceRegex.Match([]byte(name)) && err != nil + } + + var localUfrag, localPwd, remoteUfrag, remotePwd string + if localUfrag, localPwd, err = p.LocalCreds(); err != nil { + return Peer{}, fmt.Errorf("failed to get local credentials: %w", err) + } + if remoteUfrag, remotePwd, err = p.RemoteCredentials(); err != nil { + return Peer{}, fmt.Errorf("failed to get remote credentials: %w", err) + } + + p.logger.WithFields(log.Fields{ + "ufrag_local": localUfrag, + "pwd_local": localPwd, + "ufrag_remote": remoteUfrag, + "pwd_remote": remotePwd, + }).Debug("Peer credentials") + + agentConfig.LocalUfrag = localUfrag + agentConfig.LocalPwd = localPwd + + if p.args.ProxyType == proxy.ProxyTypeEBPF { + proxy.SetupEBPFMux(&agentConfig, p.Interface.ListenPort) + } + + if p.ICEAgent, err = ice.NewAgent(&agentConfig); err != nil { + return Peer{}, fmt.Errorf("failed to create ICE agent: %w", err) + } + + // When we have gathered a new ICE Candidate send it to the remote peer + if err := p.ICEAgent.OnCandidate(p.onCandidate); err != nil { + return Peer{}, fmt.Errorf("failed to setup on candidate handler: %w", err) + } + + // When selected candidate pair changes + if err := p.ICEAgent.OnSelectedCandidatePairChange(p.onSelectedCandidatePairChange); err != nil { + return Peer{}, fmt.Errorf("failed to setup on selected candidate pair handler: %w", err) + } + + // When ICE Connection state has change print to stdout + if err := p.ICEAgent.OnConnectionStateChange(p.onConnectionStateChange); err != nil { + return Peer{}, fmt.Errorf("failed to setup on connection state handler: %w", err) + } + + p.logger.Info("Gathering local candidates") + if err := p.ICEAgent.GatherCandidates(); err != nil { + return Peer{}, fmt.Errorf("failed to gather candidates: %w", err) + } + + go p.start(remoteUfrag, remotePwd) + + return p, nil +} diff --git a/pkg/intf/user.go b/pkg/intf/user.go new file mode 100644 index 00000000..b9799cda --- /dev/null +++ b/pkg/intf/user.go @@ -0,0 +1,134 @@ +// +build !windows + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package intf + +import ( + "fmt" + "net" + + log "github.com/sirupsen/logrus" + "riasc.eu/wice/pkg/args" + "riasc.eu/wice/pkg/backend" + + "golang.zx2c4.com/wireguard/conn" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/ipc" + "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/wgctrl" +) + +type UserDevice struct { + BaseInterface + + tun tun.Device + log *device.Logger + userDevice *device.Device + userAPI net.Listener +} + +func newLogger(log *log.Entry) *device.Logger { + logger := log.WithField("logger", "wireguard") + + return &device.Logger{ + Verbosef: logger.Debugf, + Errorf: logger.Errorf, + } +} + +func (i *UserDevice) Close() error { + err := i.userAPI.Close() + if err != nil { + return err + } + + i.userDevice.Close() + + err = i.BaseInterface.Close() + if err != nil { + return err + } + + return nil +} + +func (i *UserDevice) handleUserApi() { + for { + conn, err := i.userAPI.Accept() + if err != nil { + i.logger.WithError(err).Warn("Failed to accept UAPI connection") + return + } + + go i.userDevice.IpcHandle(conn) + } +} + +func CreateUserInterface(name string, client *wgctrl.Client, backend backend.Backend, args *args.Args) (Interface, error) { + var err error + logger := log.WithFields(log.Fields{ + "intf": name, + "type": "user", + }) + + dev := &UserDevice{ + log: newLogger(logger), + } + + logger.Debug("Starting in-process wireguard-go interface") + + // Create TUN device + dev.tun, err = tun.CreateTUN(name, device.DefaultMTU) + if err != nil { + return nil, fmt.Errorf("failed to create TUN device: %w", err) + } + + // Fix interface name + realName, err := dev.tun.Name() + if err == nil && realName != name { + name = realName + } + + // Open UAPI file (or use supplied fd) + fileUAPI, err := ipc.UAPIOpen(name) + if err != nil { + return nil, fmt.Errorf("UAPI listen error: %w", err) + } + + var bind conn.Bind = nil + if bind == nil { + bind = conn.NewDefaultBind() + } + + // Create new device + dev.userDevice = device.NewDevice(dev.tun, bind, dev.log) + + logger.Debug("Device started") + + // Open UApi socket + dev.userAPI, err = ipc.UAPIListen(name, fileUAPI) + if err != nil { + return nil, fmt.Errorf("failed to listen on UAPI socket: %w", err) + } + + // Handle UApi requests + go dev.handleUserApi() + logger.Debug("UAPI listener started for interface") + + // Connect to UAPI + wgDev, err := client.Device(name) + if err != nil { + return nil, err + } + + dev.BaseInterface, err = NewInterface(wgDev, client, backend, args) + if err != nil { + return nil, err + } + + return dev, nil +} diff --git a/pkg/intf/watch.go b/pkg/intf/watch.go new file mode 100644 index 00000000..2f98a3ce --- /dev/null +++ b/pkg/intf/watch.go @@ -0,0 +1,132 @@ +package intf + +import ( + "fmt" + "os" + "path" + "strings" + + log "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + + "github.com/fsnotify/fsnotify" + "github.com/vishvananda/netlink" + + nl "riasc.eu/wice/pkg/netlink" +) + +const ( + wireguardSockDir = "/var/run/wireguard/" + + InterfaceAdded InterfaceEventOp = iota + InterfaceDeleted +) + +type InterfaceEventOp int +type InterfaceEvent struct { + Op InterfaceEventOp + Name string +} + +func (ls InterfaceEventOp) String() string { + switch ls { + case InterfaceAdded: + return "added" + case InterfaceDeleted: + return "deleted" + default: + return "" + } +} + +func (e InterfaceEvent) String() string { + return fmt.Sprintf("%s %s", e.Name, e.Op) +} + +func WatchWireguardInterfaces() (chan InterfaceEvent, chan error, error) { + events := make(chan InterfaceEvent, 16) + errors := make(chan error, 16) + + done := make(<-chan struct{}) + + // Watch kernel interfaces + chNl := make(chan netlink.LinkUpdate, 32) + err := netlink.LinkSubscribeWithOptions(chNl, done, netlink.LinkSubscribeOptions{ + ErrorCallback: func(err error) { + errors <- err + }, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to subscribe to netlink link event group: %w", err) + } + + // Watch userspace UAPI sockets + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, nil, fmt.Errorf("failed to create fsnotify watcher: %w", err) + } + + if _, err := os.Stat(wireguardSockDir); !os.IsNotExist(err) { + err = watcher.Add(wireguardSockDir) + if err != nil { + return nil, nil, fmt.Errorf("failed to watch %s: %w", wireguardSockDir, err) + } + } + + go func() { + for { + select { + // Netlink link updates + case lu := <-chNl: + log.WithField("update", lu).Trace("Received netlink link update") + if lu.Link.Type() != nl.LinkTypeWireguard { + continue + } + + switch lu.Header.Type { + case unix.RTM_NEWLINK: + events <- InterfaceEvent{ + Op: InterfaceAdded, + Name: lu.Attrs().Name, + } + case unix.RTM_DELLINK: + events <- InterfaceEvent{ + Op: InterfaceDeleted, + Name: lu.Attrs().Name, + } + } + + // Fsnotify events + case event := <-watcher.Events: + log.WithField("event", event).Trace("Received fsnotify event") + + name := normalizeSocketName(event.Name) + + if event.Op&fsnotify.Create == fsnotify.Create { + events <- InterfaceEvent{ + Op: InterfaceAdded, + Name: name, + } + } else if event.Op&fsnotify.Remove == fsnotify.Remove { + events <- InterfaceEvent{ + Op: InterfaceDeleted, + Name: name, + } + } else { + log.Warn("Unknown fsnotify event: %+v", event) + } + + // Fsnotify errors + case errors <- <-watcher.Errors: + log.Trace("Error while watching for link changes") + } + } + }() + + return events, errors, nil +} + +func normalizeSocketName(name string) string { + name = path.Base(name) + return strings.TrimSuffix(name, ".sock") +} diff --git a/pkg/netlink/netlink.go b/pkg/netlink/netlink.go new file mode 100644 index 00000000..0abfc3f1 --- /dev/null +++ b/pkg/netlink/netlink.go @@ -0,0 +1,20 @@ +package netlink + +import "github.com/vishvananda/netlink" + +const ( + LinkTypeWireguard = "wireguard" +) + +// github.com/vishvananda/netlink does not come with the wireguard link type yet +type Wireguard struct { + netlink.LinkAttrs +} + +func (wg *Wireguard) Attrs() *netlink.LinkAttrs { + return &wg.LinkAttrs +} + +func (wg *Wireguard) Type() string { + return LinkTypeWireguard +} diff --git a/pkg/netlink/netlink_test.go b/pkg/netlink/netlink_test.go new file mode 100644 index 00000000..7ac7a73f --- /dev/null +++ b/pkg/netlink/netlink_test.go @@ -0,0 +1,37 @@ +package netlink_test + +import ( + "os" + "testing" + + "github.com/vishvananda/netlink" + nl "riasc.eu/wice/pkg/netlink" +) + +func TestWireguardLink(t *testing.T) { + if os.Getuid() != 0 { + t.Skip() + } + + l := &nl.Wireguard{ + LinkAttrs: netlink.NewLinkAttrs(), + } + l.LinkAttrs.Name = "wg-test0" + err := netlink.LinkAdd(l) + if err != nil { + t.Errorf("failed to create Wireguard interface: %s", err) + } + + l2, err := netlink.LinkByName("wg-test0") + if err != nil { + t.Errorf("failed to get link details: %s", err) + } + + if l2.Type() != "wireguard" { + t.Fail() + } + + if err := netlink.LinkDel(l); err != nil { + t.Errorf("failed to delete Wireguard device: %w", err) + } +} diff --git a/pkg/proxy/ebpf.go b/pkg/proxy/ebpf.go new file mode 100644 index 00000000..e8ead5cd --- /dev/null +++ b/pkg/proxy/ebpf.go @@ -0,0 +1,93 @@ +// +build linux + +package proxy + +import ( + "fmt" + "net" + "runtime" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/pion/ice/v2" + + icex "riasc.eu/wice/internal/ice" + netx "riasc.eu/wice/internal/net" + + log "github.com/sirupsen/logrus" +) + +type EBPFProxy struct { + BaseProxy +} + +func CheckEBPFSupport() bool { + return runtime.GOOS == "linux" +} + +func SetupEBPFMux(agentConfig *ice.AgentConfig, listenPort int) error { + addr := net.UDPAddr{ + IP: net.IPv4zero, + Port: listenPort, + } + + conn, err := netx.NewFilteredUDPConn(addr) + if err != nil { + return fmt.Errorf("failed to create filtered UDP connection: %w", err) + } + + spec := ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + License: "Apache-2.0", + Instructions: asm.Instructions{ + asm.Mov.Reg(asm.R6, asm.R1), // LDABS requires ctx in R6 + asm.LoadAbs(-0x100000+22, asm.Half), + asm.JNE.Imm(asm.R0, int32(listenPort), "skip"), + asm.LoadAbs(-0x100000+32, asm.Word), + asm.JNE.Imm(asm.R0, int32(StunMagicCookie), "skip"), + asm.Mov.Imm(asm.R0, -1).Sym("exit"), + asm.Return(), + asm.Mov.Imm(asm.R0, 0).Sym("skip"), + asm.Return(), + }, + } + prog, err := ebpf.NewProgramWithOptions(&spec, ebpf.ProgramOptions{ + LogLevel: 1, // TODO take configured log-level from args + }) + if err != nil { + return fmt.Errorf("failed to create BPF program: %w", err) + } + + err = conn.ApplyFilter(prog) + if err != nil { + return fmt.Errorf("failed to attach eBPF program to socket: %w", err) + } + + agentConfig.UDPMux = icex.NewFilteredUDPMux(icex.FilteredUDPMuxParams{ + Logger: log.WithField("logger", "ice-mux"), + Conn: conn, + }) + + return nil +} + +func NewEBPFProxy(ident string, listenPort int, cb UpdateEndpointCb, conn net.Conn) (*EBPFProxy, error) { + + rUDPAddr := conn.RemoteAddr().(*net.UDPAddr) + cb(rUDPAddr) + + return &EBPFProxy{ + BaseProxy: BaseProxy{ + Ident: ident, + }, + // Conn: conn, + }, nil +} + +func (bpf *EBPFProxy) Close() error { + return nil +} + +func (bpf *EBPFProxy) UpdateEndpoint(addr *net.UDPAddr) error { + return nil +} diff --git a/pkg/proxy/nftables.go b/pkg/proxy/nftables.go new file mode 100644 index 00000000..3e5b6819 --- /dev/null +++ b/pkg/proxy/nftables.go @@ -0,0 +1,254 @@ +// +build linux + +package proxy + +import ( + "fmt" + "net" + "runtime" + + "github.com/google/nftables" + "github.com/google/nftables/binaryutil" + "github.com/google/nftables/expr" + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netns" + "golang.org/x/sys/unix" +) + +type NFTablesProxy struct { + BaseProxy + + logger log.FieldLogger + + NFConn *nftables.Conn + Conn net.Conn +} + +func CheckNFTablesSupport() bool { + return runtime.GOOS == "linux" +} + +func NewNFTablesProxy(ident string, listenPort int, cb UpdateEndpointCb, conn net.Conn) (*NFTablesProxy, error) { + ns, err := netns.Get() + if err != nil { + return nil, err + } + + proxy := &NFTablesProxy{ + BaseProxy: BaseProxy{ + Ident: ident, + ListenPort: listenPort, + }, + logger: log.WithFields(log.Fields{ + "logger": "proxy", + "type": "nftables", + }), + NFConn: &nftables.Conn{ + NetNS: int(ns), + }, + Conn: conn, + } + + proxy.logger.Infof("Network namespace: %s", ns) + + proxy.setupTable() + + // Update Wireguard peer endpoint + rAddr := proxy.Conn.RemoteAddr().(*net.UDPAddr) + if err = cb(rAddr); err != nil { + return nil, err + } + + proxy.logger.Info("Configured stateless nftables port redirection") + + return proxy, nil +} + +func (p *NFTablesProxy) deleteTable() error { + tb := nftables.Table{ + Name: "wice", + Family: nftables.TableFamilyINet, + } + p.NFConn.DelTable(&tb) // Delete any previous existing table + p.NFConn.Flush() // We dont care about errors here... + + return nil +} + +func (p *NFTablesProxy) tableName() string { + // pkSlug := base32.HexEncoding.EncodeToString(p.WGPeer.PublicKey[:16]) + return fmt.Sprintf("wice-%s", p.Ident) +} + +func (p *NFTablesProxy) setupTable() error { + // Delete any stale tables created by WICE + p.deleteTable() + + lAddr := p.Conn.LocalAddr().(*net.UDPAddr) + rAddr := p.Conn.RemoteAddr().(*net.UDPAddr) + + tb := nftables.Table{ + Name: p.tableName(), + Family: nftables.TableFamilyINet, + } + p.NFConn.AddTable(&tb) + + // Ingress + chIngress := nftables.Chain{ + Name: "ingress", + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookInput, + Priority: nftables.ChainPriorityRaw, + Table: &tb, + } + p.NFConn.AddChain(&chIngress) + + // Match non-STUN UDP ingress traffic directed at the port of our local ICE candidate + // and redirect to the listen port of the Wireguard interface. + // STUN traffic will pass to the iceConn for keepalives and connection checks. + rDnat := nftables.Rule{ + Table: &tb, + Chain: &chIngress, + } + p.NFConn.AddChain(&chIngress) + + // meta l4proto udp + rDnat.Exprs = append(rDnat.Exprs, &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, + }) + rDnat.Exprs = append(rDnat.Exprs, &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{unix.IPPROTO_UDP}, + }) + + // udp dport lAddr.Port + rDnat.Exprs = append(rDnat.Exprs, &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, + Len: 2, + }) + rDnat.Exprs = append(rDnat.Exprs, &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(uint16(lAddr.Port)), + }) + + // @th,96,32 != StunMagicCookie + rDnat.Exprs = append(rDnat.Exprs, &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 12, + Len: 4, + }) + rDnat.Exprs = append(rDnat.Exprs, &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: binaryutil.BigEndian.PutUint32(StunMagicCookie), + }) + + // notrack + rDnat.Exprs = append(rDnat.Exprs, &expr.Notrack{}) + + // udp dport set p.Device.ListenPort + rDnat.Exprs = append(rDnat.Exprs, &expr.Immediate{ + Register: 1, + Data: binaryutil.BigEndian.PutUint16(uint16(p.ListenPort)), + }) + rDnat.Exprs = append(rDnat.Exprs, &expr.Payload{ + OperationType: expr.PayloadWrite, + SourceRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, + Len: 2, + }) + + p.NFConn.AddRule(&rDnat) + + // Egress + chEgress := nftables.Chain{ + Name: "egress", + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookOutput, + Priority: nftables.ChainPriorityRaw, + Table: &tb, + } + p.NFConn.AddChain(&chEgress) + + // Perform SNAT to the source port of Wireguard UDP traffic to match port of our local ICE candidate + rSnat := nftables.Rule{ + Table: &tb, + Chain: &chEgress, + } + + // meta l4proto udp + rSnat.Exprs = append(rSnat.Exprs, &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, + }) + rSnat.Exprs = append(rSnat.Exprs, &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{unix.IPPROTO_UDP}, + }) + + // udp sport p.ListenPort + rSnat.Exprs = append(rSnat.Exprs, &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 0, + Len: 2, + }) + rSnat.Exprs = append(rSnat.Exprs, &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(uint16(p.ListenPort)), + }) + + // udp dport rAddr.Port + rSnat.Exprs = append(rSnat.Exprs, &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, + Len: 2, + }) + rSnat.Exprs = append(rSnat.Exprs, &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(uint16(rAddr.Port)), + }) + + // notrack + rSnat.Exprs = append(rSnat.Exprs, &expr.Notrack{}) + + // udp sport set lAddr.Port + rSnat.Exprs = append(rSnat.Exprs, &expr.Immediate{ + Register: 1, + Data: binaryutil.BigEndian.PutUint16(uint16(lAddr.Port)), + }) + rSnat.Exprs = append(rSnat.Exprs, &expr.Payload{ + OperationType: expr.PayloadWrite, + SourceRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 0, + Len: 2, + }) + + p.NFConn.AddRule(&rSnat) + + if err := p.NFConn.Flush(); err != nil { + return fmt.Errorf("failed setup nftables: %w", err) + } + + return nil +} + +func (p *NFTablesProxy) Close() error { + return p.deleteTable() +} + +func (bpf *NFTablesProxy) UpdateEndpoint(addr *net.UDPAddr) error { + return nil +} diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go new file mode 100644 index 00000000..2becf558 --- /dev/null +++ b/pkg/proxy/proxy.go @@ -0,0 +1,98 @@ +package proxy + +import ( + "errors" + "io" + "net" +) + +type ProxyType int + +type UpdateEndpointCb func(addr *net.UDPAddr) error + +const ( + ProxyTypeInvalid ProxyType = iota + ProxyTypeAuto + ProxyTypeUser + ProxyTypeNFTables + ProxyTypeEBPF + + StunMagicCookie uint32 = 0x2112A442 +) + +type Proxy interface { + io.Closer + + UpdateEndpoint(addr *net.UDPAddr) error +} + +type BaseProxy struct { + ListenPort int + Ident string +} + +func ProxyTypeFromString(typ string) ProxyType { + switch typ { + case "auto": + return ProxyTypeAuto + case "user": + return ProxyTypeUser + case "nftables": + return ProxyTypeNFTables + case "ebpf": + return ProxyTypeEBPF + default: + return ProxyTypeInvalid + } +} + +func (pt ProxyType) String() string { + switch pt { + case ProxyTypeAuto: + return "auto" + case ProxyTypeUser: + return "user" + case ProxyTypeNFTables: + return "nftables" + case ProxyTypeEBPF: + return "ebpf" + } + + return "invalid" +} + +func AutoProxy() ProxyType { + if CheckEBPFSupport() { + return ProxyTypeEBPF + } else if CheckNFTablesSupport() { + return ProxyTypeNFTables + } else { + return ProxyTypeUser + } +} + +func NewProxy(pt ProxyType, ident string, listenPort int, cb UpdateEndpointCb, conn net.Conn) (Proxy, error) { + switch pt { + case ProxyTypeUser: + return NewUserProxy(ident, listenPort, cb, conn) + case ProxyTypeNFTables: + return NewNFTablesProxy(ident, listenPort, cb, conn) + case ProxyTypeEBPF: + return NewEBPFProxy(ident, listenPort, cb, conn) + } + + return nil, errors.New("unknown proxy type") +} + +func Type(p Proxy) ProxyType { + switch p.(type) { + case *NFTablesProxy: + return ProxyTypeNFTables + case *UserProxy: + return ProxyTypeUser + case *EBPFProxy: + return ProxyTypeEBPF + default: + return ProxyTypeInvalid + } +} diff --git a/pkg/proxy/user.go b/pkg/proxy/user.go new file mode 100644 index 00000000..7e3cdae5 --- /dev/null +++ b/pkg/proxy/user.go @@ -0,0 +1,74 @@ +package proxy + +import ( + "io" + "net" + + log "github.com/sirupsen/logrus" +) + +const ( + maxSegmentSize = (1 << 16) - 1 +) + +type UserProxy struct { + BaseProxy + conn *net.UDPConn + + logger log.FieldLogger +} + +func NewUserProxy(ident string, listenPort int, cb UpdateEndpointCb, conn net.Conn) (*UserProxy, error) { + var err error + + proxy := &UserProxy{ + BaseProxy: BaseProxy{ + Ident: ident, + }, + logger: log.WithFields(log.Fields{ + "logger": "proxy", + "type": "user", + }), + } + + // Userspace proxying + rAddr := net.UDPAddr{ + IP: nil, // localhost + Port: listenPort, + } + lAddr := net.UDPAddr{ + IP: net.IPv6loopback, + Port: 0, // choose automatically + } + + proxy.conn, err = net.DialUDP("udp", &lAddr, &rAddr) + if err != nil { + return nil, err + } + + // Update Wireguard peer endpoint + addr := proxy.conn.LocalAddr().(*net.UDPAddr) + err = cb(addr) + if err != nil { + return nil, err + } + + ingressBuf := make([]byte, maxSegmentSize) + egressBuf := make([]byte, maxSegmentSize) + + // Bi-directional copy between ICE and loopback UDP sockets until proxy.conn is closed + go io.CopyBuffer(conn, proxy.conn, ingressBuf) + go io.CopyBuffer(proxy.conn, conn, egressBuf) + + proxy.logger.Info("Setup user-space proxy") + + return proxy, nil +} + +func (p *UserProxy) Close() error { + return p.conn.Close() +} + +func (bpf *UserProxy) UpdateEndpoint(addr *net.UDPAddr) error { + return nil +} diff --git a/test/nat_test.go b/test/nat_test.go new file mode 100644 index 00000000..d49812c6 --- /dev/null +++ b/test/nat_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "net" + + "github.com/stv0g/gont" +) + +func main() { + n := gont.NewNetwork("test") + + n.Reset() + + sw1, _ := n.AddSwitch("sw1") + sw2, _ := n.AddSwitch("sw2") + sw3, _ := n.AddSwitch("sw3") + + mask := net.IPv4Mask(255, 255, 255, 0) + + h12, _ := n.AddHost("h12", net.IPv4(10, 0, 1, 1), + &gont.Interface{"eth0", net.IPv4(10, 0, 1, 2), mask, sw1}) + + h22, _ := n.AddHost("h22", net.IPv4(10, 0, 2, 1), + &gont.Interface{"eth0", net.IPv4(10, 0, 2, 2), mask, sw2}) + + n.AddHost("h23", net.IPv4(10, 0, 2, 1), + &gont.Interface{"eth0", net.IPv4(10, 0, 2, 3), mask, sw2}) + + h32, _ := n.AddHost("h32", net.IPv4(10, 0, 3, 1), + &gont.Interface{"eth0", net.IPv4(10, 0, 3, 2), mask, sw3}) + + n.AddNAT("n1", nil, + &gont.Interface{"nb", net.IPv4(10, 0, 1, 1), mask, sw1}, + &gont.Interface{"sb", net.IPv4(10, 0, 2, 1), mask, sw2}) + + n.AddNAT("n2", nil, + &gont.Interface{"nb", net.IPv4(10, 0, 2, 10), mask, sw2}, + &gont.Interface{"sb", net.IPv4(10, 0, 3, 1), mask, sw3}) + + h32.Ping(h12) + h22.Traceroute(h12) +} diff --git a/test/simple_test.go b/test/simple_test.go new file mode 100644 index 00000000..6d037321 --- /dev/null +++ b/test/simple_test.go @@ -0,0 +1,109 @@ +package hornet_test + +import ( + "bufio" + "fmt" + "io" + "net" + "os/exec" + "syscall" + "testing" + "time" + + log "github.com/sirupsen/logrus" +) + +func Killall(cmds ...*exec.Cmd) error { + for _, cmd := range cmds { + err := cmd.Process.Signal(syscall.SIGTERM) + if err != nil { + return err + } + + err = cmd.Wait() + if err != nil { + return err + } + } + + return nil +} + +func SlicePrefix(prefix string, stream *io.ReadCloser) { + scanner := bufio.NewScanner(*stream) + for scanner.Scan() { + fmt.Println(scanner.Text()) // Println will add back the final '\n' + } + if err := scanner.Err(); err != nil { + log.WithError(err).Error("Reading stream") + } +} + +func RunWice(h *gont.Host, args ...string) (*exec.Cmd, error) { + cmd := append([]string{"../../../cmd/wice/main.go"}, args...) + w, stdout, stderr, err := h.GoRunAsync(cmd...) + if err != nil { + return nil, err + } + + go SlicePrefix("Wice "+h.Name+": ", stdout) + go SlicePrefix("Wice "+h.Name+": ", stderr) + + return w, nil +} + +func ConfigureWireguard(h *gont.Host) error { + + return nil +} + +func TestWice(t *testing.T) { + n := gont.NewNetwork("test") + defer n.Close() + + sw, err := n.AddSwitch("sw") + if err != nil { + t.Fail() + } + + // h1, err := n.AddHost("h1", nil, &gont.Interface{"eth0", net.IPv4(10, 0, 0, 1), mask(), sw}) + // if err != nil { + // t.Fail() + // } + + // h2, err := n.AddHost("h2", nil, &gont.Interface{"eth0", net.IPv4(10, 0, 0, 2), mask(), sw}) + // if err != nil { + // t.Fail() + // } + + h3, err := n.AddHost("h3", nil, &gont.Interface{"eth0", net.IPv4(10, 0, 0, 3), mask(), sw}) + if err != nil { + t.Fail() + } + + b, stdout, stderr, err := h3.GoRunAsync("../../../cmd/wice-signal-http") + if err != nil { + t.Fail() + } + + go SlicePrefix("Backend: ", stdout) + go SlicePrefix("Backend: ", stderr) + + // w1, err := RunWice(h1) + // if err != nil { + // t.Fail() + // } + + // w2, err := RunWice(h2) + // if err != nil { + // t.Fail() + // } + + h3.Run("curl", "http://h3:8080/") + + time.Sleep(2 * time.Second) + + if err = Killall(b); err != nil { + t.Fail() + } +}