├── .github └── workflows │ └── go.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── k8snetlook │ └── main.go ├── examples └── run-k8s.yaml ├── go.mod ├── go.sum ├── k8snetlook-logo.png ├── k8snetlook ├── checkers.go ├── common.go ├── host.go ├── init.go ├── kube.go ├── pod.go └── sample_kubeconfig.yaml ├── logutil └── logutil.go └── netutils ├── dnsutil.go ├── dnsutil_test.go ├── httputil.go ├── httputil_test.go ├── icmputil.go ├── icmputil_test.go ├── netutils.go └── pmtuprobe.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | jobs: 9 | 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Set up Go 1.x 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: ^1.13 19 | id: go 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v2 23 | 24 | - name: Update dependencies 25 | run: | 26 | go mod tidy 27 | 28 | - name: Build binary 29 | run: make all 30 | 31 | - name: Run go vet 32 | run: make vet 33 | 34 | - name: Run Unit Tests 35 | run: make test 36 | 37 | - name: Cleanup 38 | run: make clean 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Bin folder 15 | bin/ 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY bin/k8snetlook / 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CGO_ENABLED=0 2 | GOARCH=amd64 3 | BINARY=k8snetlook 4 | TAG=v0.3 5 | 6 | UNAME_S := $(shell uname -s) 7 | ifeq (${UNAME_S},Linux) 8 | GOOS=linux 9 | endif 10 | ifeq (${UNAME_S},Darwin) 11 | GOOS=darwin 12 | endif 13 | 14 | PWD=$(shell pwd) 15 | BUILD_DIR=${PWD}/bin 16 | 17 | .PHONY: all k8snetlook-linux clean k8snetlook-osx vet test release docker-image 18 | 19 | all: k8snetlook-linux 20 | 21 | k8snetlook-linux: 22 | mkdir -p ${BUILD_DIR} 23 | go mod tidy 24 | CGO_ENABLED=${CGO_ENABLED} GOARCH=${GOARCH} GOOS=linux go build -ldflags="-s -w" -o ${BUILD_DIR}/${BINARY} ${PWD}/cmd/k8snetlook 25 | 26 | k8snetlook-osx: 27 | mkdir -p ${BUILD_DIR} 28 | go mod tidy 29 | CGO_ENABLED=${CGO_ENABLED} GOARCH=${GOARCH} GOOS=darwin go build -ldflags="-s -w" -o ${BUILD_DIR}/${BINARY}-osx ${PWD}/cmd/k8snetlook 30 | 31 | vet: 32 | go mod tidy 33 | CGO_ENABLED=${CGO_ENABLED} GOARCH=${GOARCH} GOOS=${GOOS} go vet ./... 34 | 35 | test: 36 | go mod tidy 37 | sudo CGO_ENABLED=${CGO_ENABLED} GOARCH=${GOARCH} GOOS=${GOOS} go test -v ./... 38 | 39 | clean: 40 | rm -rf ${BUILD_DIR} 41 | 42 | release: k8snetlook-linux 43 | cd ${BUILD_DIR} && \ 44 | upx ${BINARY} 45 | 46 | docker-image: release 47 | docker build --network host -t sarun87/k8snetlook:${TAG} . 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![master](https://github.com/sarun87/k8snetlook/workflows/Build%20&%20Test/badge.svg?branch=master) 2 | 3 | # k8snetlook 4 | Kubernetes Network Problem Detector 5 | 6 | ![plot](./k8snetlook-logo.png) 7 | 8 | ## Introduction 9 | A simple tool to help debug connectivity issues within a Pod or from a specific host in a live Kubernetes cluster easily with a single command. Works with both IPv4 as well as IPv6 K8s stacks. 10 | 11 | ## Background 12 | When connectivity between two applications within a Kubernetes cluster does not work as expected, it requires specific troubleshooting steps in real time to find the issue. Often times, this involves using network tools such as `ping`, `tcpdump`, `traceroute`, `nslookup` and others to vaildate the plumbing; both within the source Pod as well as on the host that the pod is running. 13 | 14 | It gets harder when the application Pod does not have an interactive `shell` to log into or the specific network tools installed as part of the image. [Netshoot](https://github.com/nicolaka/netshoot) helps by providing a docker image with all of the networking tools necessary to _manually_ debug. Most other tools set up test deployments and run network health checks via those deployments rather than using the actual Pods/Service that is exhibiting network issues as source/destination. 15 | 16 | k8snetlook aims to automate some of the basic mundane debugging steps in a live k8s cluster. It hopes to help minimize the need to manually intervene and debug the network at the get-go. 17 | 18 | ## Usage 19 | k8snetlook needs `kubeconfig` to be supplied to it using the `-config` flag or by exporting `KUBECONFIG` enviroment variable. The tool can be used: 20 | 1) to run host level checks only, use the `host` subcommand 21 | 2) to run pod level checks, use the the `pod` subcommand and the required arguments like PodName with it. See `k8snetlook pod --help` for more information. Pod checks will also run host checks. 22 | 23 | Command usage examples 24 | 25 | To run host checks alone 26 | ``` 27 | k8snetlook host -config /etc/kubernetes/admin.yaml 28 | ``` 29 | To run Pod check which automatically runs host checks as well 30 | ``` 31 | k8snetlook pod -config /etc/kubernetes/admin.yaml -srcpodname bbox-74d847cb47-xtpdn -srcpodns default -dstpodname nginx-6db489d4b7-9l264 -dstpodns default --externalip 8.8.8.8 32 | ``` 33 | 34 | ## Caveats 35 | * Needs to be run as root. This is because raw sockets are needed (`CAP_NET_RAW` privilege) to programmatically implement the `ping` functionality. `udp` socket could be used to remove need for this requirement (TBD?) 36 | 37 | * The binary is run on the host where the Pod with connectivity issues are present 38 | * If the tool isn't able to initialize k8s client using specified kubeconfig, the tool will fail (FUTURE? run other tests that don't need k8s information) 39 | 40 | ## Run as a docker container 41 | Docker image is hosted at sarun87/k8snetlook:. Or build your own docker image using `make docker-image` command 42 | 43 | * Command to run the tool as a docker container 44 | ``` 45 | docker run --privileged --pid=host --net=host -v /var/run/docker.sock:/var/run/docker.sock -v $KUBECONFIG:/kubeconfig.yaml sarun87/k8snetlook:v0.3 /k8snetlook host -config /kubeconfig.yaml 46 | ``` 47 | Notes: 48 | * The above command assumes that the $KUBECONFIG environment variable is pointing to a valid kubeconfig & mounts it within the container 49 | * Mounts docker socket needed interact with docker daemon. 50 | * Needs privileged context to be set to access pod's network namespace. 51 | * --net=host: Should run in host network namespace, --pid=host: Run in host pid (proc & sys paths are mounted which is needed to obtain handles to Pod's network namespace) 52 | 53 | ## Download binary & run 54 | 64-bit linux binary is available for download from the [Releases](https://github.com/sarun87/k8snetlook/releases/latest) page. 55 | 56 | * download binary to a host 57 | ``` 58 | wget https://github.com/sarun87/k8snetlook/releases/download/v0.3/k8snetlook 59 | ``` 60 | * Make the downloaded file executable 61 | ``` 62 | chmod u+x k8snetlook 63 | ``` 64 | * Run tool using sudo or as root 65 | ``` 66 | ./k8snetlook 67 | 'host' or 'pod' subcommand expected 68 | 69 | usage: k8snetlook subcommand [sub-command-options] [-config path-to-kube-config] 70 | 71 | valid subcommands 72 | pod Debug Pod & host networking 73 | host Debug host networking only 74 | ``` 75 | 76 | ## Run within K8s 77 | There are advantages & disadvantages to running a K8s network debugging tool within k8s. Ease of deployment and not requiring ssh access to the host running the problem pod are clear advantages. But the underlying problem could prevent deployment of k8snetlook on the host (Eg: communication from the host to k8s api server is down). 78 | 79 | The [examples folder](https://github.com/sarun87/k8snetlook/tree/master/examples) contains a yaml manifest that creates a K8s `Job` and runs to completion. All of the required RBAC objets and the Job itself is deployed in the `k8snetlook` namespace. Steps to run k8snetlook in k8s: 80 | 81 | * Change the value of key `command` under `containers` section with the required arguments to k8snetlook 82 | * Change the value of key `kubernetes.io/hostname` under `nodeSelector` section in the yaml spec to the host on which the problem pod is running. Then run the following commands: 83 | * Apply to cluster using: 84 | ``` 85 | kubectl apply -f examples/run-k8s.yaml 86 | ``` 87 | * Check results by pulling logs of the completed job 88 | ``` 89 | kubectl -n k8snetlook get pods 90 | ``` 91 | ``` 92 | kubectl -n k8snetlook logs 93 | ``` 94 | * Delete k8snetlook after the run 95 | ``` 96 | kubectl delete -f examples/run-k8s.yaml 97 | ``` 98 | * To rerun the test, delete k8snetlook and re-apply. 99 | 100 | ## Checks currently supported by the tool 101 | By having to initialize a Kubernetes client-set, the tool intrinsically performs API connectivity check via K8s-apiserver's VIP/External Loadbalancer in case of highly available k8s-apiserver clusters. The tool supports pure IPv6 K8s stack as well as IPv4. 102 | 103 | | Host Checks | Pod Checks | 104 | | ------------------------------------------------ | ------------------------------------------------------- | 105 | | Default gateway connectivity (icmp) | Default gateway connectivity (icmp) | 106 | | K8s-apiserver ClusterIP check (https) | K8s-apiserver ClusterIP check (https) | 107 | | K8s-apiserver individual endpoints check (https) | K8s-apiserver individual endpoints check (https) | 108 | | K8s-apiserver health-check api (livez) | Destination Pod IP connectivity (icmp) | 109 | | | External IP connectivity (icmp) | 110 | | | K8s DNS name lookup check (kubernetes.local) | 111 | | | K8s DNS name lookup for specific service check | 112 | | | Path MTU discovery between Src & Dst Pod (icmp) | 113 | | | Path MTU discovery between Src Pod & External IP (icmp) | 114 | | | All K8s service endpoints IP connectivity check (icmp) | 115 | 116 | ## How to build from source 117 | To build tool from source, run `make` as follows: 118 | ``` 119 | make all 120 | ``` 121 | The binary named `k8snetlook` will be built under `root-dir/bin/` 122 | 123 | To clean existing binaries and supporting files, run: 124 | ``` 125 | make clean 126 | ``` 127 | 128 | To speed up development, there is a darwin target defined as well. To build a darwin compatible binary, run: 129 | ``` 130 | make k8snetlook-osx 131 | ``` 132 | 133 | To create a zipped release binary, run: 134 | ``` 135 | make release 136 | ``` 137 | 138 | To create a docker image, run: 139 | ``` 140 | make docker-image 141 | ``` 142 | 143 | ## Contribute 144 | PRs welcome :) 145 | 146 | ## Demos ! 147 | 148 | - At Kubecon 2021 Arun presented k8snetlook on a 3 node cluster (Vagrant VMs): https://www.youtube.com/watch?v=_BDoxy9h95U. 149 | - We went over k8snetlook recently in an Antrea-LIVE episode https://www.youtube.com/watch?v=aWUwxQ58bEQ on a VMWare Tanzu cluster (Vsphere). 150 | -------------------------------------------------------------------------------- /cmd/k8snetlook/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/sarun87/k8snetlook/k8snetlook" 9 | log "github.com/sarun87/k8snetlook/logutil" 10 | ) 11 | 12 | var ( 13 | podCmd *flag.FlagSet // Sub-command for pod debugging 14 | hostOnlyCmd *flag.FlagSet // Sub-command for host debugging 15 | podDebugging bool // Variable to hold debug mode 16 | debugLogging bool // Enable debug logging 17 | silent bool // Output only errors 18 | ) 19 | 20 | func init() { 21 | // Pod debugging flags 22 | podCmd = flag.NewFlagSet("pod", flag.ExitOnError) 23 | podCmd.StringVar(&k8snetlook.Cfg.SrcPod.Name, "srcpodname", "", "Name of source Pod to debug") 24 | podCmd.StringVar(&k8snetlook.Cfg.SrcPod.Namespace, "srcpodns", "", "Namespace to which the Pod belongs") 25 | podCmd.StringVar(&k8snetlook.Cfg.DstPod.Name, "dstpodname", "", "Name of destination Pod to connect") 26 | podCmd.StringVar(&k8snetlook.Cfg.DstPod.Namespace, "dstpodns", "", "Namespace to which the Pod belongs") 27 | podCmd.StringVar(&k8snetlook.Cfg.DstSvc.Name, "dstsvcname", "", "Name of detination Service to debug") 28 | podCmd.StringVar(&k8snetlook.Cfg.DstSvc.Namespace, "dstsvcns", "", "Namespace to which the Pod belongs") 29 | podCmd.StringVar(&k8snetlook.Cfg.ExternalIP, "externalip", "", "External IP to test egress traffic flow") 30 | podCmd.StringVar(&k8snetlook.Cfg.KubeconfigPath, "config", os.Getenv("KUBECONFIG"), "Path to Kubeconfig") 31 | podCmd.BoolVar(&debugLogging, "debug", false, "Enable debug logging to stdout") 32 | podCmd.BoolVar(&silent, "silent", false, "Output only errors to stdout") 33 | 34 | hostOnlyCmd = flag.NewFlagSet("host", flag.ExitOnError) 35 | hostOnlyCmd.StringVar(&k8snetlook.Cfg.KubeconfigPath, "config", os.Getenv("KUBECONFIG"), "Path to Kubeconfig") 36 | hostOnlyCmd.BoolVar(&debugLogging, "debug", false, "Enable debug logging to stdout") 37 | hostOnlyCmd.BoolVar(&silent, "silent", false, "Output only errors to stdout. Return result as json") 38 | 39 | } 40 | 41 | func printUsage() { 42 | fmt.Println("") 43 | fmt.Println("usage: k8snetlook subcommand [sub-command-options] [-config path-to-kube-config] ") 44 | fmt.Println("") 45 | fmt.Println("valid subcommands") 46 | fmt.Println(" pod Debug Pod & host networking") 47 | fmt.Println(" host Debug host networking only") 48 | } 49 | 50 | func main() { 51 | 52 | if len(os.Args) < 2 { 53 | fmt.Println("'host' or 'pod' subcommand expected") 54 | printUsage() 55 | os.Exit(1) 56 | } 57 | 58 | switch os.Args[1] { 59 | case "pod": 60 | podDebugging = true 61 | podCmd.Parse(os.Args[2:]) 62 | case "host": 63 | hostOnlyCmd.Parse(os.Args[2:]) 64 | default: 65 | fmt.Println("'host' or 'pod' subcommand expected") 66 | printUsage() 67 | os.Exit(1) 68 | } 69 | 70 | validateArgs() 71 | 72 | logLevel := log.INFO 73 | if debugLogging { 74 | logLevel = log.DEBUG 75 | } 76 | if silent { 77 | logLevel = log.ERROR 78 | } 79 | 80 | log.SetLogLevel(logLevel) 81 | 82 | if err := k8snetlook.Init(k8snetlook.Cfg.KubeconfigPath); err != nil { 83 | log.Error("Unable to initialize k8snetlook\n") 84 | log.Error("%v\n", err) 85 | return 86 | } 87 | defer k8snetlook.Cleanup() 88 | 89 | k8snetlook.RunHostChecks() 90 | if podDebugging == true { 91 | k8snetlook.RunPodChecks() 92 | } 93 | if silent { 94 | fmt.Printf("%s", k8snetlook.GetReportJSON()) 95 | return 96 | } 97 | k8snetlook.PrintReport() 98 | } 99 | 100 | func validateArgs() { 101 | fmt.Println("") 102 | if podDebugging && (k8snetlook.Cfg.SrcPod.Name == "" || k8snetlook.Cfg.SrcPod.Namespace == "") { 103 | fmt.Printf("error: srcpodname flag and srcpodns required for pod debugging\n\n") 104 | podCmd.Usage() 105 | os.Exit(1) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/run-k8s.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: k8snetlook 5 | 6 | --- 7 | 8 | apiVersion: v1 9 | kind: ServiceAccount 10 | metadata: 11 | name: k8snetlook 12 | namespace: k8snetlook 13 | 14 | --- 15 | 16 | kind: ClusterRole 17 | apiVersion: rbac.authorization.k8s.io/v1 18 | metadata: 19 | name: k8snetlook 20 | rules: 21 | - apiGroups: [""] 22 | resources: ["pods", "nodes", "endpoints", "services", "serviceaccounts", "secrets"] 23 | verbs: ["get", "list"] 24 | 25 | --- 26 | 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | name: k8snetlook 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: ClusterRole 34 | name: k8snetlook 35 | subjects: 36 | - kind: ServiceAccount 37 | name: k8snetlook 38 | namespace: k8snetlook 39 | 40 | --- 41 | 42 | apiVersion: batch/v1 43 | kind: Job 44 | metadata: 45 | name: k8snetlook 46 | namespace: k8snetlook 47 | spec: 48 | template: 49 | spec: 50 | hostNetwork: true 51 | hostPID: true 52 | serviceAccountName: k8snetlook 53 | nodeSelector: 54 | ## Change hostname to represent the host that is running the src pod 55 | kubernetes.io/hostname: ip-10-0-1-253.us-west-2.compute.internal 56 | containers: 57 | - name: k8snetlook 58 | image: sarun87/k8snetlook:v0.3 59 | ## Edit command suite your debugging needs 60 | command: ["/k8snetlook", "host"] 61 | ## Pod debugging example 62 | #command: ["/k8snetlook", "pod", "-srcpodname=nginx-6db489d4b7-2hww8", "-srcpodns=default", "-dstpodname=nginx-6db489d4b7-9l264", "-dstpodns=default", "-externalip=8.8.8.8"] 63 | volumeMounts: 64 | - mountPath: /var/run/docker.sock 65 | name: docker-socket 66 | securityContext: 67 | privileged: true 68 | volumes: 69 | - name: docker-socket 70 | hostPath: 71 | path: /var/run/docker.sock 72 | type: Socket 73 | restartPolicy: Never 74 | backoffLimit: 0 75 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sarun87/k8snetlook 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/docker/docker v20.10.14+incompatible 7 | github.com/google/gopacket v1.1.19 8 | github.com/miekg/dns v1.1.48 9 | github.com/satori/go.uuid v1.2.0 10 | github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 11 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 12 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 13 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 14 | k8s.io/apimachinery v0.23.6 15 | k8s.io/client-go v0.23.6 16 | ) 17 | 18 | require ( 19 | github.com/Microsoft/go-winio v0.5.1 // indirect 20 | github.com/containerd/containerd v1.6.4 // indirect 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/docker/distribution v2.8.1+incompatible // indirect 23 | github.com/docker/go-connections v0.4.0 // indirect 24 | github.com/docker/go-units v0.4.0 // indirect 25 | github.com/go-logr/logr v1.2.2 // indirect 26 | github.com/gogo/protobuf v1.3.2 // indirect 27 | github.com/golang/protobuf v1.5.2 // indirect 28 | github.com/google/go-cmp v0.5.6 // indirect 29 | github.com/google/gofuzz v1.2.0 // indirect 30 | github.com/googleapis/gnostic v0.5.5 // indirect 31 | github.com/imdario/mergo v0.3.12 // indirect 32 | github.com/json-iterator/go v1.1.12 // indirect 33 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect 34 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 35 | github.com/modern-go/reflect2 v1.0.2 // indirect 36 | github.com/morikuni/aec v1.0.0 // indirect 37 | github.com/opencontainers/go-digest v1.0.0 // indirect 38 | github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/sirupsen/logrus v1.8.1 // indirect 41 | github.com/spf13/pflag v1.0.5 // indirect 42 | golang.org/x/mod v0.4.2 // indirect 43 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect 44 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 45 | golang.org/x/text v0.3.7 // indirect 46 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 47 | golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect 48 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 49 | google.golang.org/appengine v1.6.7 // indirect 50 | google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect 51 | google.golang.org/grpc v1.43.0 // indirect 52 | google.golang.org/protobuf v1.27.1 // indirect 53 | gopkg.in/inf.v0 v0.9.1 // indirect 54 | gopkg.in/yaml.v2 v2.4.0 // indirect 55 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 56 | gotest.tools/v3 v3.2.0 // indirect 57 | k8s.io/api v0.23.6 // indirect 58 | k8s.io/klog/v2 v2.30.0 // indirect 59 | k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect 60 | k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect 61 | sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect 62 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect 63 | sigs.k8s.io/yaml v1.2.0 // indirect 64 | ) 65 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 22 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 23 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 24 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 25 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 26 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 27 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 28 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 29 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 30 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 31 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 32 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 33 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 34 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 35 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 36 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 37 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 40 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 41 | github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 42 | github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= 43 | github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= 44 | github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= 45 | github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 46 | github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 47 | github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 48 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 49 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 50 | github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= 51 | github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= 52 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 53 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 54 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 55 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 56 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 57 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 58 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 59 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 60 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 61 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 62 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 63 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 64 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 65 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 66 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 67 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 68 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 69 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 70 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 71 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 72 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 73 | github.com/containerd/containerd v1.6.4 h1:SEDZBp10mhCp+hkO3Njz/YhGrI7ah3edNcUlRdUPOgg= 74 | github.com/containerd/containerd v1.6.4/go.mod h1:oWOqbuJUZmOVafhA0lj2NAXbiO1u7F0K5l1bUgdyo94= 75 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 76 | github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 77 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 78 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 79 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 80 | github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= 81 | github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 82 | github.com/docker/docker v20.10.14+incompatible h1:+T9/PRYWNDo5SZl5qS1r9Mo/0Q8AwxKKPtu9S1yxM0w= 83 | github.com/docker/docker v20.10.14+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 84 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 85 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 86 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= 87 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 88 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 89 | github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 90 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 91 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 92 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 93 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 94 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 95 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 96 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 97 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 98 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 99 | github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 100 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 101 | github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 102 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 103 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 104 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 105 | github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= 106 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 107 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 108 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 109 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 110 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 111 | github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 112 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 113 | github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= 114 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 115 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 116 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 117 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 118 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 119 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 120 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 121 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 122 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 123 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 124 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 125 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 126 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 127 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 128 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 129 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 130 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 131 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 132 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 133 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 134 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 135 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 136 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 137 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 138 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 139 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 140 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 141 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 142 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 143 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 144 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 145 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 146 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 147 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 148 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 149 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 150 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 151 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 152 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 153 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 154 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 155 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 156 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 157 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 158 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 159 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 160 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 161 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 162 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 163 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 164 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 165 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 166 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 167 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 168 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 169 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 170 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 171 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 172 | github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 173 | github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 174 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 175 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 176 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 177 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 178 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 179 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 180 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 181 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 182 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 183 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 184 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 185 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 186 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 187 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 188 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 189 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 190 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 191 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 192 | github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= 193 | github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= 194 | github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= 195 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 196 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 197 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 198 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 199 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 200 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 201 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 202 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 203 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 204 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 205 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 206 | github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= 207 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 208 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 209 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 210 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 211 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 212 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 213 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 214 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 215 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 216 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 217 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 218 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 219 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 220 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 221 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 222 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 223 | github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= 224 | github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= 225 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 226 | github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 227 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= 228 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= 229 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 230 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 231 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 232 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 233 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 234 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 235 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 236 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 237 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 238 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 239 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 240 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 241 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 242 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 243 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 244 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 245 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 246 | github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= 247 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 248 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 249 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 250 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 251 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 252 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 253 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 254 | github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= 255 | github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 256 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 257 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 258 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 259 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 260 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 261 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 262 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 263 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 264 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 265 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 266 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 267 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 268 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 269 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 270 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 271 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 272 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 273 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 274 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 275 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 276 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 277 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 278 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 279 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 280 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 281 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 282 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 283 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 284 | github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 h1:+UB2BJA852UkGH42H+Oee69djmxS3ANzl2b/JtT1YiA= 285 | github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= 286 | github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 287 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= 288 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 289 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 290 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 291 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 292 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 293 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 294 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 295 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 296 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 297 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 298 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 299 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 300 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 301 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 302 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 303 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 304 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 305 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 306 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 307 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 308 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 309 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 310 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 311 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 312 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 313 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 314 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 315 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 316 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 317 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 318 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 319 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 320 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 321 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 322 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 323 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 324 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 325 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 326 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 327 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 328 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 329 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 330 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 331 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 332 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 333 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 334 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 335 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 336 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 337 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 338 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 339 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 340 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 341 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 342 | golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= 343 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 344 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 345 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 346 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 347 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 348 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 349 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 350 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 351 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 352 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 353 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 354 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 355 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 356 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 357 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 358 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 359 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 360 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 361 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 362 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 363 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 364 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 365 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 366 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 367 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 368 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 369 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 370 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 371 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 372 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 373 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 374 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 375 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 376 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 377 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 378 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 379 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 380 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 381 | golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 382 | golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 383 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= 384 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 385 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 386 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 387 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 388 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 389 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 390 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 391 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 392 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 393 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 394 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 395 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 396 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= 397 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 398 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 399 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 400 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 401 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 402 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 403 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 404 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 405 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 406 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 407 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 408 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 409 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 410 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 411 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 412 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 413 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 417 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 418 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 419 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 420 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 421 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 422 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 423 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 424 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 425 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 426 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 427 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 428 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 429 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 430 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 431 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 432 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 433 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 434 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 435 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 436 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 437 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 438 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 439 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 440 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 441 | golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 442 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 443 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 444 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 445 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 446 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 447 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 448 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 449 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 450 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 451 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 452 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 453 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 454 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 457 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 458 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 459 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 460 | golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 461 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= 462 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 463 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 464 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 465 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 466 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 467 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 468 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 469 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 470 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 471 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 472 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 473 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 474 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 475 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 476 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 477 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 478 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 479 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 480 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= 481 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 482 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 483 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 484 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 485 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 486 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 487 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 488 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 489 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 490 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 491 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 492 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 493 | golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 494 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 495 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 496 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 497 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 498 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 499 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 500 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 501 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 502 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 503 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 504 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 505 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 506 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 507 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 508 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 509 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 510 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 511 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 512 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 513 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 514 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 515 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 516 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 517 | golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 518 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 519 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 520 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 521 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 522 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 523 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 524 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 525 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 526 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 527 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 528 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 529 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 530 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 531 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 532 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 533 | golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= 534 | golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 535 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 536 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 537 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 538 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 539 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 540 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 541 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 542 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 543 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 544 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 545 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 546 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 547 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 548 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 549 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 550 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 551 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 552 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 553 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 554 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 555 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 556 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 557 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 558 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 559 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 560 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 561 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 562 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 563 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 564 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 565 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 566 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 567 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 568 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 569 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 570 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 571 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 572 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 573 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 574 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 575 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 576 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 577 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 578 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 579 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 580 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 581 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 582 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 583 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 584 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 585 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 586 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 587 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 588 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 589 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 590 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 591 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 592 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 593 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 594 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 595 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 596 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 597 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 598 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 599 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 600 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 601 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 602 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 603 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 604 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 605 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 606 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 607 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 608 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 609 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 610 | google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= 611 | google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= 612 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 613 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 614 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 615 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 616 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 617 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 618 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 619 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 620 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 621 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 622 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 623 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 624 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 625 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 626 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 627 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 628 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 629 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 630 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 631 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 632 | google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= 633 | google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 634 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 635 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 636 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 637 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 638 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 639 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 640 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 641 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 642 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 643 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 644 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 645 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 646 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 647 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 648 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 649 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 650 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 651 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 652 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 653 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 654 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 655 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 656 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 657 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 658 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 659 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 660 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 661 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 662 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 663 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 664 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 665 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 666 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 667 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 668 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 669 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 670 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 671 | gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= 672 | gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= 673 | gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= 674 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 675 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 676 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 677 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 678 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 679 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 680 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 681 | k8s.io/api v0.23.6 h1:yOK34wbYECH4RsJbQ9sfkFK3O7f/DUHRlzFehkqZyVw= 682 | k8s.io/api v0.23.6/go.mod h1:1kFaYxGCFHYp3qd6a85DAj/yW8aVD6XLZMqJclkoi9g= 683 | k8s.io/apimachinery v0.23.6 h1:RH1UweWJkWNTlFx0D8uxOpaU1tjIOvVVWV/bu5b3/NQ= 684 | k8s.io/apimachinery v0.23.6/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= 685 | k8s.io/client-go v0.23.6 h1:7h4SctDVQAQbkHQnR4Kzi7EyUyvla5G1pFWf4+Od7hQ= 686 | k8s.io/client-go v0.23.6/go.mod h1:Umt5icFOMLV/+qbtZ3PR0D+JA6lvvb3syzodv4irpK4= 687 | k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 688 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 689 | k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= 690 | k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= 691 | k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 692 | k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= 693 | k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= 694 | k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 695 | k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= 696 | k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 697 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 698 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 699 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 700 | sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= 701 | sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= 702 | sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= 703 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= 704 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= 705 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= 706 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 707 | -------------------------------------------------------------------------------- /k8snetlook-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarun87/k8snetlook/760a2b5af50efae961d40182700c9e6189d725fb/k8snetlook-logo.png -------------------------------------------------------------------------------- /k8snetlook/checkers.go: -------------------------------------------------------------------------------- 1 | package k8snetlook 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "strconv" 8 | 9 | log "github.com/sarun87/k8snetlook/logutil" 10 | "github.com/sarun87/k8snetlook/netutils" 11 | ) 12 | 13 | // RunGatewayConnectivityCheck checks connectivity to default gw 14 | func RunGatewayConnectivityCheck() (bool, error) { 15 | log.Debug("Sending ICMP message to gw IP:%s", Cfg.HostGatewayIP) 16 | pass, err := netutils.SendRecvICMPMessage(Cfg.HostGatewayIP, 64, true) 17 | if err != nil { 18 | log.Debug(" (Failed) Error running RunGatewayConnectivityCheck. Error: %v\n", err) 19 | return false, err 20 | } 21 | if pass == 0 { 22 | log.Debug(" (Passed) Gateway connectivity check completed successfully") 23 | return true, nil 24 | } 25 | log.Debug(" (Failed) Gateway connectivity check failed") 26 | return false, nil 27 | } 28 | 29 | // RunDstConnectivityCheck checks connectivity to destination specified by dstIP 30 | func RunDstConnectivityCheck(dstIP string) (bool, error) { 31 | pass, err := netutils.SendRecvICMPMessage(dstIP, 64, true) 32 | if err != nil { 33 | log.Debug(" (Failed) Error running connectivity check to %s. Error: %v\n", dstIP, err) 34 | return false, err 35 | } 36 | if pass == 0 { 37 | log.Debug(" (Passed) Connectivity check to destination %s completed successfully\n", dstIP) 38 | return true, nil 39 | } 40 | log.Debug(" (Failed) Connectivity check to destination %s failed\n", dstIP) 41 | return false, nil 42 | } 43 | 44 | // RunKubeAPIServiceIPConnectivityCheck checks connectivity to K8s api service via clusterIP 45 | func RunKubeAPIServiceIPConnectivityCheck() (bool, error) { 46 | // TODO: Handle secure/non-secure api-servers 47 | // HTTP 401 return code is a successful check 48 | url := fmt.Sprintf("https://%s", net.JoinHostPort(Cfg.KubeAPIService.IP, strconv.Itoa(int(Cfg.KubeAPIService.Port)))) 49 | var body []byte 50 | responseCode, err := netutils.SendRecvHTTPMessage(url, "", &body) 51 | if err != nil { 52 | log.Debug(" (Failed) Error running RunKubeAPIServiceIPConnectivityCheck. Error: %v\n", err) 53 | return false, err 54 | } 55 | if responseCode == http.StatusUnauthorized { 56 | log.Debug(" (Passed) Kube API Service IP connectivity check completed successfully") 57 | } else { 58 | log.Debug(" (Passed) Kube API Service IP connectivity check returned a non 401 HTTP Code") 59 | } 60 | return true, nil 61 | } 62 | 63 | // RunKubeAPIEndpointIPConnectivityCheck checks connectivity to k8s api server via each endpoint (nodeIP) 64 | func RunKubeAPIEndpointIPConnectivityCheck() (bool, error) { 65 | // TODO: Handle secure/non-secure api-servers 66 | // HTTP 401 return code is a successful check 67 | endpoints := getEndpointsFromService("default", "kubernetes") 68 | totalCount := len(endpoints) 69 | if totalCount == 0 { 70 | return false, fmt.Errorf("could not fetch endpoints for k8s api server") 71 | } 72 | passedCount := 0 73 | for _, ep := range endpoints { 74 | url := fmt.Sprintf("https://%s", net.JoinHostPort(ep.IP, strconv.Itoa(int(ep.Port)))) 75 | log.Debug(" checking endpoint: %s ........", url) 76 | var body []byte 77 | responseCode, err := netutils.SendRecvHTTPMessage(url, "", &body) 78 | if err != nil { 79 | log.Debug(" failed connectivity check. Error: %v\n", err) 80 | continue 81 | } 82 | if responseCode == http.StatusUnauthorized { 83 | log.Debug(" passed connectivity check") 84 | } else { 85 | log.Debug(" passed connectivity check. Retured non 401 code though") 86 | } 87 | passedCount++ 88 | } 89 | if passedCount == totalCount { 90 | log.Debug(" (Passed) Kube API Endpoint IP connectivity check") 91 | return true, nil 92 | } 93 | log.Debug(" (Failed) Kube API Endoint IP connectivity check for one or more endpoints") 94 | return false, nil 95 | } 96 | 97 | // RunAPIServerHealthCheck checks api server health using livez endpoint 98 | func RunAPIServerHealthCheck() (bool, error) { 99 | url := fmt.Sprintf("https://%s/livez?verbose", net.JoinHostPort(Cfg.KubeAPIService.IP, strconv.Itoa(int(Cfg.KubeAPIService.Port)))) 100 | svcAccountToken, err := getSvcAccountToken() 101 | if err != nil { 102 | log.Debug(" (Failed) ", err) 103 | return false, err 104 | } 105 | var body []byte 106 | responseCode, err := netutils.SendRecvHTTPMessage(url, svcAccountToken, &body) 107 | if err != nil { 108 | log.Debug(" Unable to fetch api server check. Error: %v\n", err) 109 | return false, err 110 | } 111 | if responseCode != http.StatusOK { 112 | log.Debug(" (Failed) status check returned non-200 http code of %d\n", responseCode) 113 | return false, nil 114 | } 115 | log.Debug("%s", body) 116 | log.Debug(" (Passed) please check above statuses for (ok)") 117 | return true, nil 118 | } 119 | 120 | // RunK8sDNSLookupCheck checks DNS lookup functionality for a given K8s service 121 | func RunK8sDNSLookupCheck(dnsServerIP, dstSvcName, dstSvcNamespace, dstSvcExpectedIP string) (bool, error) { 122 | dnsServerURL := net.JoinHostPort(dnsServerIP, "53") 123 | // TODO: Fetch domain information from cluster 124 | svcfqdn := fmt.Sprintf("%s.%s.svc.cluster.local.", dstSvcName, dstSvcNamespace) 125 | ips, err := netutils.RunDNSLookupUsingCustomResolver(dnsServerURL, svcfqdn) 126 | if err != nil { 127 | log.Debug(" (Failed) Unable to run dns lookup to %s, error: %v\n", svcfqdn, err) 128 | return false, err 129 | } 130 | // Check if the resolved IP matches with the IP reported by K8s 131 | for _, ip := range ips { 132 | if ip == dstSvcExpectedIP { 133 | log.Debug(" (Passed) dns lookup to %s returned: %s. Expected: %s\n", svcfqdn, ip, dstSvcExpectedIP) 134 | return true, nil 135 | } 136 | } 137 | log.Debug(" (Failed) Lookup of %s retured: %v, expected: %s\n", svcfqdn, ips, dstSvcExpectedIP) 138 | return false, nil 139 | } 140 | 141 | // RunMTUProbeToDstIPCheck checks path-MTU by probing the traffic path using icmp messages 142 | func RunMTUProbeToDstIPCheck(dstIP string) (bool, error) { 143 | supportedMTU, err := netutils.PMTUProbeToDestIP(dstIP) 144 | if err != nil { 145 | log.Debug(" (Failed) Unable to run pmtud for %s. Error: %v\n", dstIP, err) 146 | return false, err 147 | } 148 | log.Debug(" Maximum MTU that works for destination IP: %s is %d\n", dstIP, supportedMTU) 149 | ifaces, err := net.Interfaces() 150 | if err != nil { 151 | log.Debug(" Unable to fetch network interfaces. Error: %v\n", err) 152 | return false, err 153 | } 154 | for _, iface := range ifaces { 155 | // If loopback device, skip 156 | if iface.Flags&net.FlagLoopback == net.FlagLoopback { 157 | continue 158 | } 159 | if iface.MTU > supportedMTU { 160 | log.Debug(" Iface %s has higher mtu than supported path mtu. Has: %d, should be less than %d\n", iface.Name, iface.MTU, supportedMTU) 161 | } 162 | } 163 | // TODO: Check for the outgoing interface mtu and compare 164 | log.Info(" (Passed) Retured MTU for destination IP: %s = %d\n", dstIP, supportedMTU) 165 | return true, nil 166 | } 167 | 168 | // RunDstSvcEndpointsConnectivityCheck checks connectivity from SrcPod to all IPs provided to this checker 169 | func RunDstSvcEndpointsConnectivityCheck(endpoints []Endpoint) (bool, error) { 170 | totalCount := len(endpoints) 171 | if totalCount == 0 { 172 | return false, fmt.Errorf("could not fetch endpoints for k8s api server") 173 | } 174 | passedCount := 0 175 | for _, ep := range endpoints { 176 | log.Debug(" checking endpoint: %s ........", ep.IP) 177 | pass, err := netutils.SendRecvICMPMessage(ep.IP, 64, true) 178 | if err != nil { 179 | log.Debug(" (Failed) Error running connectivity check to %s. Error: %v\n", ep.IP, err) 180 | } 181 | if pass == 0 { 182 | log.Debug(" (Passed) Connectivity check to destination %s completed successfully\n", ep.IP) 183 | passedCount++ 184 | } else { 185 | log.Debug(" (Failed) Connectivity check to destination %s failed\n", ep.IP) 186 | } 187 | } 188 | if passedCount == totalCount { 189 | log.Debug(" (Passed) DstSvc Endpoints IP connectivity check") 190 | return true, nil 191 | } 192 | log.Debug(" (Failed) DstSvc Endoints IP connectivity check for one or more endpoints") 193 | return false, nil 194 | } 195 | -------------------------------------------------------------------------------- /k8snetlook/common.go: -------------------------------------------------------------------------------- 1 | package k8snetlook 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | log "github.com/sarun87/k8snetlook/logutil" 7 | ) 8 | 9 | // PrintReport prints the summary of the checks 10 | func PrintReport() { 11 | log.Info("----------------k8snetlook-----------------") 12 | log.Info("") 13 | log.Info("----> Host Checks") 14 | var hostPassCount, podPassCount int 15 | for _, ch := range allChecks.HostChecks { 16 | symbol := "fail" 17 | if ch.Success { 18 | symbol = " ok " 19 | hostPassCount++ 20 | } 21 | log.Info(" %s\t%s\n", symbol, ch.Name) 22 | } 23 | log.Info("") 24 | if len(allChecks.PodChecks) > 0 { 25 | log.Info("----> Pod Checks (from within SrcPod)") 26 | for _, ch := range allChecks.PodChecks { 27 | symbol := "fail" 28 | if ch.Success { 29 | symbol = " ok " 30 | podPassCount++ 31 | } 32 | log.Info(" %s\t%s\n", symbol, ch.Name) 33 | } 34 | } 35 | log.Info("") 36 | log.Info("-------------Summary-------------------") 37 | log.Info("") 38 | log.Info(" Host Checks: %d/%d\n", hostPassCount, len(allChecks.HostChecks)) 39 | log.Info(" Pod Checks: %d/%d\n", podPassCount, len(allChecks.PodChecks)) 40 | log.Info(" Total Checks: %d/%d\n", hostPassCount+podPassCount, len(allChecks.HostChecks)+len(allChecks.PodChecks)) 41 | log.Info("") 42 | log.Info("---------------------------------------") 43 | } 44 | 45 | // GetReportJSON returns allChecks object as a JSON string 46 | func GetReportJSON() string { 47 | jsonResult, err := json.Marshal(allChecks) 48 | if err != nil { 49 | return "Unable to return results as JSON string" 50 | } 51 | return string(jsonResult) 52 | } 53 | -------------------------------------------------------------------------------- /k8snetlook/host.go: -------------------------------------------------------------------------------- 1 | package k8snetlook 2 | 3 | import ( 4 | log "github.com/sarun87/k8snetlook/logutil" 5 | ) 6 | 7 | // RunHostChecks runs checks from host network namespace 8 | func RunHostChecks() { 9 | log.Debug("----------- Host Checks -----------") 10 | 11 | log.Debug("----> [From Host] Running default gateway connectivity check..") 12 | pass, err := RunGatewayConnectivityCheck() 13 | allChecks.HostChecks = append(allChecks.HostChecks, Check{ 14 | Name: "Default gateway connectivity check", Success: pass, ErrorMsg: err}) 15 | 16 | log.Debug("----> [From Host] Running Kube service IP connectivity check..") 17 | pass, err = RunKubeAPIServiceIPConnectivityCheck() 18 | allChecks.HostChecks = append(allChecks.HostChecks, Check{ 19 | Name: "Kube service IP connectivity check", Success: pass, ErrorMsg: err}) 20 | 21 | log.Debug("----> [From Host] Running Kube API Server Endpoint IP connectivity check..") 22 | pass, err = RunKubeAPIEndpointIPConnectivityCheck() 23 | allChecks.HostChecks = append(allChecks.HostChecks, Check{ 24 | Name: "Kube API Server Endpoint IP connectivity check", Success: pass, ErrorMsg: err}) 25 | 26 | log.Debug("----> [From Host] Running Kube API Server health check..") 27 | pass, err = RunAPIServerHealthCheck() 28 | allChecks.HostChecks = append(allChecks.HostChecks, Check{ 29 | Name: "Kube API Server health check", Success: pass, ErrorMsg: err}) 30 | 31 | log.Debug("-----------------------------------") 32 | } 33 | -------------------------------------------------------------------------------- /k8snetlook/init.go: -------------------------------------------------------------------------------- 1 | package k8snetlook 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "strings" 7 | 8 | "github.com/docker/docker/client" 9 | log "github.com/sarun87/k8snetlook/logutil" 10 | "github.com/sarun87/k8snetlook/netutils" 11 | "github.com/vishvananda/netns" 12 | ) 13 | 14 | const ( 15 | defaultInternetEgressTestIP = "8.8.8.8" 16 | ) 17 | 18 | // Pod struct specifies properties required for Pod network debugging 19 | type Pod struct { 20 | Name string 21 | Namespace string 22 | IP string 23 | NsHandle netns.NsHandle // Initializes this with an open FD to the netns file /proc//ns/net 24 | } 25 | 26 | // Service struct specifies properties required for decribing a K8s service 27 | type Service struct { 28 | Name string 29 | Namespace string 30 | ClusterIP Endpoint 31 | SvcEndpoints []Endpoint 32 | } 33 | 34 | // Endpoint struct specifies properties that an Endpoint represents 35 | type Endpoint struct { 36 | IP string 37 | Port int32 38 | } 39 | 40 | // Config struct represents the properties required by k8snetlook to run checks 41 | // most properties are populated from user input 42 | type Config struct { 43 | SrcPod Pod 44 | DstPod Pod 45 | DstSvc Service 46 | ExternalIP string 47 | KubeconfigPath string 48 | 49 | KubeAPIService Endpoint 50 | KubeDNSService Endpoint 51 | HostGatewayIP string 52 | } 53 | 54 | // Check describes the reporting structure for a network check 55 | type Check struct { 56 | Name string `json:"name"` 57 | Success bool `json:"success"` 58 | ErrorMsg error `json:"error_msg"` 59 | } 60 | 61 | // Checker stores check names and results for all of the checks 62 | type Checker struct { 63 | PodChecks []Check `json:"pod_checks,omitempty"` 64 | HostChecks []Check `json:"host_checks"` 65 | } 66 | 67 | // allChecks holds information about the checks being run by k8snetlook 68 | var allChecks Checker 69 | 70 | // Cfg is an instance of Config struct 71 | var Cfg Config 72 | 73 | // Init initializes k8snetlook 74 | func Init(kubeconfigPath string) error { 75 | if err := initKubernetesClient(kubeconfigPath); err != nil { 76 | return err 77 | } 78 | return initK8sInfo() 79 | } 80 | 81 | // initK8sInfo initializes information related to pods, services by querying k8s api 82 | func initK8sInfo() error { 83 | var err error 84 | // If we aren't able to talk to the k8sapiserver endpoint via ip specified by kubeconfig, 85 | // return. Do not execute further 86 | if Cfg.KubeAPIService, err = getServiceClusterIP("default", "kubernetes"); err != nil { 87 | return err 88 | } 89 | Cfg.HostGatewayIP, _ = netutils.GetHostGatewayIP() 90 | Cfg.KubeDNSService, _ = getServiceClusterIP("kube-system", "kube-dns") 91 | Cfg.SrcPod.NsHandle = netns.NsHandle(-1) 92 | if Cfg.SrcPod.Name != "" && Cfg.SrcPod.Namespace != "" { 93 | Cfg.SrcPod.IP = getPodIPFromName(Cfg.SrcPod.Namespace, Cfg.SrcPod.Name) 94 | Cfg.SrcPod.NsHandle = getPodNetnsHandle(Cfg.SrcPod.Namespace, Cfg.SrcPod.Name) 95 | } 96 | Cfg.DstPod.NsHandle = netns.NsHandle(-1) 97 | if Cfg.DstPod.Name != "" && Cfg.DstPod.Namespace != "" { 98 | Cfg.DstPod.IP = getPodIPFromName(Cfg.DstPod.Namespace, Cfg.DstPod.Name) 99 | } 100 | if Cfg.DstSvc.Name != "" && Cfg.DstSvc.Namespace != "" { 101 | Cfg.DstSvc.ClusterIP, _ = getServiceClusterIP(Cfg.DstSvc.Namespace, Cfg.DstSvc.Name) 102 | Cfg.DstSvc.SvcEndpoints = getEndpointsFromService(Cfg.DstSvc.Namespace, Cfg.DstSvc.Name) 103 | } 104 | return nil 105 | } 106 | 107 | func getPodNetnsHandle(namespace string, podName string) netns.NsHandle { 108 | containerID := getContainerIDFromPod(namespace, podName) 109 | if containerID == "" { 110 | log.Error("Unable to fetch container id for pod %s. Exiting..\n", podName) 111 | Cleanup() 112 | os.Exit(1) 113 | } 114 | containerID = strings.TrimPrefix(containerID, "docker://") 115 | log.Debug("ContainerID:%s\n", containerID) 116 | cli, err := client.NewClientWithOpts(client.FromEnv,client.WithAPIVersionNegotiation()) 117 | if err != nil { 118 | log.Error("Unable to create docker client: %v", err) 119 | os.Exit(1) 120 | } 121 | containerJSON, err := cli.ContainerInspect(context.Background(), containerID) 122 | if err != nil { 123 | log.Error("Unable to inspect container: %v", err) 124 | os.Exit(1) 125 | } 126 | log.Debug("Pid of container: %d\n", containerJSON.State.Pid) 127 | nshandle, err := netns.GetFromPid(containerJSON.State.Pid) 128 | if err != nil { 129 | log.Error("Unable to fetch netns handle for pod %s. Error: %v Exiting..\n", podName, err) 130 | Cleanup() 131 | os.Exit(1) 132 | } 133 | return nshandle 134 | } 135 | 136 | // Cleanup closes all of the open network namespaces handles 137 | func Cleanup() { 138 | if Cfg.SrcPod.NsHandle.IsOpen() { 139 | Cfg.SrcPod.NsHandle.Close() 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /k8snetlook/kube.go: -------------------------------------------------------------------------------- 1 | package k8snetlook 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | log "github.com/sarun87/k8snetlook/logutil" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "golang.org/x/net/context" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/clientcmd" 13 | ) 14 | 15 | var clientset *kubernetes.Clientset 16 | 17 | func initKubernetesClient(kubeconfigPath string) error { 18 | // check if running in-cluster. If so initialize client-set using incluster method 19 | config, err := rest.InClusterConfig() 20 | if err != nil { 21 | log.Debug("Not running in Pod or unable to fetch config via incluster method. Error:%v", err) 22 | log.Debug("Trying from kubeconfig specified via command line flag") 23 | // Fall back to using config provided as part of command line arguments 24 | // use the current context in kubeconfig 25 | if kubeconfigPath == "" { 26 | return fmt.Errorf("Not running in Pod & kubeconfig not specified using -config flag") 27 | } 28 | config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath) 29 | if err != nil { 30 | return err 31 | } 32 | } 33 | config.Timeout = time.Second * 4 34 | clientset, err = kubernetes.NewForConfig(config) 35 | if err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | func getServiceClusterIP(namespace string, serviceName string) (Endpoint, error) { 42 | service, err := clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) 43 | if err != nil { 44 | log.Error("Error fetching %s service in %s ns. Error: %v", serviceName, namespace, err) 45 | return Endpoint{}, err 46 | } 47 | // Return one port only 48 | return Endpoint{IP: service.Spec.ClusterIP, Port: service.Spec.Ports[0].Port}, nil 49 | } 50 | 51 | func getPodIPFromName(namespace string, podName string) string { 52 | // if namespace == "" i.e metav1.NamespaceAll, then all pods are listed 53 | pod, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) 54 | if err != nil { 55 | log.Error("Error fetching %s pod in %s ns. Error: %v", podName, namespace, err) 56 | return "" 57 | } 58 | return pod.Status.PodIP 59 | } 60 | 61 | func getContainerIDFromPod(namespace string, podName string) string { 62 | // if namespace == "" i.e metav1.NamespaceAll, then all pods are listed 63 | pod, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) 64 | if err != nil { 65 | log.Error("Error fetching %s pod in %s ns. Error: %v", podName, namespace, err) 66 | return "" 67 | } 68 | // Pod should have alteast one container (pause) 69 | return pod.Status.ContainerStatuses[0].ContainerID 70 | } 71 | 72 | func getEndpointsFromService(namespace string, serviceName string) []Endpoint { 73 | var ret []Endpoint 74 | endpoints, err := clientset.CoreV1().Endpoints(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) 75 | if err != nil { 76 | log.Error("Error fetching %s service endpoints in %s ns. Error: %v", serviceName, namespace, err) 77 | return ret 78 | } 79 | for _, subset := range endpoints.Subsets { 80 | for _, ip := range subset.Addresses { 81 | for _, port := range subset.Ports { 82 | ret = append(ret, Endpoint{IP: ip.IP, Port: port.Port}) 83 | } 84 | } 85 | } 86 | return ret 87 | } 88 | 89 | func getSvcAccountToken() (string, error) { 90 | svcAccount, err := clientset.CoreV1().ServiceAccounts("default").Get(context.TODO(), "default", metav1.GetOptions{}) 91 | if err != nil { 92 | return "", fmt.Errorf("Error fetching default service acccount. Error: %s", err) 93 | } 94 | secret, err := clientset.CoreV1().Secrets("default").Get(context.TODO(), svcAccount.Secrets[0].Name, metav1.GetOptions{}) 95 | if err != nil { 96 | return "", fmt.Errorf("Error fetching secret for service account. Error: %s", err) 97 | } 98 | return string(secret.Data["token"]), nil 99 | } 100 | -------------------------------------------------------------------------------- /k8snetlook/pod.go: -------------------------------------------------------------------------------- 1 | package k8snetlook 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | log "github.com/sarun87/k8snetlook/logutil" 8 | "github.com/vishvananda/netns" 9 | ) 10 | 11 | // RunPodChecks runs checks from within the Pod network namespace 12 | func RunPodChecks() { 13 | // 1. Switch to SrcPod network namespace 14 | // 2. Run checks from within pod network namespace 15 | // 3. Switch back to host network namespace 16 | 17 | log.Debug("----------- Pod Checks -----------") 18 | 19 | // Lock OS thread to prevent ns change 20 | runtime.LockOSThread() 21 | defer runtime.UnlockOSThread() 22 | 23 | // Get current network ns (Should be the host network ns) 24 | hostNsHandle, err := netns.Get() 25 | if err != nil { 26 | fmt.Println("Unable to get handle to current netns", err) 27 | return 28 | } 29 | defer hostNsHandle.Close() 30 | 31 | // Change network ns to SrcPod network ns 32 | if err := netns.Set(Cfg.SrcPod.NsHandle); err != nil { 33 | log.Error("Unable to switch to pod network namespace:%v\n", err) 34 | return 35 | } 36 | 37 | // Execute checks from within the Pod network ns 38 | log.Debug("----> [From SrcPod] Running Kube service IP connectivity check..") 39 | pass, err := RunKubeAPIServiceIPConnectivityCheck() 40 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 41 | Name: "Kube service IP connectivity check", Success: pass, ErrorMsg: err}) 42 | 43 | log.Debug("----> [From SrcPod] Running Kube API Server Endpoint IP connectivity check..") 44 | pass, err = RunKubeAPIEndpointIPConnectivityCheck() 45 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 46 | Name: "Kube API Server Endpoint IP connectivity check", Success: pass, ErrorMsg: err}) 47 | 48 | log.Debug("----> [From SrcPod] Running default gateway connectivity check..") 49 | pass, err = RunGatewayConnectivityCheck() 50 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 51 | Name: "Default gateway connectivity check", Success: pass, ErrorMsg: err}) 52 | 53 | log.Debug("----> [From SrcPod] Running DNS lookup test (kubernetes.default)..") 54 | pass, err = RunK8sDNSLookupCheck(Cfg.KubeDNSService.IP, "kubernetes", "default", 55 | Cfg.KubeAPIService.IP) 56 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 57 | Name: "DNS lookup check for kubernetes.default", Success: pass, ErrorMsg: err}) 58 | 59 | if Cfg.DstPod.IP != "" { 60 | log.Debug("----> [From SrcPod] Running DstPod connectivity check..") 61 | pass, err = RunDstConnectivityCheck(Cfg.DstPod.IP) 62 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 63 | Name: "DstPod connectivity check", Success: pass, ErrorMsg: err}) 64 | 65 | log.Debug("----> [From SrcPod] Running pmtud check for dstIP..") 66 | pass, err = RunMTUProbeToDstIPCheck(Cfg.DstPod.IP) 67 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 68 | Name: "pMTU check for DstIP", Success: pass, ErrorMsg: err}) 69 | } 70 | 71 | if Cfg.ExternalIP != "" { 72 | log.Debug("----> [From SrcPod] Running externalIP connectivity check..") 73 | pass, err = RunDstConnectivityCheck(Cfg.ExternalIP) 74 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 75 | Name: "ExternalIP connectivity check", Success: pass, ErrorMsg: err}) 76 | 77 | log.Debug("----> [From SrcPod] Running pmtud check for externalIP..") 78 | pass, err = RunMTUProbeToDstIPCheck(Cfg.ExternalIP) 79 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 80 | Name: "pMTU check for ExternalIP", Success: pass, ErrorMsg: err}) 81 | } 82 | 83 | if Cfg.DstSvc.ClusterIP.IP != "" { 84 | log.Debug("----> [From SrcPod] Running DstSvc DNS lookup check..") 85 | pass, err = RunK8sDNSLookupCheck(Cfg.KubeDNSService.IP, Cfg.DstSvc.Name, Cfg.DstSvc.Namespace, 86 | Cfg.DstSvc.ClusterIP.IP) 87 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 88 | Name: "DNS lookup for DstSvc", Success: pass, ErrorMsg: err}) 89 | 90 | log.Debug("----> [From SrcPod] Running DstSvc Endpoints connectivity check..") 91 | pass, err = RunDstSvcEndpointsConnectivityCheck(Cfg.DstSvc.SvcEndpoints) 92 | allChecks.PodChecks = append(allChecks.PodChecks, Check{ 93 | Name: "DstSvc Endpoints connectivity check", Success: pass, ErrorMsg: err}) 94 | } 95 | 96 | // Change network ns back to host 97 | netns.Set(hostNsHandle) 98 | } 99 | -------------------------------------------------------------------------------- /k8snetlook/sample_kubeconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | certificate-authority-data: dummy-cadata 5 | server: https://127.0.0.1 6 | name: dummy-cluster 7 | contexts: 8 | - context: 9 | cluster: dummy-cluster 10 | namespace: default 11 | user: dummy-user 12 | name: default 13 | current-context: default 14 | kind: Config 15 | preferences: {} 16 | users: 17 | - name: dummy-user 18 | user: 19 | token: dummy-token -------------------------------------------------------------------------------- /logutil/logutil.go: -------------------------------------------------------------------------------- 1 | package logutil 2 | 3 | // Simple extension to Golang log using pointers 4 | // from here: https://stackoverflow.com/a/29556974 5 | // Keeping logging simple. See https://dave.cheney.net/2015/11/05/lets-talk-about-logging 6 | // for an interesting take on logging systems :) 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "os" 12 | ) 13 | 14 | type MyLogger struct { 15 | *log.Logger 16 | level int // One of DEBUG, ERROR, INFO 17 | } 18 | 19 | const ( 20 | DEBUG = 1 << iota 21 | INFO 22 | ERROR 23 | ) 24 | 25 | var myLog MyLogger 26 | 27 | func Error(format string, v ...interface{}) { 28 | if myLog.level <= ERROR { 29 | s := fmt.Sprintf("ERROR: "+format, v...) 30 | myLog.Output(2, s) 31 | } 32 | } 33 | 34 | func Info(format string, v ...interface{}) { 35 | if myLog.level <= INFO { 36 | s := fmt.Sprintf(format, v...) 37 | myLog.Output(2, s) 38 | } 39 | } 40 | 41 | func Debug(format string, v ...interface{}) { 42 | if myLog.level <= DEBUG { 43 | s := fmt.Sprintf("DEBUG: "+format, v...) 44 | myLog.Output(2, s) 45 | } 46 | } 47 | 48 | func SetLogLevel(lvl int) { 49 | myLog.level = lvl 50 | myLog.Logger = log.New(os.Stdout, "", 0) 51 | } 52 | -------------------------------------------------------------------------------- /netutils/dnsutil.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/miekg/dns" 7 | ) 8 | 9 | // Does not work, not sure why :( 10 | /*func runDNSLookupUsingCustomResolver(dnsFQDN) ([]net.IPAddr, error) { 11 | // Create a custom resolver since /etc/resolv.conf is the host's configuration 12 | // and not the pods config. This is because the below code is executed within 13 | // the Pod netns but not the file system of the pod. 14 | r := &net.Resolver{ 15 | PreferGo: true, 16 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 17 | d := net.Dialer{ 18 | Timeout: time.Millisecond * time.Duration(10000), 19 | } 20 | //return net.Dial(network, net.JoinHostPort(dnsServerIP, "53")) 21 | return d.DialContext(ctx, network, net.JoinHostPort(dnsServerIP, "53")) 22 | }, 23 | } 24 | // Lookup for svcname.svcnamespace eg: kubernetes.default 25 | dnsFQDN := fmt.Sprintf("%s.%s", dstSvcName, dstSvcNamespace) 26 | ips, err := r.LookupIPAddr(context.Background(), dnsFQDN) 27 | if err != nil { 28 | fmt.Printf(" (Failed) dns lookup to %s failed. Error: %v\n", dnsFQDN, err) 29 | return nil, err 30 | } 31 | return ips, nil 32 | } 33 | */ 34 | 35 | // RunDNSLookupUsingCustomResolver sends a dns lookup for hostFQDN 36 | // to DNS server specified by nameserver. 37 | // run dns lookup using github.com/miekg/dns 38 | // code referenced from: https://github.com/bogdanovich/dns_resolver 39 | // nameserver string format: "ip:port" 40 | // hostFQDN string format: "abc.def.ghi." 41 | func RunDNSLookupUsingCustomResolver(nameserver, hostFQDN string) ([]string, error) { 42 | // TODO: Add retries 43 | 44 | result := []string{} 45 | 46 | // Create DNS32 Message with single question 47 | msg := &dns.Msg{ 48 | MsgHdr: dns.MsgHdr{ 49 | Id: dns.Id(), 50 | RecursionDesired: true, 51 | }, 52 | Question: []dns.Question{{Name: dns.Fqdn(hostFQDN), Qtype: dns.TypeA, Qclass: dns.ClassINET}}, 53 | } 54 | 55 | // Send question and add resolved ips to result 56 | res, err := getIPFromDNSQuery(msg, nameserver) 57 | if err != nil { 58 | return nil, err 59 | } 60 | result = append(result, res...) 61 | 62 | // Create DNS64 Message with single question 63 | msg = &dns.Msg{ 64 | MsgHdr: dns.MsgHdr{ 65 | Id: dns.Id(), 66 | RecursionDesired: true, 67 | }, 68 | Question: []dns.Question{{Name: dns.Fqdn(hostFQDN), Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}}, 69 | } 70 | 71 | // Send question and add resolved ips to result 72 | res, err = getIPFromDNSQuery(msg, nameserver) 73 | if err != nil { 74 | return nil, err 75 | } 76 | result = append(result, res...) 77 | 78 | return result, err 79 | } 80 | 81 | func getIPFromDNSQuery(msg *dns.Msg, nameserver string) ([]string, error) { 82 | result := []string{} 83 | // Send question to nameserver and wait for answer 84 | in, err := dns.Exchange(msg, nameserver) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if in != nil && in.Rcode != dns.RcodeSuccess { 90 | // Return error code 91 | return result, errors.New(dns.RcodeToString[in.Rcode]) 92 | } 93 | 94 | // Fetch IP Addresses in DNS Answer and return 95 | for _, record := range in.Answer { 96 | if t, ok := record.(*dns.A); ok { 97 | result = append(result, t.A.String()) 98 | } 99 | if t, ok := record.(*dns.AAAA); ok { 100 | result = append(result, t.AAAA.String()) 101 | } 102 | } 103 | return result, err 104 | } 105 | -------------------------------------------------------------------------------- /netutils/dnsutil_test.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDNSLookupGoogle(t *testing.T) { 8 | res, err := RunDNSLookupUsingCustomResolver("8.8.8.8:53", "www.google.com") 9 | if err != nil { 10 | t.Errorf("Unable to resolve Google using Google DNS! Error:%v", err) 11 | } 12 | t.Logf("Resolved IPs:%v\n", res) 13 | // Should have an IPv4 and an IPv6 address for Google 14 | if len(res) < 2 { 15 | t.Errorf("DNS resolution for www.google.com into ipv4 and ipv6 addresses failed with Google DNS") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /netutils/httputil.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | // SendRecvHTTPMessage sends out a HTTP GET request to the url specified 12 | // add token to X-Auth-Token as a Bearer token if token is specified 13 | // Return body from GET response as part of body *[]byte 14 | func SendRecvHTTPMessage(url string, token string, body *[]byte) (int, error) { 15 | // Transport Layer settings 16 | tr := &http.Transport{ 17 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 18 | } 19 | // Create HTTP Client 20 | client := &http.Client{Transport: tr, Timeout: time.Duration(5) * time.Second} 21 | // Create GET request 22 | req, err := http.NewRequest("GET", url, nil) 23 | // Add Authorization header if token specified 24 | if token != "" { 25 | req.Header.Add("Authorization", "Bearer "+token) 26 | } 27 | // Send request 28 | res, err := client.Do(req) 29 | if err != nil { 30 | return -1, fmt.Errorf("HTTP request to %s failed: %v", url, err) 31 | } 32 | // return body of GET response 33 | *body, _ = ioutil.ReadAll(res.Body) 34 | res.Body.Close() 35 | return res.StatusCode, nil 36 | } 37 | -------------------------------------------------------------------------------- /netutils/httputil_test.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | "testing" 9 | ) 10 | 11 | func skipTest(t *testing.T) { 12 | if os.Getenv("TESTIPV6") == "" { 13 | t.Skip("Skipping testing in CI environment") 14 | } 15 | } 16 | 17 | func TestSendRecvHTTPMessageV4(t *testing.T) { 18 | var body []byte 19 | // Use Google IPv4 Address 20 | url := "http://172.217.164.100:80" 21 | responseCode, err := SendRecvHTTPMessage(url, "", &body) 22 | if err != nil { 23 | t.Errorf("Unable to fetch response Error: %v", err) 24 | } 25 | if responseCode != http.StatusOK { 26 | t.Errorf("Response code not 200") 27 | } 28 | t.Logf("%s", body) 29 | } 30 | 31 | func TestSendRecvHTTPMessageV6(t *testing.T) { 32 | // Skip when run through make test 33 | skipTest(t) 34 | var body []byte 35 | // TODO: Use localtest server for unit testing 36 | // Use www.google.com IPV6 address 37 | url := fmt.Sprintf("http://%s", net.JoinHostPort("2607:f8b0:4005:804::2004", "80")) 38 | responseCode, err := SendRecvHTTPMessage(url, "", &body) 39 | if err != nil { 40 | t.Errorf("Unable to fetch response Error: %v", err) 41 | } 42 | if responseCode != http.StatusNotFound { 43 | t.Errorf("Expected 404. Got:%v", responseCode) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /netutils/icmputil.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "golang.org/x/net/ipv6" 11 | 12 | "github.com/google/gopacket" 13 | "github.com/google/gopacket/layers" 14 | log "github.com/sarun87/k8snetlook/logutil" 15 | uuid "github.com/satori/go.uuid" 16 | "golang.org/x/net/icmp" 17 | "golang.org/x/net/ipv4" 18 | ) 19 | 20 | const ( 21 | icmpTimeout = 4 22 | icmpMessagePrefix = "K8SNETLOOK" 23 | defaultPayloadSize = 84 24 | icmpIDRandMin = 5000 25 | icmpIDRandMax = 32000 26 | maxCountICMPReply = 10 27 | ) 28 | 29 | // SendRecvICMPMessage checks if icmp ping is successful. 30 | // Looks at 2 icmp packets for required response 31 | // returncode: 0 - no error. Echo reply received successfully 32 | // 1 - Fragmentation required 33 | // 2 - got icmp but unknwon type 34 | func SendRecvICMPMessage(dstIP string, payloadSize int, dontFragment bool) (int, error) { 35 | // Note: Does not handle IPv4 literal in IPv6. TODO later 36 | ip := net.ParseIP(dstIP) 37 | if ip.To4() != nil { 38 | // IPv4 39 | return sendRecvICMPMessageV4(dstIP, payloadSize, dontFragment) 40 | } 41 | // IPv6 42 | return sendRecvICMPMessageV6(dstIP, payloadSize) 43 | 44 | } 45 | 46 | // sendRecvICMPMessageV4 checks if icmp ping is successful. 47 | // Looks at 2 icmp packets for required response 48 | // returncode: 0 - no error. Echo reply received successfully 49 | // 1 - Fragmentation required 50 | // 2 - got icmp but unknwon type 51 | func sendRecvICMPMessageV4(dstIP string, payloadSize int, dontFragment bool) (int, error) { 52 | // If an additional payload size isn't specified, use default 53 | if payloadSize < defaultPayloadSize { 54 | payloadSize = defaultPayloadSize 55 | } 56 | // Listen for ICMP reply on all IPs 57 | c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") 58 | if err != nil { 59 | return -1, fmt.Errorf("Unable to open icmp socket for ping test: %v", err) 60 | } 61 | defer c.Close() 62 | 63 | icmpMessageBody := fmt.Sprintf("%s-%s", icmpMessagePrefix, uuid.NewV4()) 64 | // Send ICMP message 65 | if err := sendICMPMessageV4(dstIP, payloadSize, dontFragment, icmpMessageBody); err != nil { 66 | return -1, err 67 | } 68 | 69 | rb := make([]byte, payloadSize) 70 | c.SetReadDeadline(time.Now().Add(time.Second * icmpTimeout)) 71 | 72 | // Read maxCountICMPReply packets 73 | for tries := 0; tries < maxCountICMPReply; tries++ { 74 | n, _, err := c.ReadFrom(rb) 75 | if err != nil { 76 | if err.(net.Error).Timeout() { 77 | return -1, fmt.Errorf("ICMP timeout") 78 | } 79 | return -1, fmt.Errorf("Unable to read reply from icmp socket: %v", err) 80 | } 81 | // Check if read message is an ICMP message 82 | rm, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), rb[:n]) 83 | if err != nil { 84 | return -1, fmt.Errorf("Unable to parse ICMP message:%v", err) 85 | } 86 | // Check to see if ICMP message type is ECHO reply 87 | switch rm.Type { 88 | case ipv4.ICMPTypeEchoReply: 89 | // Reflection received successfully. Return success 90 | // log.Debug(" got reflection from %v with payload size:%d\n", peer, payloadSize) 91 | // To check if echo reply is specific to this app, check message 92 | b, _ := rm.Body.Marshal(1) // 1 : ICMPv4 type protocol number 93 | if strings.Contains(string(b), icmpMessageBody) { 94 | // k8snetlook reply received 95 | return 0, nil 96 | } 97 | log.Debug(" got echo reply but not for k8snellook packet. Continuing to read more icmp reply packets") 98 | case ipv4.ICMPTypeDestinationUnreachable: 99 | if rm.Code == layers.ICMPv4CodeFragmentationNeeded { 100 | // log.Debug(" Fragmentation required, and DF flag set\n") 101 | return 1, nil 102 | } 103 | default: 104 | //log.Debug(" got %+v; want echo reply\n", rm) 105 | } 106 | } 107 | // Got ICMP type but not an echo reply 108 | return 2, nil 109 | } 110 | 111 | // sendICMPMessage sends a single ICMP packet over the wire 112 | // Picked from https://github.com/ipsecdiagtool/ipsecdiagtool project & modified as necessary 113 | func sendICMPMessageV4(dstIP string, payloadSize int, dontfragment bool, icmpMessageBody string) error { 114 | // IP Layer 115 | ip := layers.IPv4{ 116 | SrcIP: net.ParseIP("0.0.0.0"), 117 | DstIP: net.ParseIP(dstIP), 118 | Version: 4, 119 | TTL: 64, 120 | Protocol: layers.IPProtocolICMPv4, 121 | } 122 | // ICMP Layer 123 | icmp := layers.ICMPv4{ 124 | TypeCode: layers.CreateICMPv4TypeCode(uint8(ipv4.ICMPTypeEcho), 0), 125 | } 126 | opts := gopacket.SerializeOptions{ 127 | FixLengths: true, 128 | ComputeChecksums: true, 129 | } 130 | ipHeaderBuf := gopacket.NewSerializeBuffer() 131 | if err := ip.SerializeTo(ipHeaderBuf, opts); err != nil { 132 | return err 133 | } 134 | ipHeader, err := ipv4.ParseHeader(ipHeaderBuf.Bytes()) 135 | if err != nil { 136 | return err 137 | } 138 | // If dontfragment = True, set the bit in IP Header 139 | if dontfragment { 140 | ipHeader.Flags |= ipv4.DontFragment 141 | } 142 | payloadBuf := gopacket.NewSerializeBuffer() 143 | //Influence the payload size 144 | payloadbytes := []byte(icmpMessageBody) 145 | if payloadSize > len(icmpMessageBody) { 146 | padding := make([]byte, payloadSize-len(icmpMessageBody)) 147 | payloadbytes = append(payloadbytes, padding...) 148 | } 149 | payload := gopacket.Payload(payloadbytes) 150 | if err := gopacket.SerializeLayers(payloadBuf, opts, &icmp, payload); err != nil { 151 | return err 152 | } 153 | //Send packet 154 | packetConn, err := net.ListenPacket("ip4:icmp", "0.0.0.0") 155 | if err != nil { 156 | return err 157 | } 158 | defer packetConn.Close() 159 | rawConn, err := ipv4.NewRawConn(packetConn) 160 | if err != nil { 161 | return err 162 | } 163 | defer rawConn.Close() 164 | return rawConn.WriteTo(ipHeader, payloadBuf.Bytes(), nil) 165 | } 166 | 167 | // sendRecvICMPMessageV6 checks if icmp ping is successful. 168 | // Looks at 2 icmp packets for required response 169 | // returncode: 0 - no error. Echo reply received successfully 170 | // 1 - Fragmentation required 171 | // 2 - got icmp but unknwon type 172 | func sendRecvICMPMessageV6(dstIP string, payloadSize int) (int, error) { 173 | // Don't fragment is always set for IPv6. IPv6 packets cannot be fragmented 174 | // Listen for ICMP reply on all IPs 175 | c, err := icmp.ListenPacket("ip6:ipv6-icmp", "::") 176 | if err != nil { 177 | return -1, fmt.Errorf("Unable to open icmp socket for ping test: %v", err) 178 | } 179 | defer c.Close() 180 | 181 | // If an additional payload size isn't specified, use default 182 | if payloadSize < defaultPayloadSize { 183 | payloadSize = defaultPayloadSize 184 | } 185 | icmpMessageBody := fmt.Sprintf("%s-%s", icmpMessagePrefix, uuid.NewV4()) 186 | payloadbytes := []byte(icmpMessageBody) 187 | if payloadSize > len(icmpMessageBody) { 188 | // id: 4 bytes, seq: 4 bytes, totaling 8 bytes 189 | padding := make([]byte, payloadSize-len(icmpMessageBody)-8) 190 | payloadbytes = append(payloadbytes, padding...) 191 | } 192 | icmpID := (rand.Intn(icmpIDRandMax-icmpIDRandMin) + icmpIDRandMin) & 0xffff 193 | icmpSeq := 1 194 | wm := icmp.Message{ 195 | Type: ipv6.ICMPTypeEchoRequest, Code: 0, 196 | Body: &icmp.Echo{ 197 | ID: icmpID, 198 | Seq: icmpSeq, 199 | Data: payloadbytes, 200 | }, 201 | } 202 | wb, err := wm.Marshal(nil) 203 | if err != nil { 204 | return -1, fmt.Errorf("Unable to convert icmp echo message to byte string: %v", err) 205 | } 206 | if _, err := c.WriteTo(wb, &net.IPAddr{IP: net.ParseIP(dstIP)}); err != nil { 207 | return -1, fmt.Errorf("Unable to send icmp echo request to %s:%v", dstIP, err) 208 | } 209 | 210 | rb := make([]byte, payloadSize) 211 | c.SetReadDeadline(time.Now().Add(time.Second * icmpTimeout)) 212 | 213 | // Read maxCountICMPReply packets 214 | for tries := 0; tries < maxCountICMPReply; tries++ { 215 | n, _, err := c.ReadFrom(rb) 216 | if err != nil { 217 | if err.(net.Error).Timeout() { 218 | return -1, fmt.Errorf("ICMP timeout") 219 | } 220 | return -1, fmt.Errorf("Unable to read reply from icmp socket: %v", err) 221 | } 222 | // Check if read message is an ICMP message 223 | rm, err := icmp.ParseMessage(ipv6.ICMPTypeEchoReply.Protocol(), rb[:n]) 224 | if err != nil { 225 | return -1, fmt.Errorf("Unable to parse ICMP message:%v", err) 226 | } 227 | // Check to see if ICMP message type is ECHO reply 228 | switch rm.Type { 229 | case ipv6.ICMPTypeEchoReply: 230 | // Reflection received successfully. Return success 231 | // log.Debug(" got reflection from %v with payload size:%d\n", peer, payloadSize) 232 | // To check if echo reply is specific to this app, check message 233 | b, _ := rm.Body.Marshal(58) // 58 : ICMPv6 type protocol number 234 | if strings.Contains(string(b), icmpMessageBody) { 235 | // k8snetlook reply received 236 | return 0, nil 237 | } 238 | log.Debug(" got echo reply but not for k8snellook packet. Continuing to read more icmp reply packets") 239 | case ipv6.ICMPTypePacketTooBig: 240 | // log.Debug(" Fragmentation required, and DF flag set\n") 241 | return 1, nil 242 | default: 243 | log.Debug(" got %+v; want echo reply\n", rm) 244 | } 245 | } 246 | // Got ICMP type but not an echo reply 247 | return 2, nil 248 | } 249 | -------------------------------------------------------------------------------- /netutils/icmputil_test.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSendRcvICMPMessageSuccess(t *testing.T) { 8 | ret, err := SendRecvICMPMessage("127.0.0.1", 64, true) 9 | if err != nil { 10 | t.Errorf("ICMP reply expected from localhost. Received error: %s", err) 11 | return 12 | } 13 | if ret != 0 { 14 | t.Errorf("Expected (0, nil) but received (%d, nil).", ret) 15 | } 16 | } 17 | 18 | func TestSendRcvICMPMessageFailure(t *testing.T) { 19 | // Using arbitary IP for failure test 20 | _, err := SendRecvICMPMessage("192.192.192.192", 64, true) 21 | if err == nil { 22 | t.Errorf("Expected ICMP to arbitary IP to fail with a timeout") 23 | } 24 | if err.Error() != "ICMP timeout" { 25 | t.Errorf("Received a different error than expected. Received: %v", err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /netutils/netutils.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/sys/unix" 7 | 8 | log "github.com/sarun87/k8snetlook/logutil" 9 | "github.com/vishvananda/netlink" 10 | ) 11 | 12 | // GetHostGatewayIP returns the IP of the default gw as listed in the route list 13 | func GetHostGatewayIP() (string, error) { 14 | gwIP, err := getHostGatewayIPUsingFamily(unix.AF_INET) 15 | if err != nil { 16 | log.Debug("Error: %v", err) 17 | // If we are here, there was a problem returning list v4 routes. Try v6 routes 18 | gwIP, err = getHostGatewayIPUsingFamily(unix.AF_INET6) 19 | if err != nil { 20 | // v6 gw route not found either. Return nil 21 | return "", err 22 | } 23 | } 24 | return gwIP, nil 25 | } 26 | 27 | func getHostGatewayIPUsingFamily(family int) (string, error) { 28 | routes, err := netlink.RouteList(nil, family) 29 | if err != nil { 30 | return "", err 31 | } 32 | for _, r := range routes { 33 | // Check if the route is marked as a gw route 34 | if r.Gw != nil && r.Dst == nil { 35 | return r.Gw.String(), nil 36 | } 37 | } 38 | return "", fmt.Errorf("unable to find a route with default gw") 39 | } 40 | -------------------------------------------------------------------------------- /netutils/pmtuprobe.go: -------------------------------------------------------------------------------- 1 | package netutils 2 | 3 | import ( 4 | "net" 5 | 6 | log "github.com/sarun87/k8snetlook/logutil" 7 | ) 8 | 9 | const ( 10 | ipHeaderSize = 20 11 | icmpHeaderSize = 8 12 | maxMTUSize = 9000 13 | ) 14 | 15 | // PMTUProbeToDestIP runs ICMP pings to destination with varying payload size 16 | // and returns the highest MTU that works. Currently works for IPv4 only 17 | func PMTUProbeToDestIP(dstIP string) (int, error) { 18 | var maxOkMTU int 19 | minPayloadSize, maxPayloadSize := (icmpHeaderSize + ipHeaderSize), (maxMTUSize - icmpHeaderSize - ipHeaderSize) 20 | 21 | res, err := SendRecvICMPMessage(dstIP, minPayloadSize, true) 22 | if err != nil || res == 1 { 23 | return -1, err 24 | } 25 | maxOkMTU = minPayloadSize 26 | // Use binary search to check for working mtu 27 | for minPayloadSize <= maxPayloadSize { 28 | midPayloadSize := (minPayloadSize + maxPayloadSize) / 2 29 | log.Debug("Trying with mtu size:%d\n", midPayloadSize) 30 | ret, err := SendRecvICMPMessage(dstIP, midPayloadSize, true) 31 | if err != nil { 32 | //fmt.Println("Received error:", err) 33 | if e, ok := err.(*net.OpError); ok { 34 | // Check if send failed due to Message too long (i.e. paylod > src if mtu) 35 | if e.Err.Error() == "sendmsg: message too long" { 36 | // log.Debug("WARN: Cannot send packet size larger than iface MTU") 37 | // Go lower 38 | maxPayloadSize = midPayloadSize - 1 39 | continue 40 | } 41 | } else { 42 | // Some other error. Not handling this as part of mtu probing 43 | return -1, err 44 | } 45 | } 46 | if ret == 1 { 47 | // if return code is 1, icmp reply had fragmentation required. So go loweer 48 | maxPayloadSize = midPayloadSize - 1 49 | } else { 50 | // successful icmp response. Go higher 51 | log.Debug(" got reflection from %s with payload: %d\n", dstIP, midPayloadSize) 52 | minPayloadSize = midPayloadSize + 1 53 | maxOkMTU = midPayloadSize 54 | } 55 | } 56 | return maxOkMTU + ipHeaderSize + icmpHeaderSize, nil 57 | } 58 | --------------------------------------------------------------------------------