├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── .travis.yml ├── LICENSE-BSD-Cloudflare ├── Makefile ├── README.md ├── addr.go ├── addr_test.go ├── ext └── ext.go ├── fwd.go ├── go.mod ├── go.sum ├── kacon.go ├── local_routes.go ├── main.go ├── main_test.go ├── metrics.go ├── net.go ├── netns_utils.go ├── pp.go ├── proxy.go ├── routing.go ├── stack.go ├── test-gvisor.sh ├── tests ├── __init__.py ├── base.py ├── cover.py ├── mockdns │ └── mockdns.go ├── mocktcpecho │ └── mocktcpecho.go ├── mockudpecho │ └── mockudpecho.go ├── runner.py ├── test_basic.py └── utils.py ├── udp.go └── unconn └── unconn.go /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cover 2 | .GOPATH 3 | bin/* 4 | *.pyc 5 | 6 | *~ 7 | *.orig 8 | *.deb 9 | \#*# 10 | 11 | # Tooling 12 | /cscope.* 13 | 14 | # Intermediate build objects 15 | *.bc 16 | *.o 17 | *.s 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.15.x 4 | before_install: 5 | - go get -u github.com/mattn/goveralls 6 | script: 7 | - export GO111MODULE=on 8 | - make 9 | - make cover 10 | - goveralls -coverprofile=.cover/all.merged -service=travis-ci 11 | env: 12 | global: 13 | - secure: cc1Lquck6/m3CVkHkDoKXaSlG7aajWFv/U55b+6qOlTpaXj2Ux9moQr+49SYgjXBY6gQjGN3ZBOEFsoJYtnAe3VlpBTyxu+MXsocWdPZBDkU5U7hgcS1PXA0jwksFJ8LTFau/LeseizYPubeYRwKauVDvjQ45CjegbC/A6+Pty8Ecsdr2kK5qXg99l9u47nbluhGfH7skYSUdMO2RHsr2Cc1KwpGbx9HICDwyZi2Otg0dbH3jwaUPYNuebiNOBSsHFRSGIGPtsXu4KAWW8l0L6nnTB+R2V7Z9NSOY/0hQS+ISEvp48EHQ66qEa0Fb15pJnyx6ARJtFMKdxkelDUncbCDHSX6FJoOucmeggWdS+I7S/QimYes61hh5cO4iV4/XsnUqci9iVsbV4PwlJhJAFaBd3Jv/1D9ri/ljKHUePDCG9RVLZcnKJxEPUdht4VJ8CmWT8NxuVDZ/HJf3krWq90MJRYB8F4TT1bQpubWf7XGRG829PDkOL07iNhRTvBToLL7T+LEnUfHDZbl71TsnrxboqIzmZxDN+gX82WY8gmasrronZ/nYcGDFTYpFB6PooCmsmlc3hGNljQl0ADIecwbuTcxPJmVpB2II8RwStPtAMufFPfKUvXfS7xjuMINMMpPcSYLiH6sVXHh6G9E+8/hDmPXDNjyeXEMWc/uvSE= 14 | -------------------------------------------------------------------------------- /LICENSE-BSD-Cloudflare: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 CloudFlare, Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the CloudFlare, Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export GOPRIVATE := code.cfops.it 2 | IMPORT_PATH := github.com/cloudflare/slirpnetstack 3 | 4 | VERSION := $(shell git describe --tags --always --dirty="-dev") 5 | DATE := $(shell date -u '+%Y-%m-%d-%H:%MUTC') 6 | GOFLAGS := -ldflags='-compressdwarf=false -X "$(IMPORT_PATH)/ext.Version=$(VERSION)" -X "$(IMPORT_PATH)/ext.BuildTime=$(DATE)"' 7 | 8 | bin/slirpnetstack: *.go go.mod 9 | go build \ 10 | $(GOFLAGS) \ 11 | -o $@ \ 12 | $(IMPORT_PATH) 13 | 14 | 15 | bin/slirpnetstack.cover: *.go go.mod 16 | go test \ 17 | $(GOFLAGS) \ 18 | -coverpkg="$(IMPORT_PATH)" \ 19 | -c \ 20 | -o $@ \ 21 | -tags testrunmain \ 22 | $(IMPORT_PATH) 23 | 24 | bin/gocovmerge: 25 | go build -o $@ github.com/wadey/gocovmerge 26 | 27 | .PHONY: format 28 | format: 29 | go fmt *go 30 | 31 | ifdef COVER 32 | PWD:=$(CURDIR) 33 | SLIRPNETSTACKDEP:=./bin/slirpnetstack.cover 34 | SLIRPNETSTACKBIN:= "./bin/slirpnetstack.cover -test.coverprofile=$(PWD)/.cover/%(nr)s.out" 35 | else 36 | SLIRPNETSTACKDEP:=./bin/slirpnetstack 37 | SLIRPNETSTACKBIN:= "./bin/slirpnetstack" 38 | endif 39 | 40 | .PHONY: test 41 | test: $(SLIRPNETSTACKDEP) 42 | go test ./... 43 | SLIRPNETSTACKBIN=$(SLIRPNETSTACKBIN) \ 44 | PYTHONPATH=. \ 45 | PYTHONIOENCODING=utf-8 \ 46 | unshare -Ur python3 -m tests.runner tests 47 | 48 | cover: bin/gocovmerge 49 | @-mkdir -p .cover 50 | @rm -f .cover/*.out .cover/all.merged 51 | @$(MAKE) test COVER=1 52 | @./bin/gocovmerge .cover/*.out > .cover/all.merged 53 | @echo "[*] Total test coverage:" 54 | @./tests/cover.py .cover/all.merged 55 | ifdef CI 56 | go tool cover -html .cover/all.merged -o .cover/all.html 57 | endif 58 | ifdef HTML 59 | go tool cover -html .cover/all.merged 60 | endif 61 | 62 | clean: 63 | rm -f bin/* .cover/*out 64 | 65 | GOTESTTARGETS = \ 66 | bin/mocktcpecho \ 67 | bin/mockudpecho \ 68 | bin/mockdns 69 | 70 | test: $(GOTESTTARGETS) 71 | $(GOTESTTARGETS): $(wildcard tests/*/*.go) 72 | go build \ 73 | -o $@ \ 74 | $(IMPORT_PATH)/tests/$(subst bin/,,$@) 75 | 76 | update-gomod: 77 | # Use something like that if you want to pin to specific commit: 78 | # go get -u gvisor.dev/gvisor@2f6429be86f927058392a85dcb6512bebb836d9d 79 | # otherwise this fetches tip 80 | go get -u gvisor.dev/gvisor@go all 81 | go mod tidy 82 | $(MAKE) bin/gocovmerge bin/slirpnetstack bin/slirpnetstack.cover 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/majek/slirpnetstack.svg?branch=master)](https://travis-ci.org/majek/slirpnetstack) [![Coverage Status](https://coveralls.io/repos/github/majek/slirpnetstack/badge.svg?branch=master)](https://coveralls.io/github/majek/slirpnetstack?branch=master) 2 | 3 | slirpnetstack 4 | ============= 5 | 6 | User-mode networking for unprivileged network namespaces. 7 | 8 | 9 | First take a look at slirp4netns project: 10 | 11 | - https://github.com/rootless-containers/slirp4netns 12 | 13 | 14 | The general idea of this code is to: 15 | 16 | - Acquire a handle to a network namespace used by someone else, like 17 | a container. 18 | - Open a tun/tap device living there. 19 | - Receive/send L3 packets from/to that namespace processes. 20 | 21 | The magic happens with these L3 packets - slirpnetstack uses a 22 | user-space (unprivileged) network stack. It is able to terminate 23 | network connections and translate them into syscalls. 24 | 25 | Therefore, a received SYN from guest network namespace becomes 26 | connect() in the host kernel namespace. L3 UDP packet becomes 27 | sendto(), and so on. 28 | 29 | slirpnetstack can do three things: 30 | 31 | - It can "route" connections exiting guest namespace, to internet 32 | provided by host namespace. This is useful to give internet access 33 | to guest. Classical SLIRP use case. 34 | 35 | - It can "local forward" connections. It will bind to host ip/port, 36 | and open a new connection directing it to guest. This is useful to 37 | expose services living inside guest to outside world. 38 | 39 | - It can "remote forward" connections. It will bind to guest ip/port, 40 | and open new host connection for each new one in guest. This is 41 | useful to expose host services to guest. 42 | 43 | Why not libslirp? 44 | ---------------- 45 | 46 | Libslirp is a good and mature piece of software, used by many. Sadly, 47 | it suffers occasional security problems: 48 | 49 | - https://github.com/rootless-containers/slirp4netns/security/advisories 50 | 51 | Furthermore it is based on ancient networking stack, so adding modern 52 | features like IPv6 or window scaling is difficult. There has been many 53 | user-space networking stacks created recently, but neither seem to be 54 | fitting the slirp use case. 55 | 56 | Non-features of slirpnetstack 57 | ----------------------------- 58 | 59 | Broken things: 60 | 61 | - ping - icmp echo request - are terminated locally. 62 | - udp requires connection tracking, therefore is hard to do in 63 | general. Timeouts are arbitrary. 64 | - gvisor/netstack has some implementation issues, so of course this 65 | project inhertits them. 66 | 67 | Networking topology 68 | ------------------- 69 | 70 | Slirpnetstack assumes that the namespace will have the following IP's: 71 | 72 | - `10.0.2.100/24` for IPv4 73 | - `[fd00::100]/64` for IPv6 74 | 75 | And that the guest will use the following IP's as default routes: 76 | 77 | - `10.0.2.2` for IPv4 78 | - `[fd00::2]` for IPv6 79 | 80 | In other words, Slirpnetstack listens on these IP's and will handle 81 | traffic routed to it. 82 | 83 | 84 | Usage with custom network namespace 85 | ----------------------------------- 86 | 87 | Before you start, you need a guest network namespace handle. This is 88 | usually a /proc//ns/net path. You can create such a namespace 89 | with this command - notice, it doesn't require root: 90 | 91 | marek@:~$ unshare -Urn 92 | root@:~# 93 | 94 | This will give you full permissions to do stuff inside a net 95 | namespace. Now you must configure it: 96 | 97 | ``` 98 | ip link set lo up 99 | ip tuntap add mode tap name tun0 100 | ip link set tun0 mtu 65521 101 | ip link set tun0 up 102 | ip addr add 10.0.2.100/24 dev tun0 103 | ip addr add fd00::100/64 dev tun0 104 | ip route add 0.0.0.0/0 via 10.0.2.2 dev tun0 105 | ip route add ::/0 via fd00::2 dev tun0 106 | ``` 107 | 108 | Finally, you need a pid of the process having this namespace. Easiest 109 | is to type "echo $$", like: 110 | 111 | root@:~# echo $$ 112 | 31530 113 | 114 | Alternatively you can use "lsns" from host: 115 | 116 | marek@:~$ lsns -t net | grep -- -bash 117 | 4026533150 net 2 31530 marek -bash 118 | 119 | Now you can run slirpnetstack: 120 | 121 | sudo ./bin/slirpnetstack -interface tun0 -netns /proc/31530/ns/net 122 | 123 | 124 | Usage with gvisor 125 | ----------------- 126 | 127 | Perhaps a more powerful way is to see slirpnetstack in action with 128 | gvisor. To avoid docker magic we can use OCI gvisor interface. See the 129 | script: 130 | 131 | - https://github.com/cloudflare/slirpnetstack/blob/master/test-gvisor.sh 132 | 133 | Once you run it, you will see: 134 | 135 | ``` 136 | marek@$ sudo bash test-gvisor.sh 137 | [*] Starting gvisor 138 | To enter the container run: 139 | runsc --net-raw exec --console-socket /tmp/pty.sock hello bash 140 | [*] Running slirpnetstack 141 | [.] Joininig netns /proc/8519/ns/net 142 | [.] Opening tun interface tun0 143 | [.] Restoring root netns 144 | [+] #8578 Started 145 | ``` 146 | 147 | From now on you should have internet connectivity in the isolated 148 | gvisor container, supplied by slirpnetstack. 149 | 150 | 151 | Routing security 152 | ---------------- 153 | 154 | By default the guest is totally locked. No traffic from the guest can 155 | exit to the host unless allowed explicitly with local forwarding rules 156 | or --allow statements. 157 | 158 | There are two options --deny --allow that can override the more 159 | generic firewall settings for specific IP prefixes and port 160 | ranges. For example, to allow some connectivity: 161 | 162 | --allow=udp://192.168.1.0/24:53-53 163 | 164 | This would allow connectivity to any IP in the given 192.168.1.0/24 network 165 | prefix and in the port range of 53-53 ports (one port in this case) 166 | over protocol UDP. `--deny` takes precedence over `--allow`. 167 | 168 | There are three toggles: 169 | 170 | --enable-routing allows routing to non-local IP's. Connectivity to the 171 | peers outside the host machine will work, but IP ranges that are on 172 | the host or attached to any of it's network interfaces will be 173 | blocked. This is to avoid connections to 192.168.0.0/24 style ranges 174 | if they are in use on the host. 175 | 176 | --enable-host allows routing to host-bound IP ranges like the 177 | 192.168.0.0/24 shown above. The code scrapes the local interfaces and 178 | builds the list every 30 seconds. This option is considered insecure 179 | and will allow guest to connect to resources on host. 180 | 181 | Even in such case, for sanity we block traffic to the following IP 182 | prefixes: 183 | 184 | - 0.0.0.0/8 185 | - 10.0.2.0/24 186 | - 127.0.0.0/8 187 | - 255.255.255.255/32 188 | - ::/128 189 | - ::1/128 190 | - ::/96 191 | - ::ffff:0:0:0/96 192 | - 64:ff9b::/96 193 | 194 | 195 | Development 196 | ----------- 197 | 198 | You can run tests with: 199 | 200 | make tests 201 | 202 | Or tests with code coverage 203 | 204 | make cover 205 | 206 | You can get HTML report with 207 | 208 | make cover HTML=1 209 | 210 | Finally, to run standalone test: 211 | 212 | SLIRPNETSTACKBIN=./bin/slirpnetstack \ 213 | unshare -Ur \ 214 | python3 -m unittest tests.test_basic.RoutingTestSecurity.test_remote_srv 215 | 216 | 217 | Updating netstack/gvisor 218 | ------------------------ 219 | 220 | To update netstack dependency try running: 221 | 222 | make update-gomod 223 | 224 | This sometimes works, but when it fails it's messy. In such case, 225 | clone `gvisor` repo, check out `origin/go` branch to get the 226 | golang-consumable code and point to it by adding this line to 227 | `go.mod`. This is useful in bisecting netstack issues: 228 | 229 | replace gvisor.dev/gvisor => ../../src/gvisor 230 | -------------------------------------------------------------------------------- /addr.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "gvisor.dev/gvisor/pkg/tcpip" 11 | ) 12 | 13 | /* SSH supports this syntax: 14 | -R ... connections to given TCP port ... on the remote host are to be forwarded to the local side... 15 | -L ... on the local host are to be forwarded to the given port... on the remote side... 16 | 17 | -L [bind_address:]port:host:hostport 18 | -R [bind_address:]port:host:hostport 19 | -R [bind_address:]port 20 | 21 | -L [bind_address:]port:remote_socket 22 | -L local_socket:remote_socket 23 | -L local_socket:host:hostport 24 | -R [bind_address:]port:local_socket 25 | -R remote_socket:host:hostport 26 | -R remote_socket:local_socket 27 | 28 | */ 29 | type FwdAddr struct { 30 | network string 31 | bind defAddress 32 | host defAddress 33 | kaEnable bool 34 | kaInterval time.Duration 35 | proxyProtocol bool 36 | } 37 | 38 | type FwdAddrSlice []FwdAddr 39 | 40 | func (f *FwdAddrSlice) String() string { 41 | s := make([]string, 0, 10) 42 | for _, fa := range *f { 43 | x := fmt.Sprintf("%s://%s-%s", 44 | fa.network, 45 | fa.bind.String(), 46 | fa.host.String()) 47 | s = append(s, x) 48 | } 49 | return strings.Join(s, " ") 50 | } 51 | 52 | func (f *FwdAddrSlice) Set(value string) error { 53 | var ( 54 | bindPort, bindIP string 55 | network string 56 | rest string 57 | hostIP, hostPort string 58 | ) 59 | 60 | p := strings.SplitN(value, "://", 2) 61 | switch len(p) { 62 | case 2: 63 | network = p[0] 64 | rest = p[1] 65 | case 1: 66 | network = "tcp" 67 | rest = p[0] 68 | } 69 | 70 | var fwa FwdAddr 71 | switch network { 72 | case "udprpc": 73 | fwa.network = "udp" 74 | fwa.kaEnable = true 75 | fwa.kaInterval = 0 76 | case "udpspp": 77 | fwa.network = "udp" 78 | fwa.proxyProtocol = true 79 | case "tcppp": 80 | fwa.network = "tcp" 81 | fwa.proxyProtocol = true 82 | case "tcp", "udp": 83 | fwa.network = network 84 | default: 85 | return fmt.Errorf("unknown network type %q", network) 86 | } 87 | 88 | p = SplitHostPort(rest) 89 | switch len(p) { 90 | case 1: 91 | bindPort = p[0] 92 | case 2: 93 | bindIP = p[0] 94 | bindPort = p[1] 95 | case 3: 96 | bindPort = p[0] 97 | hostIP = p[1] 98 | hostPort = p[2] 99 | case 4: 100 | bindIP = p[0] 101 | bindPort = p[1] 102 | hostIP = p[2] 103 | hostPort = p[3] 104 | } 105 | 106 | if bindIP != "" || bindPort != "" { 107 | bind, err := ParseDefAddress(bindIP, bindPort) 108 | if err != nil { 109 | return err 110 | } 111 | fwa.bind = *bind 112 | } 113 | 114 | if bindPort != "" && hostPort == "" { 115 | // in case only bindPort is set, and not hostPort, set the default: 116 | hostPort = bindPort 117 | } 118 | 119 | if hostIP != "" || hostPort != "" { 120 | host, err := ParseDefAddress(hostIP, hostPort) 121 | if err != nil { 122 | return err 123 | } 124 | fwa.host = *host 125 | } 126 | 127 | *f = append(*f, fwa) 128 | return nil 129 | } 130 | 131 | // SetDefaultAddrs populates any unset endpoint with the provided default value. 132 | // IPv6 values are only used if the one of the existing addresses is IPv6. 133 | func (f *FwdAddrSlice) SetDefaultAddrs(bindAddrDef net.IP, bindAddr6Def net.IP, hostAddrDef net.IP, hostAddr6Def net.IP) { 134 | for i := range *f { 135 | fa := &(*f)[i] 136 | if (fa.bind.static.Addr != "" && fa.bind.static.Addr.To4() == "") || 137 | (fa.host.static.Addr != "" && fa.host.static.Addr.To4() == "") { 138 | // Bind and/or host is IPv6 139 | fa.bind.SetDefaultAddr(bindAddr6Def) 140 | fa.host.SetDefaultAddr(hostAddr6Def) 141 | } else { 142 | // Neither address is IPv6 143 | fa.bind.SetDefaultAddr(bindAddrDef) 144 | fa.host.SetDefaultAddr(hostAddrDef) 145 | } 146 | } 147 | } 148 | 149 | func (f *FwdAddr) BindAddr() (net.Addr, error) { 150 | switch f.network { 151 | case "tcp": 152 | x := f.bind.GetTCPAddr() 153 | if x == nil { 154 | return nil, fmt.Errorf("dns lookup error of tcp addr: %w", f.bind.error) 155 | } 156 | return x, nil 157 | case "udp": 158 | x := f.bind.GetUDPAddr() 159 | if x == nil { 160 | return nil, fmt.Errorf("dns lookup error of udp addr: %w", f.bind.error) 161 | } 162 | return x, nil 163 | } 164 | return nil, fmt.Errorf("unknown network type: %v", f.network) 165 | } 166 | 167 | func (f *FwdAddr) HostAddr() (net.Addr, error) { 168 | switch f.network { 169 | case "tcp": 170 | x := f.host.GetTCPAddr() 171 | if x == nil { 172 | return nil, fmt.Errorf("dns lookup error of tcp addr: %w", f.host.error) 173 | } 174 | return x, nil 175 | case "udp": 176 | x := f.host.GetUDPAddr() 177 | if x == nil { 178 | return nil, fmt.Errorf("dns lookup error of udp addr: %w", f.host.error) 179 | } 180 | return x, nil 181 | } 182 | return nil, fmt.Errorf("unknown network type: %v", f.network) 183 | } 184 | 185 | func FullAddressFromAddr(a net.Addr) *tcpip.FullAddress { 186 | switch v := a.(type) { 187 | case *net.TCPAddr: 188 | return &tcpip.FullAddress{ 189 | Addr: tcpip.Address(v.IP), 190 | Port: uint16(v.Port), 191 | } 192 | case *net.UDPAddr: 193 | return &tcpip.FullAddress{ 194 | Addr: tcpip.Address(v.IP), 195 | Port: uint16(v.Port), 196 | } 197 | } 198 | return nil 199 | } 200 | 201 | func netAddrIP(a net.Addr) net.IP { 202 | switch v := a.(type) { 203 | case *net.TCPAddr: 204 | return v.IP 205 | case *net.UDPAddr: 206 | return v.IP 207 | } 208 | return nil 209 | } 210 | 211 | func netAddrPort(a net.Addr) int { 212 | switch v := a.(type) { 213 | case *net.TCPAddr: 214 | return v.Port 215 | case *net.UDPAddr: 216 | return v.Port 217 | } 218 | return 0 219 | } 220 | 221 | func netAddrSetPort(a net.Addr, port int) net.Addr { 222 | switch v := a.(type) { 223 | case *net.TCPAddr: 224 | x := *v 225 | x.Port = port 226 | return &x 227 | case *net.UDPAddr: 228 | x := *v 229 | x.Port = port 230 | return &x 231 | } 232 | return nil 233 | } 234 | 235 | // Addr that can be set from flag.Var. For example: 236 | // flag.Var(&metricAddr, "m", "Metrics address") 237 | type AddrFlags struct { 238 | net.Addr 239 | } 240 | 241 | func (a *AddrFlags) String() string { 242 | if a.Addr == nil { 243 | return "" 244 | } 245 | return fmt.Sprintf("%s://%s", a.Network(), a.Addr) 246 | } 247 | 248 | func (a *AddrFlags) Set(value string) error { 249 | if addr, err := AddrFromString(value); err != nil { 250 | return err 251 | } else { 252 | a.Addr = addr 253 | return nil 254 | } 255 | } 256 | 257 | func AddrFromString(value string) (net.Addr, error) { 258 | p := strings.SplitN(value, "://", 2) 259 | if len(p) != 2 { 260 | return nil, fmt.Errorf("Address must be in form net://address, where net is one of unix/tcp/udp") 261 | } 262 | var addr net.Addr 263 | switch p[0] { 264 | case "tcp": 265 | if v, err := net.ResolveTCPAddr(p[0], p[1]); err != nil { 266 | return nil, err 267 | } else { 268 | addr = v 269 | } 270 | 271 | case "udp": 272 | if v, err := net.ResolveUDPAddr(p[0], p[1]); err != nil { 273 | return nil, err 274 | } else { 275 | addr = v 276 | } 277 | 278 | case "unix": 279 | if v, err := net.ResolveUnixAddr(p[0], p[1]); err != nil { 280 | return nil, err 281 | } else { 282 | addr = v 283 | } 284 | default: 285 | return nil, fmt.Errorf("Address must be in form net://address, where net is one of unix/tcp/udp") 286 | } 287 | return addr, nil 288 | } 289 | 290 | func SplitHostPort(buf string) []string { 291 | sliceOfParts := make([]string, 0) 292 | part := make([]byte, 0) 293 | var parens int 294 | for _, c := range []byte(buf) { 295 | switch { 296 | case c == '[': 297 | if parens > 0 { 298 | part = append(part, c) 299 | } 300 | parens++ 301 | case c == ']': 302 | if parens > 1 { 303 | part = append(part, c) 304 | } 305 | parens-- 306 | case parens == 0 && c == ':': 307 | sliceOfParts = append(sliceOfParts, string(part)) 308 | part = make([]byte, 0) 309 | default: 310 | part = append(part, c) 311 | } 312 | } 313 | sliceOfParts = append(sliceOfParts, string(part)) 314 | return sliceOfParts 315 | } 316 | 317 | type IPFlag struct { 318 | ip net.IP 319 | } 320 | 321 | func (f *IPFlag) String() string { 322 | return fmt.Sprintf("%s", f.ip) 323 | } 324 | 325 | func (f *IPFlag) Set(value string) error { 326 | var err error 327 | f.ip, _, err = netParseOrResolveIP(value) 328 | if err != nil { 329 | return err 330 | } 331 | return nil 332 | } 333 | 334 | type IPPortRange struct { 335 | network string 336 | ipRange *net.IPNet 337 | portMin uint16 338 | portMax uint16 339 | } 340 | 341 | type IPPortRangeSlice []IPPortRange 342 | 343 | func (p *IPPortRange) String() string { 344 | a := []string{ 345 | p.network, 346 | "://", 347 | } 348 | if p.ipRange.IP.To4() == nil { 349 | a = append(a, "[") 350 | } 351 | a = append(a, p.ipRange.String()) 352 | if p.ipRange.IP.To4() == nil { 353 | a = append(a, "]") 354 | } 355 | if p.portMin != 0 || p.portMax != 0 { 356 | a = append(a, ":", fmt.Sprintf("%d-%d", p.portMin, p.portMax)) 357 | } 358 | return strings.Join(a, "") 359 | } 360 | 361 | func (f *IPPortRangeSlice) String() string { 362 | var a []string 363 | for _, p := range *f { 364 | a = append(a, p.String()) 365 | } 366 | return strings.Join(a, ",") 367 | } 368 | 369 | func (f *IPPortRangeSlice) Set(commaValue string) error { 370 | for _, value := range strings.Split(commaValue, ",") { 371 | network, rest := "", "" 372 | p := strings.SplitN(value, "://", 2) 373 | switch len(p) { 374 | case 2: 375 | network = p[0] 376 | rest = p[1] 377 | case 1: 378 | network = "tcp" 379 | rest = p[0] 380 | } 381 | 382 | var ( 383 | pMin, pMax uint64 384 | err1, err2 error 385 | ) 386 | 387 | p = SplitHostPort(rest) 388 | if len(p) < 1 || len(p) > 2 { 389 | return fmt.Errorf("error parsing ipportrange") 390 | } 391 | host := p[0] 392 | 393 | if len(p) == 2 { 394 | portRange := p[1] 395 | pn := strings.SplitN(portRange, "-", 2) 396 | switch len(pn) { 397 | case 2: 398 | pMin, err1 = strconv.ParseUint(pn[0], 10, 16) 399 | pMax, err2 = strconv.ParseUint(pn[1], 10, 16) 400 | case 1: 401 | pMin, err1 = strconv.ParseUint(pn[0], 10, 16) 402 | pMax, err2 = pMin, err1 403 | } 404 | if err1 != nil { 405 | return err1 406 | } 407 | if err2 != nil { 408 | return err2 409 | } 410 | if pMax < pMin { 411 | return fmt.Errorf("min port must be smaller equal than max") 412 | } 413 | } 414 | 415 | _, ipnet, err := net.ParseCIDR(host) 416 | if err != nil { 417 | ip, _, err := netParseOrResolveIP(host) 418 | if err != nil { 419 | return err 420 | } 421 | var mask net.IPMask 422 | if ip.To4() == nil { 423 | mask = net.CIDRMask(128, 128) 424 | } else { 425 | mask = net.CIDRMask(32, 32) 426 | } 427 | 428 | ipnet = &net.IPNet{ 429 | IP: ip, 430 | Mask: mask, 431 | } 432 | } 433 | 434 | pr := IPPortRange{ 435 | network: network, 436 | ipRange: ipnet, 437 | portMin: uint16(pMin), 438 | portMax: uint16(pMax), 439 | } 440 | 441 | *f = append(*f, pr) 442 | } 443 | return nil 444 | } 445 | 446 | func (f *IPPortRangeSlice) Contains(addr net.Addr) bool { 447 | addrNetwork := addr.Network() 448 | addrPort := uint16(netAddrPort(addr)) 449 | addrIP := netAddrIP(addr) 450 | for _, p := range *f { 451 | if p.network != addrNetwork { 452 | // wrong proto 453 | continue 454 | } 455 | if p.portMin != 0 || p.portMax != 0 { 456 | if addrPort < p.portMin || addrPort > p.portMax { 457 | // port out of range if ports are in the selector 458 | continue 459 | } 460 | } 461 | if !p.ipRange.Contains(addrIP) { 462 | continue 463 | } 464 | return true 465 | } 466 | return false 467 | } 468 | -------------------------------------------------------------------------------- /addr_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func equalSlice(t *testing.T, expected, actual []string) { 8 | if len(expected) != len(actual) { 9 | t.Error("mismatching slice lengths") 10 | } 11 | 12 | for i := range expected { 13 | if expected[i] != actual[i] { 14 | t.Errorf("mismatching index %d, expected %s got %s", i, expected[i], actual[i]) 15 | } 16 | } 17 | } 18 | 19 | func TestSplitHost(t *testing.T) { 20 | t.Run("empty string", func(t *testing.T) { 21 | p := SplitHostPort("") 22 | equalSlice(t, []string{""}, p) 23 | }) 24 | 25 | t.Run("ipv4 addr", func(t *testing.T) { 26 | p := SplitHostPort(":2222:[tcp.echo.server@srv-127.0.0.1:53]:0") 27 | equalSlice(t, []string{"", "2222", "tcp.echo.server@srv-127.0.0.1:53", "0"}, p) 28 | 29 | p = SplitHostPort(":2222:[tcp.echo.server@srv-[127.0.0.1]:53]:0") 30 | equalSlice(t, []string{"", "2222", "tcp.echo.server@srv-[127.0.0.1]:53", "0"}, p) 31 | }) 32 | 33 | t.Run("ipv6 addr", func(t *testing.T) { 34 | p := SplitHostPort(":2222:[tcp.echo.server@srv-[::1]:53]:0") 35 | equalSlice(t, []string{"", "2222", "tcp.echo.server@srv-[::1]:53", "0"}, p) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /ext/ext.go: -------------------------------------------------------------------------------- 1 | package ext 2 | 3 | var ( 4 | Version = "DEV" 5 | BuildTime = "unknown" 6 | ) 7 | -------------------------------------------------------------------------------- /fwd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "gvisor.dev/gvisor/pkg/tcpip/stack" 8 | ) 9 | 10 | type Listener interface { 11 | Close() error 12 | Addr() net.Addr 13 | } 14 | 15 | func LocalForwardTCP(state *State, s *stack.Stack, rf FwdAddr, doneChannel <-chan bool) (Listener, error) { 16 | tmpBind := rf.bind.GetTCPAddr() 17 | host := rf.host.GetTCPAddr() 18 | if tmpBind == nil || host == nil { 19 | return nil, fmt.Errorf("DNS lookup failed") 20 | } 21 | 22 | srv, err := net.ListenTCP(rf.network, tmpBind) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | go func() error { 28 | for { 29 | nRemote, err := srv.Accept() 30 | if err != nil { 31 | // Not sure when Accept() can error, 32 | // nor what the correct resolution 33 | // is. Most likely socket is closed. 34 | return err 35 | } 36 | remote := &KaTCPConn{nRemote.(*net.TCPConn)} 37 | 38 | go func() { 39 | LocalForward(state, s, remote, host, nil, rf.proxyProtocol) 40 | }() 41 | } 42 | }() 43 | 44 | return srv, nil 45 | } 46 | 47 | type UDPListner struct { 48 | *net.UDPConn 49 | } 50 | 51 | func (u *UDPListner) Addr() net.Addr { 52 | return u.UDPConn.LocalAddr() 53 | } 54 | 55 | func LocalForwardUDP(state *State, s *stack.Stack, rf FwdAddr, doneChannel <-chan bool) (Listener, error) { 56 | tmpBind := rf.bind.GetUDPAddr() 57 | targetAddr := rf.host.GetUDPAddr() 58 | 59 | if tmpBind == nil || targetAddr == nil { 60 | return nil, fmt.Errorf("DNS lookup failed") 61 | } 62 | 63 | srv, err := net.ListenUDP(rf.network, tmpBind) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | SetReuseaddr(srv) 69 | 70 | laddr := srv.LocalAddr().(*net.UDPAddr) 71 | 72 | go func() error { 73 | buf := make([]byte, 64*1024) 74 | for { 75 | n, addr, err := srv.ReadFrom(buf) 76 | if err != nil { 77 | return err 78 | } 79 | raddr := addr.(*net.UDPAddr) 80 | 81 | // Warning, this is racy, what if two packets are in the queue? 82 | connectedUdp, err := MagicDialUDP(laddr, raddr) 83 | if err != nil { 84 | // This actually can totally happen in 85 | // the said race. Just drop the packet then. 86 | continue 87 | } 88 | 89 | if rf.kaEnable && rf.kaInterval == 0 { 90 | connectedUdp.closeOnWrite = true 91 | } 92 | 93 | // We cannot just pass buf to LocalForward() 94 | // as it is called in a go routine, which 95 | // could consume/read buf while next loop 96 | // iteration (and current go routine) writes 97 | // it with srv.ReadFrom(buf). 98 | lfbuf := make([]byte, n) 99 | copy(lfbuf, buf[:n]) 100 | 101 | go func() { 102 | LocalForward(state, s, connectedUdp, targetAddr, lfbuf, rf.proxyProtocol) 103 | }() 104 | } 105 | }() 106 | return &UDPListner{srv}, nil 107 | } 108 | 109 | func LocalForward(state *State, s *stack.Stack, conn KaConn, targetAddr net.Addr, buf []byte, proxyProtocol bool) { 110 | var ( 111 | err error 112 | ppSrc, ppDst net.Addr 113 | sppHeader []byte 114 | ) 115 | if proxyProtocol && buf == nil { 116 | buf = make([]byte, 4096) 117 | if _, ok := conn.(*KaUDPConn); ok { 118 | // For UDP this sadly needs to be 64KiB 119 | // because the packet might be large and 120 | // fragmented. 121 | buf = make([]byte, 64*1024) 122 | } 123 | 124 | n, err := conn.Read(buf) 125 | if err != nil { 126 | goto pperror 127 | } 128 | buf = buf[:n] 129 | } 130 | 131 | if proxyProtocol { 132 | var ( 133 | n int 134 | ) 135 | if targetAddr.Network() == "tcp" { 136 | n, ppSrc, ppDst, err = DecodePP(buf) 137 | buf = buf[n:] 138 | } else { 139 | n, ppSrc, ppDst, err = DecodeSPP(buf) 140 | sppHeader = make([]byte, n) 141 | copy(sppHeader, buf[:n]) 142 | buf = buf[n:] 143 | } 144 | if err != nil { 145 | goto pperror 146 | } 147 | } 148 | 149 | { 150 | var ( 151 | srcIP net.Addr 152 | ppPrefix = "" 153 | ) 154 | // When doing local forward, if the source IP of host 155 | // connection had routable IP (unlike 156 | // 127.0.0.1)... well... spoof it! The client might find it 157 | // useful who launched the connection in the first place. 158 | if proxyProtocol == false { 159 | srcIP = conn.RemoteAddr() 160 | } else { 161 | ppPrefix = "PP " 162 | srcIP = ppSrc 163 | } 164 | // If the source IP as reported by PP or the client is not routable, still forward 165 | // connection. Just don't use/leak the original IP. 166 | var isSourcev4 = FullAddressFromAddr(srcIP).Addr.To4() != "" 167 | var isTargetv4 = FullAddressFromAddr(targetAddr).Addr.To4() != "" 168 | if IPNetContains(state.StaticRoutingDeny, netAddrIP(srcIP)) || isSourcev4 != isTargetv4 { 169 | // Source IP not rewritten because: 170 | // * static routing deny 171 | // * IPv6 -> IPv4, IPv6 addr can't be mapped to IPv4 172 | // * IPv4 -> IPv6, IPv4 addr could be in IPv4-mapped-IPv6 format, but netstack refuses 173 | // See https://github.com/google/netstack/blob/master/tcpip/transport/tcp/endpoint.go:checkV4Mapped(), 174 | // which results in ErrInvalidEndpointState if Bind(IPv4-mapped-IPv6) then Connect(IPv6). 175 | srcIP = nil 176 | } 177 | 178 | if srcIP != nil { 179 | // It's very nice the proxy-protocol (or just 180 | // client) gave us client port number, but we 181 | // don't want it. Spoofing the same port 182 | // number on our side is not safe, useless, 183 | // confusing and very bug prone. 184 | srcIP = netAddrSetPort(srcIP, 0) 185 | } 186 | 187 | if netAddrPort(targetAddr) == 0 { 188 | // If the guest has dport equal to zero, fill 189 | // it up somehow. First guess - use dport of 190 | // host connection. 191 | hostPort := netAddrPort(conn.LocalAddr()) 192 | 193 | // Alternatively if we got dport from PP, use that 194 | if ppDst != nil { 195 | hostPort = netAddrPort(ppDst) 196 | } 197 | 198 | targetAddr = netAddrSetPort(targetAddr, hostPort) 199 | } 200 | 201 | guest, err := GonetDial(s, srcIP, targetAddr) 202 | var pe ProxyError 203 | if err != nil { 204 | SetResetOnClose(conn) 205 | conn.Close() 206 | pe.RemoteRead = fmt.Errorf("%s", err) 207 | pe.First = 2 208 | if logConnections { 209 | fmt.Printf("[!] %s://%s-%s/%s local-fwd %serror: %s\n", 210 | targetAddr.Network(), 211 | conn.RemoteAddr(), 212 | conn.LocalAddr(), 213 | targetAddr.String(), 214 | ppPrefix, 215 | pe) 216 | } 217 | } else { 218 | if logConnections { 219 | fmt.Printf("[+] %s://%s-%s/%s-%s local-fwd %sconn\n", 220 | targetAddr.Network(), 221 | conn.RemoteAddr(), 222 | conn.LocalAddr(), 223 | guest.LocalAddr(), 224 | targetAddr.String(), 225 | ppPrefix) 226 | } 227 | 228 | guest.Write(buf) 229 | pe = connSplice(conn, guest, sppHeader) 230 | 231 | if logConnections { 232 | fmt.Printf("[-] %s://%s-%s/%s-%s local-fwd %sdone: %s\n", 233 | targetAddr.Network(), 234 | conn.RemoteAddr(), 235 | conn.LocalAddr(), 236 | guest.LocalAddr(), 237 | targetAddr.String(), 238 | ppPrefix, 239 | pe) 240 | } 241 | } 242 | } 243 | return 244 | pperror: 245 | if logConnections { 246 | fmt.Printf("[!] %s://%s-%s/%s local-fwd PP error: %s\n", 247 | targetAddr.Network(), 248 | conn.RemoteAddr(), 249 | conn.LocalAddr(), 250 | targetAddr.String(), 251 | err) 252 | } 253 | conn.Close() 254 | return 255 | } 256 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudflare/slirpnetstack 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/bazelbuild/rules_go v0.25.0 // indirect 7 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect 8 | github.com/golang/protobuf v1.4.3 // indirect 9 | github.com/google/go-cmp v0.5.4 // indirect 10 | github.com/kr/text v0.2.0 // indirect 11 | github.com/miekg/dns v1.1.35 12 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 13 | github.com/opencontainers/runc v0.1.1 14 | github.com/opencontainers/runtime-spec v1.0.2 15 | github.com/stretchr/testify v1.6.1 // indirect 16 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect 17 | github.com/vishvananda/netlink v1.1.0 18 | github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad // indirect 19 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c // indirect 20 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect 21 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect 22 | golang.org/x/sys v0.0.0-20201202213521-69691e467435 23 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect 24 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 25 | gopkg.in/netaddr.v1 v1.4.0 26 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect 27 | gvisor.dev/gvisor v0.0.0-20201204040109-0ba39926c86f 28 | ) 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= 2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 5 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 6 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.52.1-0.20200122224058-0482b626c726/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 11 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 12 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 13 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 14 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 15 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 16 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 17 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 18 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 19 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 20 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 21 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 22 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 23 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 24 | github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= 25 | github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= 26 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 27 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 28 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 29 | github.com/bazelbuild/rules_go v0.25.0 h1:mb2SfWfQcOkIGTEf8QdRna9nF6nnjlyv/qeXvPVkleE= 30 | github.com/bazelbuild/rules_go v0.25.0/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M= 31 | github.com/cenkalti/backoff v1.1.1-0.20190506075156-2146c9339422/go.mod h1:b6Nc7NRH5C4aCISLry0tLnTjcuTEvoiqcWDdsU0sOGM= 32 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= 33 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 34 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 35 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 36 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 37 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 38 | github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= 39 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 40 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 41 | github.com/containerd/cgroups v0.0.0-20181219155423-39b18af02c41/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= 42 | github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= 43 | github.com/containerd/containerd v1.3.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= 44 | github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a/go.mod h1:W0qIOTD7mp2He++YVq+kgfXezRYqzP1uDuMVH1bITDY= 45 | github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= 46 | github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= 47 | github.com/containerd/ttrpc v0.0.0-20200121165050-0be804eadb15/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= 48 | github.com/containerd/typeurl v0.0.0-20200205145503-b45ef1f1f737/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= 49 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 50 | github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= 51 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 52 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 53 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 54 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 55 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 56 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 57 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 58 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 59 | github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 60 | github.com/docker/docker v1.4.2-0.20191028175130-9e7d5ac5ea55/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 61 | github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 62 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 63 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 64 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 65 | github.com/dpjacques/clockwork v0.1.1-0.20200827220843-c1f524b839be/go.mod h1:D8mP2A8vVT2GkXqPorSBmhnshhkFBYgzhA90KmJt25Y= 66 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 67 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 68 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 69 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 70 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 71 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 72 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 73 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 74 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 75 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 76 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 77 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 78 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 79 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 80 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 81 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 82 | github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= 83 | github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 84 | github.com/gofrs/flock v0.6.1-0.20180915234121-886344bea079/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= 85 | github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= 86 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 87 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 88 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 89 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 90 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 91 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 92 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 93 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 94 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 95 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 96 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 97 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 98 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 99 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 100 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 101 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 102 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 103 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 104 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 105 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 106 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 107 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 108 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 109 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 110 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 111 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 112 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 113 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 114 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 115 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= 116 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 117 | github.com/google/go-cmp v0.5.3-0.20201020212313-ab46b8bd0abd h1:pJfrTSHC+QpCQplFZqzlwihfc+0Oty0ViHPHPxXj0SI= 118 | github.com/google/go-cmp v0.5.3-0.20201020212313-ab46b8bd0abd/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 119 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= 120 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 121 | github.com/google/go-github/v28 v28.1.2-0.20191108005307-e555eab49ce8/go.mod h1:g82e6OHbJ0WYrYeOrid1MMfHAtqjxBz+N74tfAt9KrQ= 122 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 123 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 124 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 125 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 126 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 127 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 128 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 129 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 130 | github.com/google/subcommands v1.0.2-0.20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 131 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 132 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 133 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 134 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 135 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 136 | github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 137 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 138 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 139 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 140 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 141 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 142 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 143 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 144 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 145 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 146 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 147 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 148 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 149 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 150 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 151 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 152 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 153 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 154 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 155 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 156 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 157 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 158 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 159 | github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 160 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 161 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 162 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 163 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 164 | github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= 165 | github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= 166 | github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 167 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 168 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 169 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 170 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 171 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 172 | github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9 h1:Sha2bQdoWE5YQPTlJOL31rmce94/tYi113SlFo1xQ2c= 173 | github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 174 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 175 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 176 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 177 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 178 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 179 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 180 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 181 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 182 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 183 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 184 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 185 | github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= 186 | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 187 | github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 188 | github.com/opencontainers/runtime-spec v1.0.2-0.20181111125026-1722abf79c2f/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 189 | github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= 190 | github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 191 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 192 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 193 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 194 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 195 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 196 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 197 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 198 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 199 | github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 200 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 201 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 202 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 203 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 204 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 205 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 206 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 207 | github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 208 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 209 | github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 210 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 211 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 212 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 213 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 214 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 215 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 216 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 217 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 218 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 219 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8= 220 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= 221 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= 222 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= 223 | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 224 | github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= 225 | github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= 226 | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= 227 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 228 | github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= 229 | github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 230 | github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0= 231 | github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= 232 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 233 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 234 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 235 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 236 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 237 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 238 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 239 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 240 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 241 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 242 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 243 | golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 244 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 245 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 246 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k= 247 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 248 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 249 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 250 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 251 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 252 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 253 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 254 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 255 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 256 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 257 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 258 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 259 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 260 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 261 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 262 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 263 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 264 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 265 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 266 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 267 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 268 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 269 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 270 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 271 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 272 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 273 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 274 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 275 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 276 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 277 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 278 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 279 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 280 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 281 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 282 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 283 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 284 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= 285 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 286 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 287 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 288 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 289 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 290 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 291 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 292 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 293 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 294 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 295 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 296 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= 297 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 298 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= 299 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 300 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 301 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 302 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 303 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 304 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 305 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 306 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk= 328 | golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 329 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 330 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 331 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 332 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 333 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 334 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 335 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 336 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 337 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 338 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= 339 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 340 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 341 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 342 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 343 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 344 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 345 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 346 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 347 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 348 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 349 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 350 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 351 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 352 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 353 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 354 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 355 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 356 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 357 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 358 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 359 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 360 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 361 | golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96 h1:K+nJoPcImWk+ZGPHOKkDocKcQPACCz8usiCiVQYfXsk= 362 | golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 363 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 364 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 365 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 366 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 367 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 368 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 369 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 370 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 371 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 372 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 373 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 374 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 375 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 376 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 377 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 378 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 379 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 380 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 381 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 382 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 383 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 384 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 385 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 386 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 387 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 388 | google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 389 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 390 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 391 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 392 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 393 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 394 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 395 | google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 396 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 397 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 398 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 399 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 400 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 401 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 402 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 403 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 404 | google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b h1:jEdfCm+8YTWSYgU4L7Nq0jjU+q9RxIhi0cXLTY+Ih3A= 405 | google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= 406 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 407 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 408 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 409 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 410 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 411 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 412 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 413 | gopkg.in/netaddr.v1 v1.4.0 h1:bNFIqC9idce+Xkx2wDVQX99AYr6O95IaH2b/SJdEXgo= 414 | gopkg.in/netaddr.v1 v1.4.0/go.mod h1:FQgqthG+y4FjuYlc51+a91bkLGEC4VE42XpSvK4GUWQ= 415 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 416 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 417 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 418 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 419 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 420 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 421 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 422 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 423 | gvisor.dev/gvisor v0.0.0-20201204040109-0ba39926c86f h1:0Wf9LYhP0uc2ca90o/B4TxqmPDYXa1n7UemDdP0ehG0= 424 | gvisor.dev/gvisor v0.0.0-20201204040109-0ba39926c86f/go.mod h1:MoNoZalgvqJpaNVzWcqvAFu8UE2YaFogv+r3pa0qRGY= 425 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 426 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 427 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 428 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 429 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 430 | k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs= 431 | k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= 432 | k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= 433 | k8s.io/client-go v0.16.13/go.mod h1:UKvVT4cajC2iN7DCjLgT0KVY/cbY6DGdUCyRiIfws5M= 434 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 435 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 436 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 437 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 438 | k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 439 | k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 440 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 441 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 442 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 443 | -------------------------------------------------------------------------------- /kacon.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang.org/x/sys/unix" 5 | "net" 6 | "sync" 7 | "syscall" 8 | "time" 9 | ) 10 | 11 | type KaConn interface { 12 | net.Conn 13 | SetTimeouts(kaInterval time.Duration, kaCount int) error 14 | } 15 | 16 | type KaTCPConn struct { 17 | *net.TCPConn 18 | } 19 | 20 | func (c *KaTCPConn) SetTimeouts(kaInterval time.Duration, kaCount int) error { 21 | err := c.TCPConn.SetKeepAlive(true) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | err = c.TCPConn.SetKeepAlivePeriod(kaInterval) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | raw, err := c.SyscallConn() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | // Configures the connection to time out after peer has been idle for a 37 | // while, that is it has not sent or acknowledged any data or not replied to 38 | // keep-alive probes. 39 | userTimeout := UserTimeoutFromKeepalive(kaInterval, kaCount) 40 | 41 | return raw.Control(func(s_ uintptr) { 42 | s := int(s_) 43 | syscall.SetsockoptInt(s, syscall.SOL_TCP, unix.TCP_KEEPCNT, kaCount) 44 | userTimeoutMillis := int(userTimeout / time.Millisecond) 45 | syscall.SetsockoptInt(s, syscall.SOL_TCP, unix.TCP_USER_TIMEOUT, userTimeoutMillis) 46 | }) 47 | } 48 | 49 | func UserTimeoutFromKeepalive(kaInterval time.Duration, kaCount int) time.Duration { 50 | // The idle timeout period is determined from the keep-alive probe interval 51 | // and the total number of probes to sent, that is 52 | // 53 | // TCP_USER_TIMEOUT = TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT 54 | // 55 | // in Go, TCPConn.SetKeepAlivePeriod(d) sets the value for both TCP_KEEPIDLE 56 | // and TCP_KEEPINTVL 57 | // 58 | // More info: https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/ 59 | // 60 | return kaInterval + (kaInterval * time.Duration(kaCount)) 61 | } 62 | 63 | type UDPKeepAliveError struct{ error } 64 | 65 | type KaUDPConn struct { 66 | net.Conn 67 | keepAlive bool 68 | keepAlivePeriod time.Duration 69 | closeOnWrite bool 70 | periodMu sync.RWMutex 71 | } 72 | 73 | func (c *KaUDPConn) SetTimeouts(kaInterval time.Duration, kaCount int) error { 74 | c.periodMu.Lock() 75 | c.keepAlive = true 76 | c.keepAlivePeriod = kaInterval 77 | c.periodMu.Unlock() 78 | return nil 79 | } 80 | 81 | func (c *KaUDPConn) Read(buf []byte) (int, error) { 82 | var t time.Time 83 | 84 | // IsZero means deadline is disabled. Good. 85 | c.periodMu.RLock() 86 | if c.keepAlive { 87 | t = time.Now().Add(c.keepAlivePeriod) 88 | } 89 | c.periodMu.RUnlock() 90 | 91 | // Zero is fine. Will cancel deadline. 92 | c.Conn.SetReadDeadline(t) 93 | 94 | n, err := c.Conn.Read(buf) 95 | if ne, ok := err.(net.Error); ok == true && ne.Timeout() { 96 | // On Keepalive raise special error 97 | return 0, &UDPKeepAliveError{err} 98 | } else { 99 | // Otherwise (other error or deadline timeout) just pass 100 | // direct to user. 101 | return n, err 102 | } 103 | } 104 | 105 | func (c *KaUDPConn) Write(buf []byte) (int, error) { 106 | // Here is the deal. The UDP conn doesn't have a notion of 107 | // half-closed. Instead, let's keep both directions alive, 108 | // even if only one of them is live. On Write, let's grant 109 | // more time to reader. 110 | c.periodMu.RLock() 111 | if c.keepAlive { 112 | t := time.Now().Add(c.keepAlivePeriod) 113 | c.Conn.SetReadDeadline(t) 114 | } 115 | closeOnWrite := c.closeOnWrite 116 | c.periodMu.RUnlock() 117 | 118 | n, err := c.Conn.Write(buf) 119 | if closeOnWrite { 120 | c.Conn.Close() 121 | } 122 | return n, err 123 | } 124 | -------------------------------------------------------------------------------- /local_routes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/vishvananda/netlink" 5 | "gopkg.in/netaddr.v1" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type LocalRoutes struct { 12 | sync.RWMutex 13 | ipset *netaddr.IPSet 14 | ticker *time.Ticker 15 | done chan bool 16 | } 17 | 18 | func (lr *LocalRoutes) Contains(ip net.IP) bool { 19 | lr.RLock() 20 | r := lr.ipset.Contains(ip) 21 | lr.RUnlock() 22 | return r 23 | } 24 | 25 | func (lr *LocalRoutes) Start(interval time.Duration) { 26 | lr.Lock() 27 | lr.ticker = time.NewTicker(interval) 28 | lr.done = make(chan bool) 29 | lr.ipset = FetchLocalRoutes() 30 | lr.Unlock() 31 | go func() { 32 | for { 33 | lr.Lock() 34 | lr.ipset = FetchLocalRoutes() 35 | lr.Unlock() 36 | select { 37 | case <-lr.done: 38 | return 39 | case <-lr.ticker.C: 40 | } 41 | } 42 | }() 43 | } 44 | 45 | func (lr *LocalRoutes) Stop() { 46 | lr.Lock() 47 | close(lr.done) 48 | lr.Unlock() 49 | } 50 | 51 | // Regularly load "local" and "main" routing tables, for both IPv4 and 52 | // IPv6. The idea is to refuse connections to IP ranges that might be 53 | // local. 54 | func FetchLocalRoutes() *netaddr.IPSet { 55 | ipset := netaddr.IPSet{} 56 | 57 | // "local" routing table 58 | fltr := netlink.Route{Table: 255} 59 | routes, _ := netlink.RouteListFiltered(0, &fltr, netlink.RT_FILTER_TABLE) 60 | for _, r := range routes { 61 | if r.Dst != nil { 62 | ipset.InsertNet(r.Dst) 63 | } 64 | } 65 | 66 | // "main" routing table 67 | fltr = netlink.Route{Table: 254} 68 | routes, _ = netlink.RouteListFiltered(0, &fltr, netlink.RT_FILTER_TABLE) 69 | for _, r := range routes { 70 | if r.Dst != nil { 71 | ipset.InsertNet(r.Dst) 72 | } 73 | } 74 | 75 | return &ipset 76 | } 77 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "os" 9 | "os/signal" 10 | "runtime" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/opencontainers/runc/libcontainer/system" 15 | "golang.org/x/sys/unix" 16 | 17 | "github.com/cloudflare/slirpnetstack/ext" 18 | "gvisor.dev/gvisor/pkg/log" 19 | "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" 20 | "gvisor.dev/gvisor/pkg/tcpip/stack" 21 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 22 | "gvisor.dev/gvisor/pkg/tcpip/transport/udp" 23 | ) 24 | 25 | var ( 26 | cmdVersion bool 27 | fd int 28 | netNsPath string 29 | ifName string 30 | mtu uint 31 | remoteFwd FwdAddrSlice 32 | localFwd FwdAddrSlice 33 | logConnections bool 34 | quiet bool 35 | metricAddr AddrFlags 36 | gomaxprocs int 37 | pcapPath string 38 | exitWithParent bool 39 | enableHostRouting bool 40 | enableInternetRouting bool 41 | sourceIPv4 IPFlag 42 | sourceIPv6 IPFlag 43 | allowRange IPPortRangeSlice 44 | denyRange IPPortRangeSlice 45 | dnsTTL time.Duration 46 | ) 47 | 48 | func initFlagSet(flag *flag.FlagSet) { 49 | flag.BoolVar(&cmdVersion, "version", false, "Print slirpnetstack version and exit") 50 | flag.IntVar(&fd, "fd", -1, "Unix datagram socket file descriptor") 51 | flag.StringVar(&netNsPath, "netns", "", "path to network namespace") 52 | flag.StringVar(&ifName, "interface", "tun0", "interface name within netns") 53 | flag.UintVar(&mtu, "mtu", 0, "MTU (default: 1500 for -fd, auto for -netns)") 54 | flag.Var(&remoteFwd, "R", "Connections to remote side forwarded local") 55 | flag.Var(&localFwd, "L", "Connections to local side forwarded remote") 56 | flag.BoolVar(&quiet, "quiet", false, "Print less stuff on screen") 57 | flag.Var(&metricAddr, "m", "Metrics addr") 58 | flag.IntVar(&gomaxprocs, "maxprocs", 0, "set GOMAXPROCS variable to limit cpu") 59 | flag.StringVar(&pcapPath, "pcap", "", "path to PCAP file") 60 | flag.BoolVar(&exitWithParent, "exit-with-parent", false, "Exit with parent process") 61 | flag.BoolVar(&enableHostRouting, "enable-host", false, "Allow guest to connecting to IP's that are in the host main and local routing tables") 62 | flag.BoolVar(&enableInternetRouting, "enable-routing", false, "Allow guest connecting to non-local IP's that are likley to be routed to the internet") 63 | flag.Var(&sourceIPv4, "source-ipv4", "When connecting, use the selected Source IP for ipv4") 64 | flag.Var(&sourceIPv6, "source-ipv6", "When connecting, use the selected Source IP for ipv6") 65 | flag.Var(&allowRange, "allow", "When routing, allow specified IP prefix and port range") 66 | flag.Var(&denyRange, "deny", "When routing, deny specified IP prefix and port range") 67 | flag.DurationVar(&dnsTTL, "dns-ttl", time.Duration(5*time.Second), "For how long to cache DNS in case of dns labels passed to forward target.") 68 | } 69 | 70 | func main() { 71 | status := Main(os.Args[0], os.Args[1:]) 72 | os.Exit(status) 73 | } 74 | 75 | type SrcIPs struct { 76 | srcIPv4 net.IP 77 | srcIPv6 net.IP 78 | } 79 | 80 | type State struct { 81 | StaticRoutingDeny []*net.IPNet 82 | 83 | remoteUdpFwd map[string]*FwdAddr 84 | remoteTcpFwd map[string]*FwdAddr 85 | 86 | // disable host routes 87 | localRoutes *LocalRoutes 88 | enableHostRouting bool 89 | enableInternetRouting bool 90 | allowRange IPPortRangeSlice 91 | denyRange IPPortRangeSlice 92 | 93 | srcIPs SrcIPs 94 | } 95 | 96 | func Main(programName string, args []string) int { 97 | var ( 98 | state State 99 | linkEP stack.LinkEndpoint 100 | tapMode bool = true 101 | mac net.HardwareAddr = MustParseMAC("70:71:aa:4b:29:aa") 102 | metrics *Metrics 103 | err error 104 | ) 105 | 106 | sigCh := make(chan os.Signal, 4) 107 | signal.Notify(sigCh, syscall.SIGINT) 108 | signal.Notify(sigCh, syscall.SIGTERM) 109 | 110 | for i := uint64(1024 * 1024); i > 0; i /= 2 { 111 | rLimit := syscall.Rlimit{Max: i, Cur: i} 112 | err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) 113 | if err == nil { 114 | break 115 | } 116 | } 117 | 118 | // Welcome to the golang flag parsing mess! We need to set our 119 | // own flagset and not use the defaults because of how we 120 | // handle the test coverage. You see, when running in coverage 121 | // mode, golang cover test execs "flag.Parse()" by 122 | // itself. This means we could do second flag.Parse here, 123 | // leading to doubly-parsing of flags. On top of that imagine 124 | // an error - our custom structures that have .Set and .String 125 | // methods will be called by flag.Parse called from test main, 126 | // and on error Exit(2) which will _not_ be counted against 127 | // code coverage, because it exis before code coverage machine 128 | // even starts. The point is - we need to custom parse flags 129 | // ourselves and we need to make sure that we don't use the 130 | // global flag.Parse machinery when running from coverage 131 | // tests. 132 | { 133 | flagSet := flag.NewFlagSet(programName, flag.ContinueOnError) 134 | initFlagSet(flagSet) 135 | err := flagSet.Parse(args) 136 | if err != nil { 137 | return 2 138 | } 139 | } 140 | 141 | if cmdVersion { 142 | fmt.Printf("slirpnetstack version %s\n", ext.Version) 143 | fmt.Printf("build time %s\n", ext.BuildTime) 144 | return 0 145 | } 146 | 147 | if gomaxprocs > 0 { 148 | runtime.GOMAXPROCS(gomaxprocs) 149 | } 150 | 151 | state.localRoutes = &LocalRoutes{} 152 | state.localRoutes.Start(30 * time.Second) 153 | 154 | state.enableHostRouting = enableHostRouting 155 | state.enableInternetRouting = enableInternetRouting 156 | state.srcIPs.srcIPv4 = sourceIPv4.ip 157 | state.srcIPs.srcIPv6 = sourceIPv6.ip 158 | state.allowRange = allowRange 159 | state.denyRange = denyRange 160 | 161 | logConnections = !quiet 162 | 163 | localFwd.SetDefaultAddrs( 164 | netParseIP("127.0.0.1"), 165 | netParseIP("::1"), 166 | netParseIP("10.0.2.100"), 167 | netParseIP("fd00::100")) 168 | remoteFwd.SetDefaultAddrs( 169 | netParseIP("10.0.2.2"), 170 | netParseIP("fd00::2"), 171 | netParseIP("127.0.0.1"), 172 | netParseIP("::1")) 173 | 174 | state.remoteUdpFwd = make(map[string]*FwdAddr) 175 | state.remoteTcpFwd = make(map[string]*FwdAddr) 176 | // For the list of reserved IP's see 177 | // https://idea.popcount.org/2019-12-06-addressing/ The idea 178 | // here is to forbid outbound connections to obviously wrong 179 | // or meaningless IP's. 180 | state.StaticRoutingDeny = append(state.StaticRoutingDeny, 181 | MustParseCIDR("0.0.0.0/8"), 182 | MustParseCIDR("10.0.2.0/24"), 183 | MustParseCIDR("127.0.0.0/8"), 184 | MustParseCIDR("255.255.255.255/32"), 185 | MustParseCIDR("::/128"), 186 | MustParseCIDR("::1/128"), 187 | MustParseCIDR("::/96"), 188 | MustParseCIDR("::ffff:0:0:0/96"), 189 | MustParseCIDR("64:ff9b::/96"), 190 | ) 191 | 192 | log.SetLevel(log.Warning) 193 | rand.Seed(time.Now().UnixNano()) 194 | 195 | if metricAddr.Addr != nil && metricAddr.Network() != "" { 196 | metrics, err = StartMetrics(metricAddr.Addr) 197 | if err != nil { 198 | fmt.Fprintf(os.Stderr, "[!] Failed to start metrics: %s\n", err) 199 | return -2 200 | } 201 | } 202 | 203 | if fd == -1 { 204 | fd, tapMode, mtu, err = GetTunTap(netNsPath, ifName) 205 | if err != nil { 206 | fmt.Fprintf(os.Stderr, "Failed to open TUN/TAP: %s\n", err) 207 | return -3 208 | } 209 | } else { 210 | if netNsPath != "" { 211 | fmt.Fprintf(os.Stderr, "Please specify either -fd or -netns\n") 212 | return -4 213 | } 214 | if mtu == 0 { 215 | mtu = 1500 216 | } 217 | } 218 | 219 | // This must be done after all the namespace dance, otherwise 220 | // it doesn't work. I think it has to do with lack of 221 | // PR_SET_PDEATHSIG inheritance on fork(), or maybe it is 222 | // cleared on namespace join? Dunno. Remember SIGTERM is 223 | // supposed to be gracefully handled. 224 | if exitWithParent { 225 | system.ParentDeathSignal(unix.SIGTERM).Set() 226 | } 227 | 228 | // With high mtu, low packet loss and low latency over tuntap, 229 | // the specific value isn't that important. The only important 230 | // bit is that it should be at least a couple times MSS. 231 | bufSize := 4 * 1024 * 1024 232 | 233 | s := NewStack(bufSize, bufSize) 234 | 235 | tcpHandler := TcpRoutingHandler(&state) 236 | // Set sliding window auto-tuned value. Allow 10 concurrent 237 | // new connection attempts. 238 | fwdTcp := tcp.NewForwarder(s, 0, 10, tcpHandler) 239 | s.SetTransportProtocolHandler(tcp.ProtocolNumber, fwdTcp.HandlePacket) 240 | 241 | udpHandler := UdpRoutingHandler(s, &state) 242 | fwdUdp := udp.NewForwarder(s, udpHandler) 243 | s.SetTransportProtocolHandler(udp.ProtocolNumber, fwdUdp.HandlePacket) 244 | 245 | doneChannel := make(chan bool) 246 | 247 | for _, lf := range localFwd { 248 | var srv Listener 249 | switch lf.network { 250 | case "tcp": 251 | srv, err = LocalForwardTCP(&state, s, lf, doneChannel) 252 | case "udp": 253 | srv, err = LocalForwardUDP(&state, s, lf, doneChannel) 254 | } 255 | if err != nil { 256 | fmt.Fprintf(os.Stderr, "[!] Failed to listen on %s://%s: %s\n", 257 | lf.network, lf.bind.String(), err) 258 | return -9 259 | } else { 260 | ppPrefix := "" 261 | if lf.proxyProtocol { 262 | ppPrefix = "PP " 263 | } 264 | laddr := srv.Addr() 265 | 266 | if !quiet { 267 | fmt.Printf("[+] local-fwd Local %slisten %s://%s\n", 268 | ppPrefix, 269 | laddr.Network(), 270 | laddr.String()) 271 | } 272 | } 273 | } 274 | 275 | for i, rf := range remoteFwd { 276 | bindAddr, err := rf.BindAddr() 277 | if err != nil { 278 | fmt.Fprintf(os.Stderr, "[!] Failed to resolve bind address. %v\n", err) 279 | return -10 280 | } 281 | if !quiet { 282 | fmt.Printf("[+] Accepting on remote side %s://%s\n", 283 | rf.network, rf.bind.String()) 284 | } 285 | switch rf.network { 286 | case "tcp": 287 | state.remoteTcpFwd[bindAddr.String()] = &remoteFwd[i] 288 | case "udp": 289 | state.remoteUdpFwd[bindAddr.String()] = &remoteFwd[i] 290 | } 291 | } 292 | 293 | if linkEP, err = createLinkEP(s, fd, tapMode, mac, uint32(mtu)); err != nil { 294 | fmt.Fprintf(os.Stderr, "[!] Failed to create linkEP: %s\n", err) 295 | return -5 296 | } 297 | 298 | if pcapPath != "" { 299 | pcapFile, err := os.OpenFile(pcapPath, os.O_WRONLY|os.O_CREATE, 0644) 300 | if err != nil { 301 | fmt.Fprintf(os.Stderr, "[!] Failed to open PCAP file: %s\n", err) 302 | return -6 303 | } 304 | if linkEP, err = sniffer.NewWithWriter(linkEP, pcapFile, uint32(mtu)); err != nil { 305 | fmt.Fprintf(os.Stderr, "[!]Failed to sniff linkEP: %s\n", err) 306 | return -7 307 | } 308 | defer pcapFile.Close() 309 | } 310 | 311 | if err = createNIC(s, 1, linkEP); err != nil { 312 | fmt.Fprintf(os.Stderr, "[!] Failed to createNIC: %s\n", err) 313 | return -8 314 | 315 | } 316 | 317 | StackRoutingSetup(s, 1, "10.0.2.2/24") 318 | StackPrimeArp(s, 1, netParseIP("10.0.2.100")) 319 | 320 | StackRoutingSetup(s, 1, "fd00::2/64") 321 | 322 | // [****] Finally, the mighty event loop, waiting on signals 323 | pid := syscall.Getpid() 324 | fmt.Fprintf(os.Stderr, "[+] #%d Slirpnetstack started\n", pid) 325 | syscall.Kill(syscall.Getppid(), syscall.SIGWINCH) 326 | 327 | for { 328 | select { 329 | case sig := <-sigCh: 330 | signal.Reset(sig) 331 | fmt.Fprintf(os.Stderr, "[-] #%d Slirpnetstack closing\n", pid) 332 | goto stop 333 | } 334 | } 335 | stop: 336 | // TODO: define semantics of graceful close on signal 337 | //s.Wait() 338 | if metrics != nil { 339 | metrics.Close() 340 | } 341 | if state.localRoutes != nil { 342 | state.localRoutes.Stop() 343 | } 344 | return 0 345 | } 346 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // +build testrunmain 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "os" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | type sliceString []string 13 | 14 | func (i *sliceString) String() string { 15 | return strings.Join(*i, " ") 16 | } 17 | func (i *sliceString) Set(value string) error { 18 | *i = append(*i, value) 19 | return nil 20 | } 21 | 22 | var ( 23 | mainStatus int 24 | slirpArgs sliceString 25 | ) 26 | 27 | // Inject our option whether go tests like it or not. Muhahaha. 28 | func init() { 29 | flag.Var(&slirpArgs, "args", "Args to slirpnetstack") 30 | } 31 | 32 | func TestRunMain(t *testing.T) { 33 | mainStatus = Main("slirpnetstack", slirpArgs) 34 | } 35 | 36 | func TestMain(m *testing.M) { 37 | testStatus := m.Run() 38 | if testStatus != 0 { 39 | os.Exit(testStatus) 40 | } else { 41 | os.Exit(mainStatus) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /metrics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "os" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func init() { 14 | http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { 15 | w.Write([]byte("ok")) 16 | }) 17 | } 18 | 19 | type Metrics struct { 20 | err chan error 21 | stop chan struct{} 22 | 23 | ln net.Listener 24 | srv *http.Server 25 | } 26 | 27 | func StartMetrics(addr net.Addr) (*Metrics, error) { 28 | metrics := &Metrics{} 29 | if addr == nil || addr.Network() == "" { 30 | return nil, fmt.Errorf("invalid address: %v", addr) 31 | } 32 | 33 | metrics.err = make(chan error) 34 | metrics.stop = make(chan struct{}) 35 | metrics.srv = &http.Server{ 36 | ReadTimeout: 5 * time.Second, 37 | WriteTimeout: 35 * time.Second, 38 | } 39 | 40 | var err error 41 | metrics.ln, err = net.Listen(addr.Network(), addr.String()) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | fmt.Fprintf(os.Stderr, "[ ] #%d Running metrics on %s\n", syscall.Getpid(), metrics.ln.Addr()) 47 | 48 | go func() { 49 | err := metrics.srv.Serve(metrics.ln) 50 | select { 51 | case <-metrics.stop: 52 | case metrics.err <- err: 53 | } 54 | close(metrics.err) 55 | }() 56 | 57 | return metrics, nil 58 | } 59 | 60 | func (metrics *Metrics) Err() <-chan error { 61 | return metrics.err 62 | } 63 | 64 | func (metrics *Metrics) Close() { 65 | close(metrics.stop) 66 | if metrics.ln != nil { 67 | fmt.Fprintf(os.Stderr, "[ ] #%d Stopping metrics\n", syscall.Getpid()) 68 | metrics.ln.Close() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "gvisor.dev/gvisor/pkg/tcpip" 13 | ) 14 | 15 | func IPNetContains(nets []*net.IPNet, ip net.IP) bool { 16 | for _, n := range nets { 17 | if n.Contains(ip) { 18 | return true 19 | } 20 | } 21 | return false 22 | } 23 | 24 | func MustParseCIDR(n string) *net.IPNet { 25 | _, r, err := net.ParseCIDR(n) 26 | if err != nil { 27 | panic(fmt.Sprintf("Unable to ParseCIDR %s = %s", n, err)) 28 | } 29 | return r 30 | } 31 | 32 | func MustParseMAC(m string) net.HardwareAddr { 33 | mac, err := net.ParseMAC(m) 34 | if err != nil { 35 | panic(fmt.Sprintf("Unable to ParseCIDR %s = %s", m, err)) 36 | } 37 | return mac 38 | } 39 | 40 | func SetResetOnClose(conn net.Conn) { 41 | switch v := conn.(type) { 42 | case *net.TCPConn: 43 | v.SetLinger(0) 44 | // case *gonet.Conn: 45 | // TODO: gonet doesn't support SO_LINGER yet 46 | } 47 | } 48 | 49 | // The problem with standard net.ParseIP is that it can return 50 | // ::ffff:x.x.x.x IPv4-mapped address. We don't like the lack of 51 | // uniformity. 52 | func netParseIP(h string) net.IP { 53 | ip := net.ParseIP(h) 54 | if ip == nil { 55 | return nil 56 | } 57 | if ip.To4() != nil { 58 | ip = ip.To4() 59 | } 60 | return ip 61 | } 62 | 63 | // Deferrred Address. Either just an ip, in which case 'static' is 64 | // filled and we are done, or something we need to retrieve from DNS. 65 | type defAddress struct { 66 | sync.Mutex 67 | 68 | // Static hardcoded IP/port OR previously retrieved one. In 69 | // other words it's never empty for a valid address. 70 | static tcpip.FullAddress 71 | label string 72 | fetched time.Time 73 | error error 74 | } 75 | 76 | func ParseDefAddress(ipS string, portS string) (_da *defAddress, _err error) { 77 | da := &defAddress{} 78 | if ipS != "" { 79 | if ip := netParseIP(ipS); ip != nil { 80 | // ipS is an IP literal 81 | da.static.Addr = tcpip.Address(ip) 82 | } else { 83 | // ipS is a hostname to resolve later 84 | da.label = ipS 85 | } 86 | } 87 | 88 | if portS != "" { 89 | var err error 90 | port, err := strconv.ParseUint(portS, 10, 16) 91 | if err != nil { 92 | return nil, err 93 | } 94 | da.static.Port = uint16(port) 95 | } 96 | 97 | return da, nil 98 | } 99 | 100 | func (da *defAddress) SetDefaultAddr(a net.IP) { 101 | if da.static.Addr == "" { 102 | da.static.Addr = tcpip.Address(a) 103 | } 104 | } 105 | 106 | func (da *defAddress) Retrieve() *tcpip.FullAddress { 107 | da.Lock() 108 | defer da.Unlock() 109 | if da.label == "" || time.Now().Sub(da.fetched) <= dnsTTL { 110 | return &da.static 111 | } 112 | da.fetched = time.Now() 113 | 114 | ip, port, err := FullResolve(da.label) 115 | if err != nil { 116 | // Failed to resolve 117 | da.error = err 118 | return nil 119 | } else { 120 | da.static.Addr = tcpip.Address(ip) 121 | if port != 0 { 122 | da.static.Port = uint16(port) 123 | } 124 | da.error = nil 125 | } 126 | return &da.static 127 | } 128 | 129 | func (da *defAddress) String() string { 130 | static := da.Retrieve() 131 | if static == nil { 132 | return fmt.Sprintf("%s-failed", da.label) 133 | } 134 | return fmt.Sprintf("%s:%d", net.IP(da.static.Addr).String(), da.static.Port) 135 | } 136 | 137 | func (da *defAddress) GetTCPAddr() *net.TCPAddr { 138 | static := da.Retrieve() 139 | if static == nil { 140 | return nil 141 | } 142 | 143 | return &net.TCPAddr{ 144 | IP: net.IP(static.Addr), 145 | Port: int(static.Port), 146 | } 147 | } 148 | 149 | func (da *defAddress) GetUDPAddr() *net.UDPAddr { 150 | static := da.Retrieve() 151 | if static == nil { 152 | return nil 153 | } 154 | 155 | return &net.UDPAddr{ 156 | IP: net.IP(static.Addr), 157 | Port: int(static.Port), 158 | } 159 | } 160 | 161 | func simpleLookupHost(resolver *net.Resolver, label string) (net.IP, error) { 162 | addrs, err := resolver.LookupHost(context.Background(), label) 163 | if err != nil { 164 | // On resolution failure, error out 165 | return nil, err 166 | } 167 | if len(addrs) < 1 { 168 | return nil, fmt.Errorf("Empty dns reponse for %q", label) 169 | } 170 | 171 | // prefer IPv4. No real reason. 172 | for _, addr := range addrs { 173 | ip := netParseIP(addr) 174 | if ip.To4() != nil { 175 | return ip.To4(), nil 176 | } 177 | } 178 | 179 | ip := netParseIP(addrs[0]) 180 | if ip == nil { 181 | return nil, fmt.Errorf("Empty dns reponse for %q", label) 182 | } 183 | return ip, nil 184 | } 185 | 186 | func FullResolve(label string) (net.IP, uint16, error) { 187 | port := uint16(0) 188 | p := strings.SplitN(label, "@", 2) 189 | if len(p) == 2 { 190 | srvQuery, dnsSrv := p[0], p[1] 191 | if !strings.HasPrefix(dnsSrv, "srv-") { 192 | return nil, 0, fmt.Errorf("Unknown dns type %q", dnsSrv) 193 | } 194 | 195 | dnsSrvAddr := dnsSrv[4:] 196 | if !strings.Contains(dnsSrvAddr, ":") { 197 | dnsSrvAddr = fmt.Sprintf("127.0.0.1:%s", dnsSrvAddr) 198 | } 199 | r := &net.Resolver{ 200 | PreferGo: true, 201 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 202 | d := net.Dialer{ 203 | Timeout: time.Duration(3 * time.Second), 204 | } 205 | return d.DialContext(ctx, "udp", dnsSrvAddr) 206 | }, 207 | } 208 | _, srvAddrs, err := r.LookupSRV(context.Background(), "", "", srvQuery) 209 | if err != nil || len(srvAddrs) == 0 { 210 | return nil, 0, fmt.Errorf("Failed to lookup SRV %q on %q", srvQuery, dnsSrvAddr) 211 | } 212 | 213 | // For effective resolution, allowing to utilize 214 | // /etc/hosts, trim the trailing dot if present. 215 | serviceLabel := srvAddrs[0].Target 216 | servicePort := srvAddrs[0].Port 217 | if strings.HasSuffix(serviceLabel, ".") { 218 | serviceLabel = serviceLabel[:len(serviceLabel)-1] 219 | } 220 | 221 | ip, err := simpleLookupHost(r, serviceLabel) 222 | if err == nil && ip != nil { 223 | return ip, servicePort, nil 224 | } 225 | 226 | // Fallthrough and try the OS resolver 227 | label = serviceLabel 228 | port = servicePort 229 | } 230 | 231 | ip, err := simpleLookupHost(net.DefaultResolver, label) 232 | if err != nil { 233 | return nil, 0, err 234 | } 235 | return ip, port, nil 236 | } 237 | 238 | func netParseOrResolveIP(h string) (_ip net.IP, _resolved bool, _err error) { 239 | ip := netParseIP(h) 240 | if ip != nil { 241 | return ip, false, nil 242 | } 243 | 244 | ip, _, err := FullResolve(h) 245 | return ip, true, err 246 | } 247 | 248 | func OutboundDial(srcIPs *SrcIPs, dst net.Addr) (net.Conn, error) { 249 | network := dst.Network() 250 | if network == "tcp" { 251 | dstTcp := dst.(*net.TCPAddr) 252 | var srcTcp *net.TCPAddr 253 | if srcIPs != nil && dstTcp.IP.To4() != nil && srcIPs.srcIPv4 != nil { 254 | srcTcp = &net.TCPAddr{IP: srcIPs.srcIPv4} 255 | } 256 | if srcIPs != nil && dstTcp.IP.To4() == nil && srcIPs.srcIPv6 != nil { 257 | srcTcp = &net.TCPAddr{IP: srcIPs.srcIPv6} 258 | } 259 | return net.DialTCP(network, srcTcp, dstTcp) 260 | } 261 | if network == "udp" { 262 | dstUdp := dst.(*net.UDPAddr) 263 | var srcUdp *net.UDPAddr 264 | if srcIPs != nil && dstUdp.IP.To4() != nil && srcIPs.srcIPv4 != nil { 265 | srcUdp = &net.UDPAddr{IP: srcIPs.srcIPv4} 266 | } 267 | if srcIPs != nil && dstUdp.IP.To4() == nil && srcIPs.srcIPv6 != nil { 268 | srcUdp = &net.UDPAddr{IP: srcIPs.srcIPv6} 269 | } 270 | return net.DialUDP(network, srcUdp, dstUdp) 271 | } 272 | return nil, fmt.Errorf("not tcp/udp") 273 | } 274 | -------------------------------------------------------------------------------- /netns_utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | specs "github.com/opencontainers/runtime-spec/specs-go" 8 | "gvisor.dev/gvisor/runsc/specutils" 9 | ) 10 | 11 | func joinNetNS(nsPath string, run func()) error { 12 | ch := make(chan error, 2) 13 | go func() { 14 | runtime.LockOSThread() 15 | _, err := specutils.ApplyNS(specs.LinuxNamespace{ 16 | Type: specs.NetworkNamespace, 17 | Path: nsPath, 18 | }) 19 | if err != nil { 20 | runtime.UnlockOSThread() 21 | ch <- fmt.Errorf("joining net namespace %q: %v", nsPath, err) 22 | return 23 | } 24 | run() 25 | ch <- nil 26 | }() 27 | // Here is a big hack. Avoid restoring netns. Allow golang to 28 | // reap the thread, by not calling runtime.UnlockOSThread(). 29 | // This will avoid any errors from restoreNS(). 30 | 31 | err := <-ch 32 | return err 33 | } 34 | -------------------------------------------------------------------------------- /pp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "net" 8 | "strconv" 9 | ) 10 | 11 | var ( 12 | ErrUnexpectedEOF = io.ErrUnexpectedEOF 13 | ErrBadMagic = errors.New("spp: bad magic number") 14 | ) 15 | 16 | // Coverts IP address to its IPv6 representation. Nil or wrong length 17 | // IP addresses map to :: (all zeros). 18 | func MustTo16(ip net.IP) net.IP { 19 | ip = ip.To16() 20 | if ip != nil { 21 | return ip 22 | } 23 | return net.IPv6zero 24 | } 25 | 26 | func DecodeSPP(b []byte) (int, *net.UDPAddr, *net.UDPAddr, error) { 27 | if len(b) < 38 { 28 | return 0, nil, nil, ErrUnexpectedEOF 29 | } 30 | 31 | var ( 32 | clientIP [16]byte 33 | proxyIP [16]byte 34 | ) 35 | 36 | if b[0] != 0x56 || b[1] != 0xec { 37 | return 0, nil, nil, ErrBadMagic 38 | } 39 | copy(clientIP[:], b[2:18]) 40 | copy(proxyIP[:], b[18:34]) 41 | clientPort := (int(b[34]) << 8) | int(b[35]) 42 | proxyPort := (int(b[36]) << 8) | int(b[37]) 43 | 44 | c := &net.UDPAddr{ 45 | IP: net.IP(clientIP[:]), 46 | Port: clientPort, 47 | } 48 | p := &net.UDPAddr{ 49 | IP: net.IP(proxyIP[:]), 50 | Port: proxyPort, 51 | } 52 | 53 | return 38, c, p, nil 54 | } 55 | 56 | func EncodeSPP(b []byte, c, p *net.UDPAddr) (int, error) { 57 | if len(b) < 38 { 58 | return 0, ErrUnexpectedEOF 59 | } 60 | 61 | b[0] = 0x56 62 | b[1] = 0xec 63 | copy(b[2:18], MustTo16(c.IP)) 64 | copy(b[18:34], MustTo16(p.IP)) 65 | b[34] = byte(c.Port >> 8) 66 | b[35] = byte(c.Port) 67 | b[36] = byte(p.Port >> 8) 68 | b[37] = byte(p.Port) 69 | return 38, nil 70 | } 71 | 72 | func DecodePP(b []byte) (int, *net.TCPAddr, *net.TCPAddr, error) { 73 | var line []byte 74 | var n int 75 | for i, c := range b { 76 | if c == '\n' { 77 | line = b[:i+1] 78 | n = i + 1 79 | break 80 | } 81 | } 82 | 83 | if line == nil { 84 | return 0, nil, nil, ErrUnexpectedEOF 85 | } 86 | switch { 87 | case bytes.HasPrefix(line, []byte("PROXY TCP4 ")) && bytes.HasSuffix(line, []byte("\r\n")): 88 | line = line[11 : len(line)-2] 89 | case bytes.HasPrefix(line, []byte("PROXY TCP6 ")) && bytes.HasSuffix(line, []byte("\r\n")): 90 | line = line[11 : len(line)-2] 91 | default: 92 | return n, nil, nil, ErrBadMagic 93 | } 94 | 95 | p := bytes.SplitN(line, []byte(" "), 4) 96 | if len(p) != 4 { 97 | return n, nil, nil, ErrBadMagic 98 | } 99 | 100 | srcIP := net.ParseIP(string(p[0])) 101 | dstIP := net.ParseIP(string(p[1])) 102 | srcPort, e1 := strconv.ParseUint(string(p[2]), 10, 16) 103 | dstPort, e2 := strconv.ParseUint(string(p[3]), 10, 16) 104 | if srcIP == nil || dstIP == nil || e1 != nil || e2 != nil { 105 | return n, nil, nil, ErrBadMagic 106 | } 107 | s := &net.TCPAddr{ 108 | IP: srcIP, 109 | Port: int(srcPort), 110 | } 111 | 112 | d := &net.TCPAddr{ 113 | IP: dstIP, 114 | Port: int(dstPort), 115 | } 116 | return n, s, d, nil 117 | } 118 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // For each pair of connections, there may be four errors. Error on 10 | // reading the local/host conn, error on writing to local/host conn, 11 | // error on reading from remote/guest end, error on writing to 12 | // remote/guest end. It's important to distinguish which one was 13 | // first, so 0 means LocalRead, 1 means LocalWrite, 2 means RemoteRead 14 | // and 3 means RemoteWrite was first. 15 | type ProxyError struct { 16 | LocalRead error 17 | LocalWrite error 18 | RemoteRead error 19 | RemoteWrite error 20 | First int 21 | } 22 | 23 | // See this and cry: https://github.com/golang/go/issues/4373 24 | func ErrIsMyFault(err error) bool { 25 | s := err.Error() 26 | return strings.HasSuffix(s, "use of closed network connection") 27 | } 28 | 29 | func (pe ProxyError) String() string { 30 | x := []string{ 31 | fmt.Sprintf("%s", pe.LocalRead), 32 | fmt.Sprintf("%s", pe.LocalWrite), 33 | fmt.Sprintf("%s", pe.RemoteRead), 34 | fmt.Sprintf("%s", pe.RemoteWrite), 35 | } 36 | if pe.LocalRead == nil || ErrIsMyFault(pe.LocalRead) { 37 | x[0] = "0" 38 | } 39 | if pe.LocalWrite == nil || ErrIsMyFault(pe.LocalWrite) { 40 | x[1] = "0" 41 | } 42 | if pe.RemoteRead == nil || ErrIsMyFault(pe.RemoteRead) { 43 | x[2] = "0" 44 | } 45 | if pe.RemoteWrite == nil || ErrIsMyFault(pe.RemoteWrite) { 46 | x[3] = "0" 47 | } 48 | x[pe.First] = fmt.Sprintf("[%s]", x[pe.First]) 49 | 50 | return fmt.Sprintf("l=%s/%s r=%s/%s", x[0], x[1], x[2], x[3]) 51 | } 52 | 53 | const ( 54 | MINPROXYBUFSIZE = 2 * 1024 55 | MAXPROXYBUFSIZE = 256 * 1024 56 | ) 57 | 58 | type Closer interface { 59 | CloseRead() error 60 | CloseWrite() error 61 | } 62 | 63 | func proxyOneFlow( 64 | in, out KaConn, 65 | readErrPtr, writeErrPtr *error, 66 | doneCh chan int, 67 | scDir int, sppHeader []byte) { 68 | var ( 69 | tmpBuf []byte 70 | buf = make([]byte, MINPROXYBUFSIZE) 71 | ) 72 | 73 | // For UDP sadly we need to allocate 64KiB per flow, because 74 | // the packet may be fragmented. 75 | if _, ok := out.(*KaUDPConn); ok { 76 | buf = make([]byte, 64*1024) 77 | } 78 | 79 | for { 80 | n, err := in.Read(buf[:]) 81 | if err != nil { 82 | *readErrPtr = err 83 | break 84 | } 85 | 86 | wbuf := buf[:n] 87 | if sppHeader != nil { 88 | if scDir == 0 { 89 | if len(wbuf) >= len(sppHeader) { 90 | wbuf = wbuf[len(sppHeader):] 91 | } else { 92 | // swallow the packet on error 93 | continue 94 | } 95 | } 96 | if scDir == 1 { 97 | if cap(tmpBuf) < cap(buf) { 98 | tmpBuf = make([]byte, cap(buf)) 99 | } 100 | copy(tmpBuf, sppHeader) 101 | copy(tmpBuf[len(sppHeader):], wbuf) 102 | wbuf = tmpBuf[:len(sppHeader)+len(wbuf)] 103 | } 104 | } 105 | 106 | // Write must return n==len(buf) or err 107 | // https://golang.org/pkg/io/#Writer 108 | _, err = out.Write(wbuf) 109 | if err != nil { 110 | *writeErrPtr = err 111 | break 112 | } 113 | 114 | // Heuristics: Start with small buffer and bump it up 115 | // if full reads, up to some defined max. 116 | if n == len(buf) && len(buf) < MAXPROXYBUFSIZE { 117 | buf = make([]byte, len(buf)*2) 118 | } 119 | } 120 | 121 | // Synchronize with parent. It's important to do this _before_ 122 | // closing sockets, since .Close() might trigger the other 123 | // proxy goroutine to exit with "use of closed fd" 124 | // error. There is no race here. We can push to channel 125 | // without closing yet. 126 | doneCh <- scDir 127 | 128 | in.SetTimeouts(5*time.Second, 2) 129 | out.SetTimeouts(5*time.Second, 2) 130 | 131 | if c, ok := in.(Closer); ok { 132 | c.CloseRead() 133 | } else { 134 | in.Close() 135 | } 136 | if c, ok := out.(Closer); ok { 137 | c.CloseWrite() 138 | } else { 139 | out.Close() 140 | } 141 | } 142 | 143 | func connSplice(local KaConn, remote KaConn, sppHeader []byte) ProxyError { 144 | var ( 145 | pe ProxyError 146 | doneCh = make(chan int, 2) 147 | ) 148 | 149 | local.SetTimeouts(125*time.Second, 4) 150 | remote.SetTimeouts(125*time.Second, 4) 151 | 152 | go proxyOneFlow(local, remote, &pe.LocalRead, 153 | &pe.RemoteWrite, doneCh, 0, sppHeader) 154 | proxyOneFlow(remote, local, &pe.RemoteRead, 155 | &pe.LocalWrite, doneCh, 1, sppHeader) 156 | first := <-doneCh 157 | _ = <-doneCh 158 | switch { 159 | case first == 0 && pe.LocalRead != nil: 160 | pe.First = 0 161 | case first == 0 && pe.RemoteWrite != nil: 162 | pe.First = 3 163 | case first == 1 && pe.RemoteRead != nil: 164 | pe.First = 2 165 | case first == 1 && pe.LocalWrite != nil: 166 | pe.First = 1 167 | } 168 | local.Close() 169 | remote.Close() 170 | return pe 171 | } 172 | -------------------------------------------------------------------------------- /routing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" 8 | "gvisor.dev/gvisor/pkg/tcpip/stack" 9 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 10 | "gvisor.dev/gvisor/pkg/tcpip/transport/udp" 11 | "gvisor.dev/gvisor/pkg/waiter" 12 | ) 13 | 14 | func FirewallRoutingBlock(state *State, addr net.Addr) (_block bool) { 15 | if state.denyRange.Contains(addr) { 16 | return true 17 | } 18 | 19 | if state.allowRange.Contains(addr) { 20 | return false 21 | } 22 | 23 | addrIP := netAddrIP(addr) 24 | // Is the IP on hard deny list? 25 | if IPNetContains(state.StaticRoutingDeny, addrIP) { 26 | // Firewall deny 27 | return true 28 | } 29 | 30 | // Is the ip in local routes? 31 | if state.localRoutes.Contains(addrIP) { 32 | return !state.enableHostRouting 33 | } 34 | 35 | return !state.enableInternetRouting 36 | } 37 | 38 | func UdpRoutingHandler(s *stack.Stack, state *State) func(*udp.ForwarderRequest) { 39 | h := func(r *udp.ForwarderRequest) { 40 | // Create endpoint as quickly as possible to avoid UDP 41 | // race conditions, when user sends multiple frames 42 | // one after another. 43 | var wq waiter.Queue 44 | ep, err := r.CreateEndpoint(&wq) 45 | if err != nil { 46 | fmt.Printf("r.CreateEndpoint() = %v\n", err) 47 | return 48 | } 49 | 50 | id := r.ID() 51 | loc := &net.UDPAddr{ 52 | IP: netParseIP(id.LocalAddress.String()), 53 | Port: int(id.LocalPort), 54 | } 55 | 56 | rf, ok := state.remoteUdpFwd[loc.String()] 57 | if ok == false { 58 | if block := FirewallRoutingBlock(state, loc); block { 59 | ep.Close() 60 | return 61 | } 62 | } 63 | 64 | xconn := gonet.NewUDPConn(s, &wq, ep) 65 | conn := &KaUDPConn{Conn: xconn} 66 | 67 | if rf != nil && rf.kaEnable && rf.kaInterval == 0 { 68 | conn.closeOnWrite = true 69 | } 70 | 71 | go func() { 72 | if rf != nil { 73 | RemoteForward(conn, &state.srcIPs, rf) 74 | } else { 75 | RoutingForward(conn, &state.srcIPs, loc) 76 | } 77 | }() 78 | } 79 | return h 80 | } 81 | 82 | func TcpRoutingHandler(state *State) func(*tcp.ForwarderRequest) { 83 | h := func(r *tcp.ForwarderRequest) { 84 | id := r.ID() 85 | loc := &net.TCPAddr{ 86 | IP: netParseIP(id.LocalAddress.String()), 87 | Port: int(id.LocalPort), 88 | } 89 | 90 | rf, ok := state.remoteTcpFwd[loc.String()] 91 | if ok == false { 92 | if block := FirewallRoutingBlock(state, loc); block { 93 | // In theory we could pass a bit of 94 | // data to the guest here. Like: 95 | // blocked on firewall - never send 96 | // RST, end host is failing - always 97 | // send RST. But this is wrong. If on 98 | // firewall we know the end host is 99 | // unreachable - just tell the guest. 100 | // Maybe we could use parametrized 101 | // ICMP / RST in future. 102 | r.Complete(true) 103 | return 104 | } 105 | } 106 | 107 | var wq waiter.Queue 108 | ep, errx := r.CreateEndpoint(&wq) 109 | if errx != nil { 110 | fmt.Printf("r.CreateEndpoint() = %v\n", errx) 111 | return 112 | } 113 | r.Complete(false) 114 | ep.SocketOptions().SetDelayOption(true) 115 | 116 | xconn := gonet.NewTCPConn(&wq, ep) 117 | conn := &GonetTCPConn{xconn, ep} 118 | 119 | go func() { 120 | if rf != nil { 121 | RemoteForward(conn, &state.srcIPs, rf) 122 | } else { 123 | RoutingForward(conn, &state.srcIPs, loc) 124 | } 125 | }() 126 | } 127 | return h 128 | } 129 | 130 | func RoutingForward(guest KaConn, srcIPs *SrcIPs, loc net.Addr) { 131 | // Cache guest.RemoteAddr() because it becomes nil on 132 | // guest.Close(). 133 | guestRemoteAddr := guest.RemoteAddr() 134 | 135 | var pe ProxyError 136 | xhost, err := OutboundDial(srcIPs, loc) 137 | if err != nil { 138 | SetResetOnClose(guest) 139 | guest.Close() 140 | pe.RemoteRead = err 141 | pe.First = 2 142 | if logConnections { 143 | fmt.Printf("[!] %s://%s/%s Routing conn error: %s\n", 144 | loc.Network(), 145 | guestRemoteAddr, 146 | loc.String(), 147 | pe) 148 | } 149 | } else { 150 | if logConnections { 151 | fmt.Printf("[+] %s://%s/%s-%s Routing conn new\n", 152 | loc.Network(), 153 | guestRemoteAddr, 154 | xhost.LocalAddr(), 155 | xhost.RemoteAddr()) 156 | } 157 | var host KaConn 158 | switch v := xhost.(type) { 159 | case *net.TCPConn: 160 | host = &KaTCPConn{v} 161 | case *net.UDPConn: 162 | host = &KaUDPConn{Conn: v} 163 | } 164 | pe = connSplice(guest, host, nil) 165 | if logConnections { 166 | fmt.Printf("[-] %s://%s/%s-%s Routing conn done: %s\n", 167 | loc.Network(), 168 | guestRemoteAddr, 169 | xhost.LocalAddr(), 170 | xhost.RemoteAddr(), 171 | pe) 172 | } 173 | } 174 | } 175 | 176 | func RemoteForward(guest KaConn, srcIPs *SrcIPs, rf *FwdAddr) { 177 | // Cache guest.RemoteAddr() because it becomes nil on 178 | // guest.Close(). 179 | guestRemoteAddr := guest.RemoteAddr() 180 | 181 | var pe ProxyError 182 | hostAddr, err := rf.HostAddr() 183 | if err != nil { 184 | // dns lookup error 185 | fmt.Printf("[!] %s://%s-%s/%s remote-fwd %v\n", 186 | rf.network, 187 | guestRemoteAddr, 188 | guest.LocalAddr(), 189 | rf.host.String(), 190 | err) 191 | return 192 | } 193 | xhost, err := OutboundDial(srcIPs, hostAddr) 194 | if err != nil { 195 | SetResetOnClose(guest) 196 | guest.Close() 197 | pe.RemoteRead = err 198 | pe.First = 2 199 | if logConnections { 200 | fmt.Printf("[!] %s://%s-%s/%s remote-fwd conn error: %s\n", 201 | rf.network, 202 | guestRemoteAddr, 203 | guest.LocalAddr(), 204 | rf.host.String(), 205 | pe) 206 | } 207 | } else { 208 | if logConnections { 209 | fmt.Printf("[+] %s://%s-%s/%s-%s remote-fwd conn new\n", 210 | rf.network, 211 | guestRemoteAddr, 212 | guest.LocalAddr(), 213 | xhost.LocalAddr(), 214 | xhost.RemoteAddr()) 215 | } 216 | var host KaConn 217 | switch v := xhost.(type) { 218 | case *net.TCPConn: 219 | host = &KaTCPConn{v} 220 | case *net.UDPConn: 221 | host = &KaUDPConn{Conn: v} 222 | } 223 | pe = connSplice(guest, host, nil) 224 | if logConnections { 225 | fmt.Printf("[-] %s://%s-%s/%s-%s remote-fwd conn done: %s\n", 226 | rf.network, 227 | guestRemoteAddr, 228 | guest.LocalAddr(), 229 | xhost.LocalAddr(), 230 | xhost.RemoteAddr(), 231 | pe) 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "os" 8 | "time" 9 | 10 | "gvisor.dev/gvisor/pkg/tcpip" 11 | "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" 12 | "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" 13 | "gvisor.dev/gvisor/pkg/tcpip/link/rawfile" 14 | "gvisor.dev/gvisor/pkg/tcpip/link/tun" 15 | "gvisor.dev/gvisor/pkg/tcpip/network/arp" 16 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 17 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" 18 | "gvisor.dev/gvisor/pkg/tcpip/stack" 19 | "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" 20 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 21 | "gvisor.dev/gvisor/pkg/tcpip/transport/udp" 22 | "gvisor.dev/gvisor/pkg/waiter" 23 | ) 24 | 25 | func GetTunTap(netNsPath string, ifName string) (int, bool, uint, error) { 26 | var ( 27 | err error 28 | ) 29 | 30 | type tunState struct { 31 | fd int 32 | tapMode bool 33 | mtu uint32 34 | err error 35 | } 36 | 37 | ch := make(chan tunState, 2) 38 | run := func() { 39 | fmt.Fprintf(os.Stderr, "[.] Opening tun interface %s\n", ifName) 40 | mtu, err := rawfile.GetMTU(ifName) 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "[!] GetMTU(%s) = %s\n", ifName, err) 43 | ch <- tunState{err: err} 44 | return 45 | } 46 | 47 | tapMode := false 48 | 49 | fd, err := tun.Open(ifName) 50 | if err != nil { 51 | tapMode = true 52 | fd, err = tun.OpenTAP(ifName) 53 | if err != nil { 54 | fmt.Fprintf(os.Stderr, "[!] open(%s) = %s\n", ifName, err) 55 | ch <- tunState{err: err} 56 | return 57 | } 58 | } 59 | ch <- tunState{fd, tapMode, mtu, nil} 60 | } 61 | 62 | if netNsPath != "" { 63 | fmt.Fprintf(os.Stderr, "[.] Joininig netns %s\n", netNsPath) 64 | err = joinNetNS(netNsPath, run) 65 | if err != nil { 66 | fmt.Fprintf(os.Stderr, "[!] Can't join netns %s: %s\n", netNsPath, err) 67 | return 0, false, 0, err 68 | } 69 | } else { 70 | run() 71 | } 72 | 73 | s := <-ch 74 | return s.fd, s.tapMode, uint(s.mtu), s.err 75 | } 76 | 77 | func NewStack(rcvBufferSize, sndBufferSize int) *stack.Stack { 78 | // Create the stack with ipv4 and tcp protocols, then add a tun-based 79 | // NIC and ipv4 address. 80 | opts := stack.Options{ 81 | NetworkProtocols: []stack.NetworkProtocolFactory{ 82 | ipv4.NewProtocol, 83 | ipv6.NewProtocol, 84 | arp.NewProtocol}, 85 | TransportProtocols: []stack.TransportProtocolFactory{ 86 | tcp.NewProtocol, 87 | udp.NewProtocol, 88 | icmp.NewProtocol4, 89 | icmp.NewProtocol6}, 90 | HandleLocal: false, 91 | } 92 | 93 | s := stack.New(opts) 94 | s.SetForwarding(ipv4.ProtocolNumber, true) 95 | s.SetForwarding(ipv6.ProtocolNumber, true) 96 | 97 | { 98 | opt := tcpip.TCPSACKEnabled(true) 99 | s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt) 100 | } 101 | { 102 | opt := tcpip.DefaultTTLOption(64) 103 | s.SetNetworkProtocolOption(ipv4.ProtocolNumber, &opt) 104 | s.SetNetworkProtocolOption(ipv6.ProtocolNumber, &opt) 105 | } 106 | 107 | // We expect no packet loss, therefore we can bump 108 | // buffers. Too large buffers thrash cache, so there is litle 109 | // point in too large buffers. 110 | { 111 | opt := tcpip.TCPReceiveBufferSizeRangeOption{Min: 1, Default: rcvBufferSize, Max: rcvBufferSize} 112 | s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt) 113 | } 114 | { 115 | opt := tcpip.TCPSendBufferSizeRangeOption{Min: 1, Default: sndBufferSize, Max: sndBufferSize} 116 | s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt) 117 | } 118 | 119 | // Enable Receive Buffer Auto-Tuning, see: 120 | // https://github.com/google/gvisor/issues/1666 121 | { 122 | opt := tcpip.TCPModerateReceiveBufferOption(true) 123 | s.SetTransportProtocolOption(tcp.ProtocolNumber, 124 | &opt) 125 | } 126 | return s 127 | } 128 | 129 | func createLinkEP(s *stack.Stack, tunFd int, tapMode bool, macAddress net.HardwareAddr, tapMtu uint32) (stack.LinkEndpoint, error) { 130 | parms := fdbased.Options{FDs: []int{tunFd}, 131 | MTU: tapMtu, 132 | RXChecksumOffload: true, 133 | } 134 | if tapMode { 135 | parms.EthernetHeader = true 136 | parms.Address = tcpip.LinkAddress(macAddress) 137 | } 138 | 139 | return fdbased.New(&parms) 140 | } 141 | 142 | func createNIC(s *stack.Stack, nic tcpip.NICID, linkEP stack.LinkEndpoint) error { 143 | if err := s.CreateNIC(nic, linkEP); err != nil { 144 | fmt.Fprintf(os.Stderr, "[!] CreateNIC(%s) = %s\n", ifName, err) 145 | return fmt.Errorf("%s", err) 146 | } 147 | 148 | s.SetSpoofing(nic, true) 149 | 150 | // In past we did s.AddAddressRange to assign 0.0.0.0/0 onto 151 | // the interface. We need that to be able to terminate all the 152 | // incoming connections - to any ip. AddressRange API has been 153 | // removed and the suggested workaround is to use Promiscous 154 | // mode. https://github.com/google/gvisor/issues/3876 155 | s.SetPromiscuousMode(nic, true) 156 | return nil 157 | } 158 | 159 | func MustSubnet(ipNet *net.IPNet) *tcpip.Subnet { 160 | subnet, errx := tcpip.NewSubnet(tcpip.Address(ipNet.IP), tcpip.AddressMask(ipNet.Mask)) 161 | if errx != nil { 162 | panic(fmt.Sprintf("Unable to MustSubnet(%s): %s", ipNet, errx)) 163 | } 164 | return &subnet 165 | } 166 | 167 | func StackRoutingSetup(s *stack.Stack, nic tcpip.NICID, assignNet string) { 168 | ipAddr, ipNet, err := net.ParseCIDR(assignNet) 169 | if err != nil { 170 | panic(fmt.Sprintf("Unable to ParseCIDR(%s): %s", assignNet, err)) 171 | } 172 | 173 | if ipAddr.To4() != nil { 174 | s.AddAddress(nic, ipv4.ProtocolNumber, tcpip.Address(ipAddr.To4())) 175 | } else { 176 | s.AddAddress(nic, ipv6.ProtocolNumber, tcpip.Address(ipAddr)) 177 | } 178 | 179 | rt := s.GetRouteTable() 180 | rt = append(rt, tcpip.Route{ 181 | Destination: *MustSubnet(ipNet), 182 | NIC: nic, 183 | }) 184 | s.SetRouteTable(rt) 185 | } 186 | 187 | func StackPrimeArp(s *stack.Stack, nic tcpip.NICID, ip net.IP) { 188 | // Prime the arp cache. Otherwise we get "no remote link 189 | // address" on first write. 190 | if ip.To4() != nil { 191 | s.GetLinkAddress(nic, 192 | tcpip.Address(ip.To4()), 193 | "", 194 | ipv4.ProtocolNumber, 195 | nil) 196 | } 197 | 198 | } 199 | 200 | func GonetDialTCP(s *stack.Stack, laddr, raddr *tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*GonetTCPConn, error) { 201 | // Create TCP endpoint, then connect. 202 | var wq waiter.Queue 203 | ep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq) 204 | if err != nil { 205 | return nil, errors.New(err.String()) 206 | } 207 | 208 | if laddr != nil { 209 | if err := ep.Bind(*laddr); err != nil { 210 | ep.Close() 211 | return nil, errors.New(err.String()) 212 | } 213 | } 214 | 215 | // Create wait queue entry that notifies a channel. 216 | // 217 | // We do this unconditionally as Connect will always return an error. 218 | waitEntry, notifyCh := waiter.NewChannelEntry(nil) 219 | wq.EventRegister(&waitEntry, waiter.EventOut) 220 | defer wq.EventUnregister(&waitEntry) 221 | 222 | err = ep.Connect(*raddr) 223 | if err == tcpip.ErrConnectStarted { 224 | select { 225 | case <-notifyCh: 226 | } 227 | 228 | err = ep.LastError() 229 | } 230 | if err != nil { 231 | ep.Close() 232 | return nil, errors.New(err.String()) 233 | } 234 | 235 | return &GonetTCPConn{gonet.NewTCPConn(&wq, ep), ep}, nil 236 | } 237 | 238 | type GonetTCPConn struct { 239 | *gonet.TCPConn 240 | ep tcpip.Endpoint 241 | } 242 | 243 | func (c *GonetTCPConn) SetTimeouts(kaInterval time.Duration, kaCount int) error { 244 | c.ep.SocketOptions().SetKeepAlive(true) 245 | { 246 | opt := tcpip.KeepaliveIdleOption(kaInterval) 247 | c.ep.SetSockOpt(&opt) 248 | } 249 | { 250 | opt := tcpip.KeepaliveIntervalOption(kaInterval) 251 | c.ep.SetSockOpt(&opt) 252 | } 253 | c.ep.SetSockOptInt(tcpip.KeepaliveCountOption, kaCount) 254 | { 255 | opt := tcpip.TCPUserTimeoutOption(UserTimeoutFromKeepalive(kaInterval, kaCount)) 256 | c.ep.SetSockOpt(&opt) 257 | } 258 | return nil 259 | } 260 | 261 | func networkProtocolNumberFromIP(ip net.IP) tcpip.NetworkProtocolNumber { 262 | var nn tcpip.NetworkProtocolNumber 263 | switch { 264 | case ip == nil: 265 | case ip.To4() != nil: 266 | nn = ipv4.ProtocolNumber 267 | case ip.To16() != nil: 268 | nn = ipv6.ProtocolNumber 269 | } 270 | return nn 271 | } 272 | 273 | func GonetDialUDP(s *stack.Stack, laddr, raddr *tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*KaUDPConn, error) { 274 | c, err := gonet.DialUDP( 275 | s, 276 | laddr, 277 | raddr, 278 | network) 279 | if err != nil { 280 | return nil, err 281 | } 282 | return &KaUDPConn{Conn: c}, nil 283 | } 284 | 285 | func GonetDial(s *stack.Stack, laddr, raddr net.Addr) (KaConn, error) { 286 | switch raddr.Network() { 287 | case "tcp": 288 | return GonetDialTCP(s, 289 | FullAddressFromAddr(laddr), 290 | FullAddressFromAddr(raddr), 291 | networkProtocolNumberFromIP(netAddrIP(raddr)), 292 | ) 293 | case "udp": 294 | return GonetDialUDP(s, 295 | FullAddressFromAddr(laddr), 296 | FullAddressFromAddr(raddr), 297 | networkProtocolNumberFromIP(netAddrIP(raddr)), 298 | ) 299 | } 300 | return nil, nil 301 | } 302 | -------------------------------------------------------------------------------- /test-gvisor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RUNSC="XXX/runsc" 4 | GVFLAGS="--net-raw" 5 | SLIRPNETSTACK="./bin/slirpnetstack" 6 | 7 | if [ ! -f config.json ]; then 8 | ${RUNSC} spec 9 | EXTRA_CAPS='"CAP_SETGID", "CAP_SETUID", "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_RAW"' 10 | sed -i "s#\(\"CAP_NET_BIND_SERVICE\"\)#\1, ${EXTRA_CAPS}#" config.json 11 | sed -i 's#readonly": true#readonly": false#' config.json 12 | sed -i 's#\("TERM=xterm"\)#\1,\n"DEBIAN_FRONTEND=noninteractive"#' config.json 13 | fi 14 | 15 | if [ ! -d rootfs ]; then 16 | mkdir rootfs 17 | docker export $(docker create ubuntu:bionic) | tar -xf - -C rootfs 18 | echo "nameserver 1.1.1.1" > rootfs/etc/resolv.conf 19 | fi 20 | 21 | ${RUNSC} kill hello || true 22 | ${RUNSC} delete hello || true 23 | 24 | ulimit -n 1048576 25 | ${RUNSC} ${GVFLAGS} create hello 26 | 27 | NSPID=`${RUNSC} state hello | jq .pid` 28 | nsenter -n -t ${NSPID} bash -c " \ 29 | ip link set lo up; \ 30 | ip tuntap add mode tap name tun0; \ 31 | ip link set tun0 mtu 65521; \ 32 | ip link set tun0 up; \ 33 | ip addr add 10.0.2.100/24 dev tun0; \ 34 | ip route add 0.0.0.0/0 via 10.0.2.2 dev tun0;" 35 | 36 | # IPv6 support 37 | if [ ]; then 38 | nsenter -n -t ${NSPID} bash -c " \ 39 | ip addr add fd00::100/64 dev tun0; \ 40 | ip route add ::/0 via fd00::2 dev tun0;" 41 | fi 42 | 43 | echo "[*] Starting gvisor" 44 | ${RUNSC} ${GVFLAGS} start hello 45 | 46 | echo "To enter the container run:" 47 | echo " ${RUNSC} ${GVFLAGS} exec --console-socket /tmp/pty.sock hello bash" 48 | 49 | echo "[*] Running slirpnetstack" 50 | ${SLIRPNETSTACK} -interface tun0 -netns /proc/${NSPID}/ns/net 51 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .test_basic import * 2 | -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | from . import utils 2 | import ctypes 3 | import errno 4 | import functools 5 | import io 6 | import os 7 | import random 8 | import re 9 | import shlex 10 | import signal 11 | import socket 12 | import subprocess 13 | import sys 14 | import tempfile 15 | import unittest 16 | 17 | LIBC = ctypes.CDLL("libc.so.6") 18 | SLIRPNETSTACKBIN = os.environ.get('SLIRPNETSTACKBIN') 19 | DEBUG = bool(os.environ.get('DEBUG')) 20 | CLONE_NEWNET = 0x40000000 21 | ORIGINAL_NET_NS = open("/proc/self/ns/net", 'rb') 22 | MOCKUDPECHO = os.environ.get('MOCKUDPECHO', './bin/mockudpecho') 23 | MOCKTCPECHO = os.environ.get('MOCKTCPECHO', './bin/mocktcpecho') 24 | MOCKDNS = os.environ.get('MOCKDNS', './bin/mockdns') 25 | IP_FREEBIND = 15 26 | 27 | execno = 0 28 | def run(argv1=[], close_fds=True): 29 | global execno 30 | execno += 1 31 | argv0 = shlex.split(SLIRPNETSTACKBIN % {"nr": execno}) 32 | 33 | if isinstance(argv1, str): 34 | argv1 = shlex.split(argv1) 35 | 36 | a = argv0 + argv1 37 | 38 | return Process(a, close_fds=close_fds) 39 | 40 | 41 | class Process(object): 42 | def __init__(self, argv, close_fds=True): 43 | last_cmd = utils.encode_shell(argv) 44 | if DEBUG: 45 | print("[r] Running: %s" % (last_cmd,)) 46 | 47 | self.p = subprocess.Popen(argv, 48 | stdout=subprocess.PIPE, 49 | stderr=subprocess.PIPE, 50 | close_fds=close_fds) 51 | self.rc = None 52 | 53 | def stdout_line(self): 54 | while True: 55 | o = self.p.stdout.readline().decode() 56 | if o == 'PASS\n' or o.startswith("coverage: "): 57 | continue 58 | return o 59 | 60 | def stdout_log(self): 61 | l = self.stdout_line() 62 | return json.loads(l) 63 | 64 | def stderr_line(self): 65 | while True: 66 | e = self.p.stderr.readline().decode() 67 | if not e: 68 | continue 69 | if e.startswith('[o]'): 70 | print(e) 71 | continue 72 | if e.startswith('panic'): 73 | while e: 74 | print(e.rstrip()) 75 | e = self.p.stderr.readline().decode() 76 | e = 'PANIC' 77 | return e 78 | 79 | def close(self, kill=True): 80 | '''Returns process return code.''' 81 | if self.p: 82 | if kill: 83 | # Ensure the process registers two signals by sending a combo of 84 | # SIGINT and SIGTERM. Sending the same signal two times is racy 85 | # because the process can't reliably detect how many times the 86 | # signal was sent. 87 | self.p.send_signal(signal.SIGINT) 88 | self.p.send_signal(signal.SIGTERM) 89 | self.rc = self.p.wait() 90 | self.p.stderr.close() 91 | self.p.stdout.close() 92 | 93 | self.p = None 94 | return self.rc 95 | 96 | def graceful_stop(self, wait=True): 97 | self.p.send_signal(signal.SIGINT) 98 | if wait: 99 | self.p.wait() 100 | 101 | class TestCase(unittest.TestCase): 102 | cleanups = None 103 | 104 | def prun(self, argv1=[], close_fds=True, netns=True): 105 | global execno 106 | execno += 1 107 | argv0 = shlex.split(SLIRPNETSTACKBIN % {"nr": execno}) 108 | 109 | if isinstance(argv1, str): 110 | argv1 = shlex.split(argv1) 111 | 112 | if netns: 113 | argv1 = argv1 + ["-netns=%s" % self.net_ns_path()] 114 | if '.cover' in SLIRPNETSTACKBIN: 115 | argv1 = ['--args=%s' % i for i in argv1] 116 | p = Process(argv0 + argv1, close_fds=close_fds) 117 | self._add_teardown(p) 118 | return p 119 | 120 | def get_tmp_filename(self, name): 121 | return os.path.join(self._tmpdir.name, name) 122 | 123 | def _add_teardown(self, item): 124 | if not self.cleanups: 125 | self.cleanups = [] 126 | self.cleanups.append(item) 127 | 128 | def setUp(self): 129 | prev_net_fd = open("/proc/self/ns/net", 'rb') 130 | r = LIBC.unshare(CLONE_NEWNET) 131 | if r != 0: 132 | print('[!] Are you running within "unshare -Ur" ? Need unshare() syscall.') 133 | sys.exit(-1) 134 | self.guest_net_fd = open("/proc/self/ns/net", 'rb') 135 | self._add_teardown(self.guest_net_fd) 136 | 137 | # mode tap, means ethernet headers 138 | os.system("ip link set lo up;" 139 | "ip tuntap add mode tap name tun0;" 140 | "ip link set tun0 mtu 65521;" 141 | "ip link set tun0 up;" 142 | "ip addr add 10.0.2.100/24 dev tun0;" 143 | "ip addr add fd00::100/64 dev tun0 nodad;" 144 | "ip route add 0.0.0.0/0 via 10.0.2.2 dev tun0;" 145 | "ip route add ::/0 via fd00::2 dev tun0;") 146 | w = subprocess.Popen(["/bin/sleep", "1073741824"]) 147 | self.guest_ns_pid = w.pid 148 | self._add_teardown(w) 149 | LIBC.setns(prev_net_fd.fileno(), CLONE_NEWNET) 150 | prev_net_fd.close() 151 | self._tmpdir = tempfile.TemporaryDirectory() 152 | self._add_teardown(self._tmpdir) 153 | 154 | def tearDown(self): 155 | while self.cleanups: 156 | item = self.cleanups.pop() 157 | if isinstance(item, subprocess.Popen): 158 | item.send_signal(signal.SIGINT) 159 | item.wait() 160 | elif isinstance(item, Process): 161 | item.close() 162 | if getattr(item, 'stdout', None): 163 | item.stdout.close() 164 | if getattr(item, 'stderr', None): 165 | item.stderr.close() 166 | elif isinstance(item, io.BufferedReader): 167 | item.close() 168 | elif isinstance(item, tempfile.TemporaryDirectory): 169 | item.cleanup() 170 | else: 171 | print("[!] Unknown cleanup type") 172 | print(type(item)) 173 | 174 | def net_ns_path(self): 175 | return "/proc/%s/ns/net" % self.guest_ns_pid 176 | 177 | def guest_netns(self): 178 | xself = self 179 | class controlled_execution: 180 | def __enter__(self): 181 | self.prev_net_fd = open("/proc/self/ns/net", 'rb') 182 | LIBC.setns(xself.guest_net_fd.fileno(), CLONE_NEWNET) 183 | def __exit__(self, type, value, traceback): 184 | LIBC.setns(self.prev_net_fd.fileno(), CLONE_NEWNET) 185 | self.prev_net_fd.close() 186 | return controlled_execution() 187 | 188 | def start_udp_echo(self, **kwargs): 189 | kwargs['tcp'] = False 190 | return self.start_echo(**kwargs) 191 | 192 | def start_tcp_echo(self, **kwargs): 193 | kwargs['tcp'] = True 194 | return self.start_echo(**kwargs) 195 | 196 | def start_echo(self, guest=False, log=False, tcp=True): 197 | if tcp: 198 | cmd = [MOCKTCPECHO] 199 | else: 200 | cmd = [MOCKUDPECHO] 201 | if log: 202 | cmd += ["-log"] 203 | if guest == False: 204 | p = Process(cmd) 205 | else: 206 | with self.guest_netns(): 207 | p = Process(cmd) 208 | echo_port = int(p.stdout_line()) 209 | self._add_teardown(p) 210 | if log: 211 | return echo_port, p.stdout_line 212 | else: 213 | return echo_port 214 | 215 | def start_dns(self, *kv): 216 | cmd = [MOCKDNS, *kv] 217 | p = Process(cmd) 218 | dns_port = int(p.stdout_line()) 219 | self._add_teardown(p) 220 | return dns_port, p 221 | 222 | def assertUdpEcho(self, *args, **kwargs): 223 | kwargs['udp'] = True 224 | s = utils.connect(*args, **kwargs) 225 | payload = b'ala%f\n' % random.random() 226 | s.sendall(payload) 227 | self.assertEqual(payload, s.recv(1024)) 228 | s.close() 229 | 230 | def assertTcpEcho(self, *args, **kwargs): 231 | s = utils.connect(*args, **kwargs) 232 | payload = b'bob%f\n' % random.random() 233 | s.sendall(payload) 234 | self.assertEqual(payload, s.recv(1024)) 235 | s.close() 236 | 237 | def assertTcpRefusedError(self, ip="127.0.0.1", port=0): 238 | with self.assertRaises(socket.error) as e: 239 | s = utils.connect(ip, port, cleanup=self) 240 | s.recv(1024) 241 | self.assertEqual(e.exception.errno, errno.ECONNREFUSED) 242 | 243 | def assertStartSync(self, p, fd=False): 244 | if not fd: 245 | self.assertIn("[.] Join", p.stderr_line()) 246 | self.assertIn("[.] Opening tun", p.stderr_line()) 247 | self.assertIn("Slirpnetstack started", p.stderr_line()) 248 | 249 | def assertListenLine(self, p, in_pattern): 250 | line = p.stdout_line().strip() 251 | self.assertIn(in_pattern, line) 252 | return int(line.split(":")[-1]) 253 | 254 | 255 | def isolateHostNetwork(): 256 | def decorate(fn): 257 | fn_name = fn.__name__ 258 | @functools.wraps(fn) 259 | def maybe(*args, **kw): 260 | prev_net_fd = open("/proc/self/ns/net", 'rb') 261 | r = LIBC.unshare(CLONE_NEWNET) 262 | if r != 0: 263 | print('[!] Are you running within "unshare -Ur" ? Need unshare() syscall.') 264 | sys.exit(-1) 265 | # mode tun, since we don't actually plan on anyone reading the other side. 266 | os.system("ip link set lo up;" 267 | "ip tuntap add mode tun name eth0;" 268 | "ip link set eth0 mtu 65521;" 269 | "ip link set eth0 up;" 270 | "ip addr add 192.168.1.100/24 dev eth0;" 271 | "ip addr add 3ffe::100/16 dev eth0 nodad;" 272 | "ip route add 0.0.0.0/0 via 192.168.1.1 dev eth0;" 273 | "ip route add ::/0 via 3ffe::1 dev eth0;") 274 | ret = fn(*args, **kw) 275 | LIBC.setns(prev_net_fd.fileno(), CLONE_NEWNET) 276 | prev_net_fd.close() 277 | return ret 278 | return maybe 279 | return decorate 280 | 281 | def find_free_port(ip='127.0.0.1', udp=False): 282 | if udp == False: 283 | p = socket.SOCK_STREAM 284 | else: 285 | p = socket.SOCK_DGRAM 286 | 287 | if ':' not in ip: 288 | s = socket.socket(socket.AF_INET, p) 289 | else: 290 | s = socket.socket(socket.AF_INET6, p) 291 | 292 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 293 | s.setsockopt(socket.IPPROTO_IP, IP_FREEBIND, 1) 294 | s.bind((ip, 0)) 295 | 296 | _, port = s.getsockname() 297 | s.close() 298 | return port 299 | -------------------------------------------------------------------------------- /tests/cover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import collections 4 | import re 5 | import sys 6 | 7 | 8 | class Cover(object): 9 | def __init__(self): 10 | self.t = [] 11 | 12 | def extend(self, vmax): 13 | delta = vmax - len(self.t) 14 | if delta > 0: 15 | self.t += [None] * delta 16 | 17 | def clear(self, start, stop): 18 | self.extend(stop+1) 19 | for x in range(start, stop+1): 20 | if self.t[x] is None: 21 | self.t[x] = False 22 | 23 | def mark(self, start, stop): 24 | self.extend(stop+1) 25 | for x in range(start, stop+1): 26 | self.t[x] = True 27 | 28 | def list(self, state): 29 | p = not state 30 | start = 0 31 | for i, v in enumerate(self.t): 32 | if p != state and v == state: 33 | p = state 34 | start = i 35 | elif p == state and v != state: 36 | p = not state 37 | yield (start, i-1) 38 | if p == state: 39 | yield (start, i-1) 40 | 41 | def count(self): 42 | tot = sum(map(lambda x: x!= None, self.t)) 43 | true = sum(map(lambda x: x == True, self.t)) 44 | return tot-true, tot 45 | 46 | 47 | def main(): 48 | parser = argparse.ArgumentParser( 49 | formatter_class=argparse.RawDescriptionHelpFormatter, 50 | description=r''' 51 | Merges and pretty prints golang coverage files. 52 | ''') 53 | 54 | parser.add_argument('file', nargs='*', 55 | help='golang coverage.out file') 56 | 57 | parser.add_argument('--missing', action='store_true', 58 | help='list missed lines') 59 | 60 | parser.add_argument('-q', '--quiet', action='store_true', 61 | help='just summary') 62 | 63 | parser.add_argument('--size', action='store_true', 64 | help='order missing blocks by size') 65 | 66 | args = parser.parse_args() 67 | 68 | files = collections.defaultdict(Cover) 69 | 70 | LINE = re.compile('^(?P.*):(?P\d+).(?P\d+),(?P\d+).(?P\d+) (?P\d+) (?P\d+)$') 71 | 72 | for fname in args.file: 73 | with open(fname, 'r') as fd: 74 | for line in fd: 75 | if line.startswith('mode:'): 76 | continue 77 | m = LINE.match(line).groupdict() 78 | c = files[m['fname']] 79 | start, stop = int(m['start_line']), int(m['stop_line']) 80 | if m['covered'] == '1': 81 | c.mark(start, stop) 82 | else: 83 | c.clear(start, stop) 84 | 85 | x = max(map(len, files.keys())) 86 | 87 | head = "Name%s Stmts Miss Cover" % (' ' * (x-2)) 88 | if args.missing: 89 | head += " Missing" 90 | print(head) 91 | 92 | if not args.quiet: 93 | print("-" * len(head)) 94 | 95 | tot_bad, tot_tot = 0, 0 96 | for t in sorted(files.keys()): 97 | bad, tot = files[t].count() 98 | if not args.quiet: 99 | if args.missing: 100 | miss = list(files[t].list(False)) 101 | if args.size: 102 | miss.sort(key=lambda a,b:b-a, reverse=True) 103 | xx = ['%s-%s' % (a,b) if a != b else str(a) for a, b in miss] 104 | else: 105 | xx = '' 106 | print('%-*s %7i%7i %4i%% %s' % ( 107 | x, t, tot, bad, 108 | ((tot - bad) * 100.) / tot, 109 | ', '.join(xx))) 110 | tot_bad += bad 111 | tot_tot += tot 112 | 113 | print("-" * len(head)) 114 | print('%-*s %7i%7i %4i%%' % (x, 'TOTAL', tot_tot, tot_bad, 115 | ((tot_tot - tot_bad) * 100.) / tot_tot)) 116 | 117 | 118 | if __name__ == "__main__": 119 | main() 120 | -------------------------------------------------------------------------------- /tests/mockdns/mockdns.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "os" 9 | "time" 10 | 11 | "github.com/miekg/dns" 12 | "strings" 13 | ) 14 | 15 | var records = map[string]string{} 16 | 17 | func handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { 18 | m := new(dns.Msg) 19 | m.SetReply(r) 20 | m.Compress = false 21 | 22 | if len(m.Question) != 1 { 23 | return 24 | } 25 | 26 | q := m.Question[0] 27 | 28 | v := records[q.Name] 29 | fmt.Printf("%q -> %q\n", q.Name, v) 30 | p := strings.SplitN(v, ":", 2) 31 | ip, port := "localhost.", "1234" 32 | switch len(p) { 33 | case 2: 34 | ip = p[0] 35 | port = p[1] 36 | } 37 | 38 | if r.Opcode == dns.OpcodeQuery { 39 | switch q.Qtype { 40 | case dns.TypeSRV: 41 | rr, _ := dns.NewRR(fmt.Sprintf("%s 0 IN SRV 1 1 %s %s", 42 | q.Name, 43 | port, 44 | ip, 45 | )) 46 | m.Answer = append(m.Answer, rr) 47 | } 48 | } 49 | w.WriteMsg(m) 50 | } 51 | 52 | func main() { 53 | port := flag.Int("port", 0, "port to run on") 54 | addr := flag.String("addr", "", "addr to run on") 55 | flag.Parse() 56 | 57 | for _, f := range flag.Args() { 58 | p := strings.SplitN(f, "=", 2) 59 | k, v := "", "" 60 | switch len(p) { 61 | case 1: 62 | k = p[0] 63 | case 2: 64 | k = p[0] 65 | v = p[1] 66 | } 67 | if !strings.HasSuffix(k, ".") { 68 | k = k + "." 69 | } 70 | records[k] = v 71 | } 72 | 73 | if *port == 0 { 74 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 75 | *port = 20000 + r.Intn(32000-20000) 76 | } 77 | fmt.Printf("%d\n", *port) 78 | 79 | udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", *addr, *port)) 80 | if err != nil { 81 | fmt.Printf("Failed to set udp listener: %s\n", err.Error()) 82 | os.Exit(-1) 83 | } 84 | 85 | conn, err := net.ListenUDP("udp", udpAddr) 86 | if err != nil { 87 | fmt.Printf("Failed to set udp listener: %s\n", err.Error()) 88 | os.Exit(-1) 89 | } 90 | 91 | // attach request handler func 92 | dns.HandleFunc(".", handleDnsRequest) 93 | 94 | server := &dns.Server{PacketConn: conn} 95 | err = server.ActivateAndServe() 96 | if err != nil { 97 | fmt.Fprintf(os.Stderr, "Failed to start server: %s\n ", err.Error()) 98 | os.Exit(1) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/mocktcpecho/mocktcpecho.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | port := flag.Int("port", 0, "port to run on") 13 | addr := flag.String("addr", "", "addr to run on") 14 | log := flag.Bool("log", false, "logline per packet") 15 | flag.Parse() 16 | 17 | var as string 18 | if !strings.Contains(*addr, ":") { 19 | as = fmt.Sprintf("%s:%d", *addr, *port) 20 | } else { 21 | as = fmt.Sprintf("[%s]:%d", *addr, *port) 22 | } 23 | a, err := net.ResolveTCPAddr("tcp", as) 24 | if err != nil { 25 | fmt.Fprintf(os.Stderr, "[!] resolve failed: %s\n", err) 26 | os.Exit(-1) 27 | } 28 | 29 | var ln net.Listener 30 | tcpLn, err := net.ListenTCP("tcp", a) 31 | if err != nil { 32 | fmt.Fprintf(os.Stderr, "[!] couldn't start listening: %s\n", err) 33 | os.Exit(-1) 34 | } 35 | ln = tcpLn 36 | 37 | lnAddr := ln.Addr() 38 | tcpAddr := lnAddr.(*net.TCPAddr) 39 | fmt.Printf("%d\n", tcpAddr.Port) 40 | 41 | for { 42 | conn, err := ln.Accept() 43 | if conn == nil { 44 | fmt.Fprintf(os.Stderr, "[-] accept failed: %s\n", err) 45 | continue 46 | } 47 | if *log { 48 | fmt.Printf("%s\n", conn.RemoteAddr()) 49 | } 50 | go handle(conn) 51 | } 52 | } 53 | 54 | func handle(conn net.Conn) { 55 | var buf [4096]byte 56 | for { 57 | n, err := conn.Read(buf[:]) 58 | if err != nil { 59 | break 60 | } 61 | _, err = conn.Write(buf[:n]) 62 | if err != nil { 63 | break 64 | } 65 | } 66 | conn.Close() 67 | } 68 | -------------------------------------------------------------------------------- /tests/mockudpecho/mockudpecho.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strings" 9 | 10 | "github.com/cloudflare/slirpnetstack/unconn" 11 | ) 12 | 13 | func main() { 14 | port := flag.Int("port", 0, "port to run on") 15 | addr := flag.String("addr", "", "addr to run on") 16 | log := flag.Bool("log", false, "logline per packet") 17 | flag.Parse() 18 | 19 | var as string 20 | if !strings.Contains(*addr, ":") { 21 | as = fmt.Sprintf("%s:%d", *addr, *port) 22 | } else { 23 | as = fmt.Sprintf("[%s]:%d", *addr, *port) 24 | } 25 | a, err := net.ResolveUDPAddr("udp", as) 26 | if err != nil { 27 | fmt.Fprintf(os.Stderr, "[!] resolve failed: %s\n", err) 28 | os.Exit(-1) 29 | } 30 | 31 | ln, err := net.ListenUDP("udp", a) 32 | if err != nil { 33 | fmt.Fprintf(os.Stderr, "[!] couldn't start listening: %s\n", err) 34 | os.Exit(-1) 35 | } 36 | 37 | lnAddr := ln.LocalAddr() 38 | udpAddr := lnAddr.(*net.UDPAddr) 39 | fmt.Printf("%d\n", udpAddr.Port) 40 | 41 | unconn.Prime(ln) 42 | 43 | var buf [64*1024]byte 44 | for { 45 | n, laddr, raddr, err := unconn.Read(ln, buf[:]) 46 | if err != nil { 47 | fmt.Fprintf(os.Stderr, "[-] read failed: %s\n", err) 48 | continue 49 | } 50 | unconn.Write(ln, laddr.IP, raddr, buf[:n]) 51 | if *log { 52 | fmt.Printf("%s\n", raddr.String()) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/runner.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | try: 5 | from teamcity.unittestpy import TeamcityTestRunner 6 | teamcity = True 7 | except ImportError: 8 | teamcity = False 9 | 10 | 11 | def is_running_under_teamcity(): 12 | # We export a different enviroment variable than teamcity package expects, 13 | # i.e. TEAMCITY_VERSION, hence a custom predicate to detect TeamCity 14 | # builds. 15 | return bool(os.getenv("CI")) 16 | 17 | 18 | if __name__ == '__main__': 19 | if teamcity and is_running_under_teamcity(): 20 | runner = TeamcityTestRunner() 21 | else: 22 | # Let unittest create it and _configure_ it that we honor the command 23 | # line options like --verbose. 24 | runner = None 25 | 26 | unittest.main(module=None, testRunner=runner) 27 | -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | from . import base 2 | from . import utils 3 | import os 4 | import socket 5 | import struct 6 | import unittest 7 | import urllib.request 8 | 9 | 10 | class BasicTest(base.TestCase): 11 | def test_help(self): 12 | ''' Basic test if -h prints stuff looking like help screen. ''' 13 | p = self.prun("-h", netns=False) 14 | o = p.stdout_line() 15 | self.assertFalse(o) 16 | e = p.stderr_line() 17 | self.assertIn("Usage of ", e) 18 | 19 | def test_version(self): 20 | ''' Basic test if -version prints version. ''' 21 | p = self.prun("-version", netns=False) 22 | o = p.stdout_line() 23 | self.assertIn("slirpnetstack version ", o) 24 | self.assertNotIn("DEV", o) 25 | o = p.stdout_line() 26 | self.assertIn("build time ", o) 27 | self.assertIn("UTC", o) 28 | self.assertNotIn("unknown", o) 29 | 30 | def test_basic_ping(self): 31 | ''' Due to how netstack is configured, we will answer to ping against 32 | any IP. Let's test it!. 33 | ''' 34 | p = self.prun() 35 | self.assertStartSync(p) 36 | with self.guest_netns(): 37 | r = os.system("ping -q 10.0.2.11 -c 1 -n > /dev/null") 38 | self.assertEqual(r, 0) 39 | r = os.system("ping -q 1.1.1.1 -c 1 -n > /dev/null") 40 | self.assertEqual(r, 0) 41 | 42 | def test_pcap(self): 43 | ''' Check -pcap capture ''' 44 | pcap = self.get_tmp_filename("test.pcap") 45 | p = self.prun("-pcap %s" % pcap) 46 | self.assertStartSync(p) 47 | with self.guest_netns(): 48 | r = os.system("ping -q 1.1.1.1 -c 1 -n > /dev/null") 49 | self.assertEqual(r, 0) 50 | caught_sizes = set() 51 | with open(pcap, 'rb') as f: 52 | data = f.read(24) 53 | header = struct.unpack(">LHHLLLL", data) 54 | self.assertEqual(header[0], 0xa1b2c3d4) 55 | data = f.read(16) 56 | (seconds, useconds, captured_length, packet_length) = struct.unpack(">LLLL", data) 57 | # we generally expect icmp echo request at 28 bytes, but 58 | # sometimes see some other packet at 76 bytes (arp?) 59 | caught_sizes.add( captured_length ) 60 | self.assertIn(28, caught_sizes) 61 | 62 | def test_fd(self): 63 | ''' Check inherinting tuntap fd with -fd option ''' 64 | sp = socket.socketpair(type=socket.SOCK_DGRAM) 65 | os.set_inheritable(sp[0].fileno(), True) 66 | p = self.prun("-fd %d" % sp[0].fileno(), close_fds=False, netns=False) 67 | self.assertStartSync(p, fd=True) 68 | # arp Who has 10.0.2.10? Tell 10.0.2.100 69 | ping = bytes.fromhex(''' 70 | ff ff ff ff ff ff 70 71 aa 4b 29 aa 08 06 00 01 71 | 08 00 06 04 00 01 70 71 aa 4b 29 aa 0a 00 02 64 72 | 00 00 00 00 00 00 0a 00 02 0a'''.replace('\n','').replace(' ', '')) 73 | sp[1].sendall(ping) 74 | while True: 75 | pong = sp[1].recv(1024) 76 | if pong[14:16] == b'\x00\x01': # Arp 77 | if pong[20:22] == b'\x00\x02': # Arp reply 78 | if pong[28:32] == b'\x0a\x00\x02\x0a': # 10.0.2.10 is on 79 | break 80 | sp[0].close() 81 | sp[1].close() 82 | 83 | def test_basic_connection(self): 84 | ''' Test connection reset on netstack IP. Netstack is not supposed to 85 | forward nor route this. ''' 86 | p = self.prun() 87 | self.assertStartSync(p) 88 | with self.guest_netns(): 89 | self.assertTcpRefusedError(port=80, ip='10.0.2.2') 90 | 91 | def test_local_fwd_error_tcp(self): 92 | ''' Test bind errors on local TCP forwarding.''' 93 | port = base.find_free_port() 94 | p = self.prun("-L %s -L %s" % (port, port)) 95 | self.assertIn("[.] Join", p.stderr_line()) 96 | self.assertIn("[.] Opening tun", p.stderr_line()) 97 | xport = self.assertListenLine(p, "local-fwd Local listen tcp://127.0.0.1") 98 | self.assertEqual(port, xport) 99 | # [!] Failed to listen on tcp://127.0.0.1:45295 100 | self.assertIn("Failed to listen on tcp://127.0.0.1", p.stderr_line()) 101 | 102 | def test_local_fwd_error_udp(self): 103 | ''' Test bind errors on local UDP forwarding.''' 104 | port = base.find_free_port() 105 | p = self.prun("-L udp://%s -L udp://%s" % (port, port)) 106 | self.assertIn("[.] Join", p.stderr_line()) 107 | self.assertIn("[.] Opening tun", p.stderr_line()) 108 | xport = self.assertListenLine(p, "local-fwd Local listen udp://127.0.0.1") 109 | self.assertEqual(port, xport) 110 | # [!] Failed to listen on udp://127.0.0.1:45295 111 | self.assertIn("Failed to listen on udp://127.0.0.1", p.stderr_line()) 112 | p.close() 113 | self.assertEqual(p.rc, 247, "exit code should be 247") 114 | 115 | def test_basic_ping6(self): 116 | '''Due to how netstack is configured, we will answer to ping against 117 | any IP. Let's test it!.''' 118 | p = self.prun() 119 | self.assertStartSync(p) 120 | with self.guest_netns(): 121 | # ping local address. sanity. This is kinda tricky. First, 122 | # tuntap must be enabled (ie: someone must pick up the 123 | # fd). Then the ip needs to have 'nodad' toggle, to 124 | # prevent it from stalling on duplicate address detection. 125 | r = os.system("ping6 -q 2001:2::2 -c 1 -n > /dev/null") 126 | self.assertEqual(r, 0) 127 | r = os.system("ping6 -q 2001:2::100 -c 1 -n > /dev/null") 128 | self.assertEqual(r, 0) 129 | # 1.1.1.1 resolver. Doesn't matter - will be terminated by netstack 130 | r = os.system("ping6 -q 2606:4700:4700::1111 -c 1 -n > /dev/null") 131 | self.assertEqual(r, 0) 132 | 133 | def test_metric(self): 134 | ''' Test if metrics server run. ''' 135 | p = self.prun("-m tcp://127.0.0.1:0") 136 | e = p.stderr_line() 137 | self.assertIn("Running metrics ", e) 138 | metrics_port = int(e.rsplit(':')[-1].rstrip()) 139 | f = urllib.request.urlopen('http://127.0.0.1:%d/debug/pprof' % (metrics_port,)) 140 | self.assertIn(b"Types of profiles available:", f.read(300)) 141 | 142 | 143 | class RoutingTest(base.TestCase): 144 | @base.isolateHostNetwork() 145 | def test_tcp_routing(self): 146 | ''' Test tcp routing. Establish connection from guest onto an IP 147 | assigned to local-scoped IP on host. ''' 148 | echo_port = self.start_tcp_echo() 149 | p = self.prun("-enable-host") 150 | self.assertStartSync(p) 151 | with self.guest_netns(): 152 | self.assertTcpEcho(ip="192.168.1.100", port=echo_port) 153 | self.assertIn("Routing conn new", p.stdout_line()) 154 | # TODO: l=[EOF]/0 r=EOF/0 or l=EOF/0 r=[EOF]/0 ? 155 | self.assertIn("Routing conn done: l=", p.stdout_line()) 156 | self.assertTcpEcho(ip="192.168.1.100", port=echo_port) 157 | 158 | @base.isolateHostNetwork() 159 | def test_tcp_routing_v6(self): 160 | ''' Test tcp routing. Establish connection from guest onto an IP 161 | assigned to local-scoped IP on host. ''' 162 | echo_port = self.start_tcp_echo() 163 | p = self.prun("-enable-host") 164 | self.assertStartSync(p) 165 | with self.guest_netns(): 166 | self.assertTcpEcho(ip="3ffe::100", port=echo_port) 167 | self.assertIn("Routing conn new", p.stdout_line()) 168 | # TODO: l=[EOF]/0 r=EOF/0 or l=EOF/0 r=[EOF]/0 ? 169 | self.assertIn("Routing conn done: l=", p.stdout_line()) 170 | self.assertTcpEcho(ip="3ffe::100", port=echo_port) 171 | 172 | @base.isolateHostNetwork() 173 | def test_tcp_routing_multi(self): 174 | ''' Test tcp routing. Can we establish like 200 connnections? ''' 175 | echo_port = self.start_tcp_echo() 176 | p = self.prun("-enable-host") 177 | self.assertStartSync(p) 178 | c = 0 179 | with self.guest_netns(): 180 | for i in range(200): 181 | self.assertTcpEcho(ip="192.168.1.100", port=echo_port) 182 | for i in range(400): 183 | l = p.stdout_line() 184 | if "Routing conn new" in l: 185 | c += 1 186 | elif "Routing conn done" in l: 187 | c -= 1 188 | self.assertEqual(c, 0) 189 | 190 | @base.isolateHostNetwork() 191 | def test_udp_routing(self): 192 | ''' Test udp routing. Send packet from guest onto an IP assigned 193 | to local-scoped IP on host. ''' 194 | echo_port = self.start_udp_echo() 195 | self.assertUdpEcho(port=echo_port, ip="192.168.1.100") 196 | 197 | p = self.prun("-enable-host") 198 | self.assertStartSync(p) 199 | with self.guest_netns(): 200 | self.assertUdpEcho(port=echo_port, ip="192.168.1.100") 201 | self.assertIn("Routing conn new", p.stdout_line()) 202 | # Can't test conn done message. 203 | self.assertUdpEcho(port=echo_port, ip="192.168.1.100") 204 | 205 | @base.isolateHostNetwork() 206 | def test_udp_routing_merge(self): 207 | '''Test udp routing. There is a bug where two packets get merged into 208 | one if rapidly sent. This is breaking DNS. ''' 209 | echo_port = self.start_udp_echo() 210 | self.assertUdpEcho(port=echo_port, ip="192.168.1.100") 211 | 212 | p = self.prun("-enable-host") 213 | self.assertStartSync(p) 214 | with self.guest_netns(): 215 | s = utils.connect(port=echo_port, ip="192.168.1.100", udp=True) 216 | s.sendall(b"ala") 217 | # We need to do a sync here due to UDP race condition 218 | self.assertIn("Routing conn new", p.stdout_line()) 219 | s.sendall(b"ma") 220 | s.sendall(b"kota") 221 | self.assertEqual(b"ala", s.recv(1024)) 222 | self.assertEqual(b"ma", s.recv(1024)) 223 | self.assertEqual(b"kota", s.recv(1024)) 224 | s.close() 225 | 226 | @base.isolateHostNetwork() 227 | def test_udp_large_packet(self): 228 | '''Test UDP packet larger than 1500 bytes''' 229 | echo_port = self.start_udp_echo() 230 | self.assertUdpEcho(port=echo_port, ip="192.168.1.100") 231 | 232 | p = self.prun("-enable-host") 233 | self.assertStartSync(p) 234 | with self.guest_netns(): 235 | s = utils.connect(port=echo_port, ip="192.168.1.100", udp=True) 236 | a = s.send(b"a"*16385) 237 | s.sendall(b"a"*(65535-28)) 238 | self.assertEqual(16385, len(s.recv(64*1024))) 239 | self.assertEqual(65507, len(s.recv(64*1024))) 240 | s.close() 241 | 242 | 243 | class GenericForwardingTest(base.TestCase): 244 | def test_fwd_parsing_one(self): 245 | '''Test basic forwarding parsing, just port''' 246 | echo_port = 1234 247 | p = self.prun("-R %s" % echo_port) 248 | self.assertStartSync(p) 249 | port = self.assertListenLine(p, "Accepting on remote side tcp://10.0.2.2") 250 | self.assertEqual(port, echo_port) 251 | 252 | def test_fwd_parsing_two(self): 253 | '''Test basic forwarding parsing, port and host''' 254 | echo_port = 1235 255 | p = self.prun("-enable-host -R 1.2.3.4:%s" % echo_port) 256 | self.assertStartSync(p) 257 | port = self.assertListenLine(p, "Accepting on remote side tcp://1.2.3.4") 258 | self.assertEqual(port, echo_port) 259 | 260 | def test_loc_parsing_one(self): 261 | '''Test basic forwarding parsing, just port''' 262 | echo_port = 1236 263 | p = self.prun("-L %s" % echo_port) 264 | self.assertStartSync(p) 265 | port = self.assertListenLine(p, "local-fwd Local listen tcp://127.0.0.1") 266 | self.assertEqual(port, echo_port) 267 | 268 | def test_loc_parsing_two(self): 269 | '''Test basic forwarding parsing, port and host''' 270 | echo_port = 1237 271 | p = self.prun("-L 127.1.2.3:%s" % echo_port) 272 | self.assertStartSync(p) 273 | port = self.assertListenLine(p, "local-fwd Local listen tcp://127.1.2.3") 274 | self.assertEqual(port, echo_port) 275 | 276 | def test_local_fwd_resolve_host(self): 277 | ''' Test if you can local forward to dns label.''' 278 | g_echo_port = self.start_tcp_echo(guest=True) 279 | p = self.prun("-L localhost:0:10.0.2.100:%s" % (g_echo_port)) 280 | self.assertStartSync(p) 281 | port = self.assertListenLine(p, "local-fwd Local listen") 282 | 283 | with self.guest_netns(): 284 | self.assertTcpEcho(ip="127.0.0.1", port=g_echo_port) 285 | self.assertTcpEcho(ip="127.0.0.1", port=port) 286 | self.assertTcpEcho(ip="127.0.0.1", port=port) 287 | self.assertIn("local-fwd conn", p.stdout_line()) 288 | 289 | 290 | class DefaultIPSelectionTests(base.TestCase): 291 | def test_local_empty(self): 292 | '''Test a local forward rule with no IPs given''' 293 | g_echo_port = self.start_udp_echo(guest=True) 294 | p = self.prun("-L udp://:0::%s" % g_echo_port) 295 | self.assertStartSync(p) 296 | port = self.assertListenLine(p, "local-fwd Local listen udp://127.0.0.1") 297 | 298 | with self.guest_netns(): 299 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 300 | self.assertUdpEcho(ip="127.0.0.1", port=port) 301 | self.assertIn("local-fwd conn", p.stdout_line()) 302 | 303 | def test_remote_empty(self): 304 | '''Test a remote forward rule with no IPs given''' 305 | g_echo_port = self.start_udp_echo() 306 | p = self.prun("-R udp://:0::%s" % g_echo_port) 307 | self.assertStartSync(p) 308 | port = self.assertListenLine(p, "Accepting on remote side udp://10.0.2.2") 309 | 310 | with self.guest_netns(): 311 | self.assertUdpEcho(ip="10.0.2.2", port=port) 312 | self.assertRegex(p.stdout_line(), "127.0.0.1:\\d+-127.0.0.1:%s remote-fwd conn new" % g_echo_port) 313 | 314 | def test_local_implicit_host_v4(self): 315 | '''Test a local forward rule with an implicit IPv4 host''' 316 | g_echo_port = self.start_udp_echo(guest=True) 317 | p = self.prun("-L udp://:0:10.0.2.100:%s" % g_echo_port) 318 | self.assertStartSync(p) 319 | port = self.assertListenLine(p, "local-fwd Local listen udp://127.0.0.1") 320 | 321 | with self.guest_netns(): 322 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 323 | self.assertUdpEcho(ip="127.0.0.1", port=port) 324 | self.assertIn("local-fwd conn", p.stdout_line()) 325 | 326 | def test_local_implicit_forward_v4(self): 327 | '''Test a local forward rule with an implicit IPv4 denstination''' 328 | g_echo_port = self.start_udp_echo(guest=True) 329 | p = self.prun("-L udp://127.0.0.1:0::%s" % g_echo_port) 330 | self.assertStartSync(p) 331 | port = self.assertListenLine(p, "local-fwd Local listen udp://127.0.0.1") 332 | 333 | with self.guest_netns(): 334 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 335 | self.assertUdpEcho(ip="127.0.0.1", port=port) 336 | self.assertIn("local-fwd conn", p.stdout_line()) 337 | 338 | def test_local_implicit_host_v6(self): 339 | '''Test a local forward rule with an implicit IPv6 host''' 340 | g_echo_port = self.start_udp_echo(guest=True) 341 | p = self.prun("-L udp://:0:[fd00::100]:%s" % g_echo_port) 342 | self.assertStartSync(p) 343 | port = self.assertListenLine(p, "local-fwd Local listen udp://[::1]") 344 | 345 | with self.guest_netns(): 346 | self.assertUdpEcho(ip="::1", port=g_echo_port) 347 | self.assertUdpEcho(ip="::1", port=port) 348 | self.assertIn("local-fwd conn", p.stdout_line()) 349 | 350 | def test_local_implicit_forward_v6(self): 351 | '''Test a local forward rule with an implicit IPv6 denstination''' 352 | g_echo_port = self.start_udp_echo(guest=True) 353 | p = self.prun("-L udp://[::1]:0::%s" % g_echo_port) 354 | self.assertStartSync(p) 355 | port = self.assertListenLine(p, "local-fwd Local listen udp://[::1]") 356 | 357 | with self.guest_netns(): 358 | self.assertUdpEcho(ip="::1", port=g_echo_port) 359 | self.assertUdpEcho(ip="::1", port=port) 360 | self.assertIn("local-fwd conn", p.stdout_line()) 361 | 362 | def test_remote_implicit_forward_v6(self): 363 | '''Test a remote forward rule with an implicit IPv6 destination''' 364 | g_echo_port = self.start_udp_echo() 365 | p = self.prun("-R udp://[fd00::2]:0::%s" % g_echo_port) 366 | self.assertStartSync(p) 367 | port = self.assertListenLine(p, "Accepting on remote side udp://fd00::2") 368 | 369 | with self.guest_netns(): 370 | self.assertUdpEcho(ip="fd00::2", port=port) 371 | self.assertRegex(p.stdout_line(), "\\[::1\\]:\\d+-\\[::1\\]:%s remote-fwd conn new" % g_echo_port) 372 | 373 | def test_local_any_v4(self): 374 | '''Test a local forward rule with the 0.0.0.0 addr''' 375 | g_echo_port = self.start_udp_echo(guest=True) 376 | p = self.prun("-L udp://0.0.0.0:0::%s" % g_echo_port) 377 | self.assertStartSync(p) 378 | port = self.assertListenLine(p, "local-fwd Local listen udp://[::]") 379 | 380 | with self.guest_netns(): 381 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 382 | self.assertUdpEcho(ip="127.0.0.1", port=port) 383 | self.assertRegex(p.stdout_line(), "10.0.2.2:\\d+-10.0.2.100:%s local-fwd conn" % g_echo_port) 384 | 385 | def test_local_any_v6(self): 386 | '''Test a local forward rule with the :: addr''' 387 | g_echo_port = self.start_udp_echo(guest=True) 388 | p = self.prun("-L udp://[::]:::%s" % g_echo_port) 389 | self.assertStartSync(p) 390 | port = self.assertListenLine(p, "local-fwd Local listen udp://[::]") 391 | 392 | with self.guest_netns(): 393 | self.assertUdpEcho(ip="::1", port=g_echo_port) 394 | self.assertUdpEcho(ip="::1", port=port) 395 | self.assertRegex(p.stdout_line(), "\\[fd00::2\\]:\\d+-\\[fd00::100\\]:%s local-fwd conn" % g_echo_port) 396 | 397 | def test_localhost_v4(self): 398 | '''Test a local forward rule with the localhost label''' 399 | g_echo_port = self.start_udp_echo(guest=True) 400 | p = self.prun("-L udp://localhost:0::%s" % g_echo_port) 401 | self.assertStartSync(p) 402 | port = self.assertListenLine(p, "local-fwd Local listen udp://127.0.0.1") 403 | 404 | with self.guest_netns(): 405 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 406 | self.assertUdpEcho(ip="127.0.0.1", port=port) 407 | self.assertRegex(p.stdout_line(), "10.0.2.2:\\d+-10.0.2.100:%s local-fwd conn" % g_echo_port) 408 | 409 | 410 | class RemoteForwardingTest(base.TestCase): 411 | def test_tcp_remote_fwd(self): 412 | ''' Test tcp remote forwarding - bind to remote port on guest and 413 | forward it to host. ''' 414 | echo_port = self.start_tcp_echo() 415 | p = self.prun("-R 80:127.0.0.1:%s" % echo_port) 416 | self.assertStartSync(p) 417 | with self.guest_netns(): 418 | self.assertTcpEcho(ip='10.0.2.2', port=80) 419 | self.assertTcpEcho(ip='10.0.2.2', port=80) 420 | 421 | def test_tcp_remote_fwd_2(self): 422 | '''Test tcp remote forwarding - bind to remote port on 423 | arbitrary guest IP and forward it to host. 424 | ''' 425 | echo_port = self.start_tcp_echo() 426 | p = self.prun("-R 192.0.2.5:80:127.0.0.1:%s" % echo_port) 427 | 428 | self.assertStartSync(p) 429 | with self.guest_netns(): 430 | self.assertTcpEcho(ip='192.0.2.5', port=80) 431 | self.assertTcpEcho(ip='192.0.2.5', port=80) 432 | 433 | def test_udp_remote_fwd(self): 434 | ''' Test udp remote forwarding - bind to remote port on guest and 435 | forward it to host. ''' 436 | echo_port = self.start_udp_echo() 437 | p = self.prun("-R udp://80:127.0.0.1:%s" % echo_port) 438 | self.assertStartSync(p) 439 | 440 | self.assertUdpEcho(ip='127.0.0.1', port=echo_port) 441 | with self.guest_netns(): 442 | self.assertUdpEcho(ip='10.0.2.2', port=80) 443 | self.assertUdpEcho(ip='10.0.2.2', port=80) 444 | 445 | def test_udp_remote_fwd_2(self): 446 | '''Test udp remote forwarding - bind to remote port on arbitrary 447 | guest IP and forward it to host.''' 448 | echo_port = self.start_udp_echo() 449 | p = self.prun("-R udp://192.0.2.5:80:127.0.0.1:%s" % echo_port) 450 | self.assertStartSync(p) 451 | 452 | self.assertUdpEcho(ip='127.0.0.1', port=echo_port) 453 | with self.guest_netns(): 454 | self.assertUdpEcho(ip='192.0.2.5', port=80) 455 | self.assertUdpEcho(ip='192.0.2.5', port=80) 456 | 457 | def test_udp_remote_fwd_merge(self): 458 | '''Test if udp message boundry is preserved. ''' 459 | echo_port = self.start_udp_echo() 460 | p = self.prun("-R udp://192.0.2.5:80:127.0.0.1:%s" % echo_port) 461 | self.assertStartSync(p) 462 | 463 | self.assertUdpEcho(ip='127.0.0.1', port=echo_port) 464 | with self.guest_netns(): 465 | s = utils.connect(ip='192.0.2.5', port=80, udp=True) 466 | s.sendall(b"ala") 467 | s.sendall(b"ma") 468 | s.sendall(b"kota") 469 | self.assertEqual(b"ala", s.recv(1024)) 470 | self.assertEqual(b"ma", s.recv(1024)) 471 | self.assertEqual(b"kota", s.recv(1024)) 472 | s.close() 473 | 474 | 475 | class LocalForwardingTest(base.TestCase): 476 | def test_tcp_local_fwd(self): 477 | ''' Test basic local forwarding - bind to local port on host and 478 | forward it to the guest. ''' 479 | g_echo_port = self.start_tcp_echo(guest=True) 480 | p = self.prun("-L 0:10.0.2.100:%s" % g_echo_port) 481 | self.assertStartSync(p) 482 | port = self.assertListenLine(p, "local-fwd Local listen") 483 | 484 | with self.guest_netns(): 485 | self.assertTcpEcho(ip="127.0.0.1", port=g_echo_port) 486 | self.assertTcpEcho(ip="127.0.0.1", port=port) 487 | self.assertTcpEcho(ip="127.0.0.1", port=port) 488 | self.assertIn("local-fwd conn", p.stdout_line()) 489 | 490 | @base.isolateHostNetwork() 491 | def test_tcp_local_fwd_2(self): 492 | '''Test tcp local forwarding - bind to local port on host and forward 493 | it to the guest. Establish connection from a routable IP - it 494 | should be preserved into the guest.''' 495 | g_echo_port, read_log = self.start_tcp_echo(guest=True, log=True) 496 | p = self.prun("-L 0:10.0.2.100:%s" % g_echo_port) 497 | self.assertStartSync(p) 498 | port = self.assertListenLine(p, "local-fwd Local listen") 499 | 500 | with self.guest_netns(): 501 | self.assertTcpEcho(ip="127.0.0.1", port=g_echo_port) 502 | self.assertIn("127.0.0.1", read_log()) 503 | self.assertTcpEcho(ip="127.0.0.1", port=port, src="192.168.1.100") 504 | self.assertTcpEcho(ip="127.0.0.1", port=port, src="192.168.1.100") 505 | self.assertIn("192.168.1.100", read_log()) 506 | self.assertIn("192.168.1.100", read_log()) 507 | self.assertIn("local-fwd conn", p.stdout_line()) 508 | 509 | @base.isolateHostNetwork() 510 | def test_tcp_local_fwd_v6tov4(self): 511 | ''' Test local forwarding - bind to local IPv6 port on host and 512 | forward it to the guest via IPv4. ''' 513 | g_echo_port = self.start_tcp_echo(guest=True) 514 | p = self.prun("-L tcp://[::]:0:10.0.2.100:%s" % g_echo_port) 515 | self.assertStartSync(p) 516 | port = self.assertListenLine(p, "local-fwd Local listen") 517 | 518 | with self.guest_netns(): 519 | self.assertTcpEcho(ip="::1", port=g_echo_port) 520 | # ::1 is on the StaticRoutingDeny list, meaning it will not be spoofed 521 | self.assertTcpEcho(ip="::1", port=port) 522 | self.assertRegex(p.stdout_line(), "10.0.2.2:\\d+-10.0.2.100:%s local-fwd conn" % g_echo_port) 523 | self.assertIn("local-fwd done", p.stdout_line()) 524 | self.assertTcpEcho(ip="3ffe::100", port=port) 525 | self.assertRegex(p.stdout_line(), "10.0.2.2:\\d+-10.0.2.100:%s local-fwd conn" % g_echo_port) 526 | 527 | @base.isolateHostNetwork() 528 | def test_tcp_local_fwd_v4tov6(self): 529 | ''' Test basic local forwarding - bind to local IPv4 port on host and 530 | forward it to the guest via IPv6. ''' 531 | g_echo_port = self.start_tcp_echo(guest=True) 532 | p = self.prun("-L tcp://0.0.0.0:0:[fd00::100]:%s" % g_echo_port) 533 | self.assertStartSync(p) 534 | port = self.assertListenLine(p, "local-fwd Local listen") 535 | 536 | with self.guest_netns(): 537 | self.assertTcpEcho(ip="127.0.0.1", port=g_echo_port) 538 | # 127.0.0.1 is on the StaticRoutingDeny list, meaning it will not be spoofed 539 | self.assertTcpEcho(ip="127.0.0.1", port=port) 540 | self.assertRegex(p.stdout_line(), "\\[fd00::2\\]:\\d+-\\[fd00::100\\]:%s local-fwd conn" % g_echo_port) 541 | self.assertIn("local-fwd done", p.stdout_line()) 542 | self.assertTcpEcho(ip="192.168.1.100", port=port) 543 | self.assertRegex(p.stdout_line(), "\\[fd00::2\\]:\\d+-\\[fd00::100\\]:%s local-fwd conn" % g_echo_port) 544 | 545 | def test_udp_local_fwd(self): 546 | ''' Test udp local forwarding - bind to local port on host and forward 547 | it to the guest. ''' 548 | g_echo_port = self.start_udp_echo(guest=True) 549 | p = self.prun("-L udp://0:10.0.2.100:%s" % g_echo_port) 550 | self.assertStartSync(p) 551 | port = self.assertListenLine(p, "local-fwd Local listen") 552 | 553 | with self.guest_netns(): 554 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 555 | self.assertUdpEcho(ip="127.0.0.1", port=port) 556 | self.assertUdpEcho(ip="127.0.0.1", port=port) 557 | self.assertIn("local-fwd conn", p.stdout_line()) 558 | 559 | @base.isolateHostNetwork() 560 | def test_udp_local_fwd_2(self): 561 | '''Test udp local forwarding - bind to local port on host and forward 562 | it to the guest. Establish connection from a routable IP - it 563 | should be preserved into the guest.''' 564 | g_echo_port, read_log = self.start_udp_echo(guest=True, log=True) 565 | p = self.prun("-L udp://0:10.0.2.100:%s" % g_echo_port) 566 | self.assertStartSync(p) 567 | port = self.assertListenLine(p, "local-fwd Local listen") 568 | 569 | with self.guest_netns(): 570 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 571 | self.assertIn("127.0.0.1", read_log()) 572 | self.assertUdpEcho(ip="127.0.0.1", port=port, src="192.168.1.100") 573 | self.assertUdpEcho(ip="127.0.0.1", port=port, src="192.168.1.100") 574 | self.assertIn("192.168.1.100", read_log()) 575 | self.assertIn("192.168.1.100", read_log()) 576 | self.assertIn("local-fwd conn", p.stdout_line()) 577 | 578 | @base.isolateHostNetwork() 579 | def test_udp_local_fwd_v6tov4(self): 580 | ''' Test local forwarding - bind to local IPv6 port on host and 581 | forward it to the guest via IPv4. ''' 582 | g_echo_port = self.start_udp_echo(guest=True) 583 | p = self.prun("-L udp://[::]:0:10.0.2.100:%s" % g_echo_port) 584 | self.assertStartSync(p) 585 | port = self.assertListenLine(p, "local-fwd Local listen") 586 | 587 | with self.guest_netns(): 588 | self.assertUdpEcho(ip="::1", port=g_echo_port) 589 | # ::1 is on the StaticRoutingDeny list, meaning it will not be spoofed 590 | self.assertUdpEcho(ip="::1", port=port) 591 | self.assertRegex(p.stdout_line(), "10.0.2.2:\\d+-10.0.2.100:%s local-fwd conn" % g_echo_port) 592 | self.assertUdpEcho(ip="3ffe::100", port=port) 593 | self.assertRegex(p.stdout_line(), "10.0.2.2:\\d+-10.0.2.100:%s local-fwd conn" % g_echo_port) 594 | 595 | @base.isolateHostNetwork() 596 | def test_udp_local_fwd_v4tov6(self): 597 | ''' Test basic local forwarding - bind to local IPv4 port on host and 598 | forward it to the guest via IPv6. ''' 599 | g_echo_port = self.start_udp_echo(guest=True) 600 | p = self.prun("-L udp://0.0.0.0:0:[fd00::100]:%s" % g_echo_port) 601 | self.assertStartSync(p) 602 | port = self.assertListenLine(p, "local-fwd Local listen") 603 | 604 | with self.guest_netns(): 605 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 606 | # 127.0.0.1 is on the StaticRoutingDeny list, meaning it will not be spoofed 607 | self.assertUdpEcho(ip="127.0.0.1", port=port) 608 | self.assertRegex(p.stdout_line(), "\\[fd00::2\\]:\\d+-\\[fd00::100\\]:%s local-fwd conn" % g_echo_port) 609 | self.assertUdpEcho(ip="192.168.1.100", port=port) 610 | self.assertRegex(p.stdout_line(), "\\[fd00::2\\]:\\d+-\\[fd00::100\\]:%s local-fwd conn" % g_echo_port) 611 | 612 | def test_udprpc_local_fwd(self): 613 | '''Test udprcp local forwarding - bind to local port on host and 614 | forward it to the guest. udprpc is about rapidly closing udp 615 | rpc flows like DNS or UDP''' 616 | g_echo_port = self.start_udp_echo(guest=True) 617 | p = self.prun("-L udprpc://0:10.0.2.100:%s" % g_echo_port) 618 | self.assertStartSync(p) 619 | port = self.assertListenLine(p, "local-fwd Local listen") 620 | 621 | with self.guest_netns(): 622 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 623 | self.assertUdpEcho(ip="127.0.0.1", port=port) 624 | self.assertIn("local-fwd conn", p.stdout_line()) 625 | self.assertIn("local-fwd done", p.stdout_line()) 626 | self.assertUdpEcho(ip="127.0.0.1", port=port) 627 | self.assertIn("local-fwd conn", p.stdout_line()) 628 | self.assertIn("local-fwd done", p.stdout_line()) 629 | 630 | def test_udp_local_fwd_merge(self): 631 | '''Test if udp message boundry is preserved. ''' 632 | g_echo_port = self.start_udp_echo(guest=True) 633 | p = self.prun("-L udp://0:10.0.2.100:%s" % g_echo_port) 634 | self.assertStartSync(p) 635 | port = self.assertListenLine(p, "local-fwd Local listen") 636 | 637 | with self.guest_netns(): 638 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 639 | s = utils.connect(port=port, ip="127.0.0.1", udp=True) 640 | s.sendall(b"ala") 641 | # We need to do a sync here due to MagicDialUDP race condition 642 | self.assertIn("local-fwd conn", p.stdout_line()) 643 | self.assertEqual(b"ala", s.recv(1024)) 644 | s.sendall(b"ma") 645 | s.sendall(b"kota") 646 | self.assertEqual(b"ma", s.recv(1024)) 647 | self.assertEqual(b"kota", s.recv(1024)) 648 | s.close() 649 | 650 | 651 | class LocalForwardingPPTest(base.TestCase): 652 | def test_tcp_pp_local_fwd(self): 653 | ''' Test inbound TCP proxy-protocol ''' 654 | g_echo_port, read_log = self.start_tcp_echo(guest=True, log=True) 655 | p = self.prun("-L tcppp://0:10.0.2.100:%s" % g_echo_port) 656 | self.assertStartSync(p) 657 | port = self.assertListenLine(p, "local-fwd Local PP listen") 658 | 659 | with self.guest_netns(): 660 | self.assertTcpEcho(ip="127.0.0.1", port=g_echo_port) 661 | self.assertIn("127.0.0.1", read_log()) 662 | 663 | s = utils.connect(port=port, ip="127.0.0.1") 664 | s.sendall(b"PROXY TCP4 1.2.3.4 4.3.2.1 1 2\r\n") 665 | self.assertIn("local-fwd PP conn", p.stdout_line()) 666 | s.sendall(b"alamakota") 667 | self.assertEqual(b"alamakota", s.recv(1024)) 668 | s.close() 669 | self.assertIn("1.2.3.4", read_log()) 670 | self.assertIn("local-fwd PP done", p.stdout_line()) 671 | 672 | s = utils.connect(port=port, ip="127.0.0.1") 673 | s.sendall(b"PROXY TCP4 4.4.4.4 4.3.2.1 1 2\r\nalama") 674 | self.assertIn("local-fwd PP conn", p.stdout_line()) 675 | s.sendall(b"kota") 676 | self.assertIn("4.4.4.4", read_log()) 677 | b = b"" 678 | while len(b) < 8: 679 | b += s.recv(1024) 680 | self.assertEqual(b"alamakota", b) 681 | s.close() 682 | 683 | def test_tcp_pp_local_fwd_noport(self): 684 | '''Test inbound TCP proxy-protocol, while specifying zero port on the 685 | guest side''' 686 | g_echo_port, read_log = self.start_tcp_echo(guest=True, log=True) 687 | p = self.prun("-L tcppp://0:10.0.2.100:0" ) 688 | self.assertStartSync(p) 689 | port = self.assertListenLine(p, "local-fwd Local PP listen") 690 | 691 | with self.guest_netns(): 692 | self.assertTcpEcho(ip="127.0.0.1", port=g_echo_port) 693 | self.assertIn("127.0.0.1", read_log()) 694 | 695 | s = utils.connect(port=port, ip="127.0.0.1") 696 | s.sendall(b"PROXY TCP4 1.2.3.4 4.3.2.1 1 %d\r\n" % (g_echo_port,)) 697 | self.assertIn("local-fwd PP conn", p.stdout_line()) 698 | s.sendall(b"alamakota") 699 | self.assertEqual(b"alamakota", s.recv(1024)) 700 | s.close() 701 | self.assertIn("1.2.3.4", read_log()) 702 | self.assertIn("local-fwd PP done", p.stdout_line()) 703 | 704 | def test_udp_spp_local_fwd(self): 705 | '''Test inbound UDP SPP. Read more in https://developers.cloudflare.com/spectrum/getting-started/proxy-protocol/#enabling-simple-proxy-protocol-for-udp''' 706 | g_echo_port, read_log = self.start_udp_echo(guest=True, log=True) 707 | p = self.prun("-L udpspp://0:10.0.2.100:%s" % g_echo_port) 708 | self.assertStartSync(p) 709 | port = self.assertListenLine(p, "local-fwd Local PP listen") 710 | 711 | with self.guest_netns(): 712 | self.assertUdpEcho(ip="127.0.0.1", port=g_echo_port) 713 | self.assertIn("127.0.0.1", read_log()) 714 | 715 | s = utils.connect(port=port, ip="127.0.0.1", udp=True) 716 | sppheader = struct.pack("!HIIIIIIIIHH", 0x56ec, 717 | 0,0,0xffff,0x01020304, 718 | 0,0,0xffff,0x04030201, 719 | 1,2) 720 | s.sendall(sppheader + b"alamakota" + b'x' * 65000) 721 | self.assertIn("local-fwd PP conn", p.stdout_line()) 722 | data = s.recv(64*1024) 723 | self.assertEqual(b"alamakota", data[38:38+9]) 724 | self.assertEqual(65000+38+9, len(data)) 725 | adr = read_log() 726 | self.assertIn("1.2.3.4", adr) 727 | 728 | s.sendall(b'\x00'*38 + b"alamakota2") 729 | self.assertEqual(b"alamakota2", s.recv(1024)[38:]) 730 | self.assertEqual(adr, read_log()) 731 | s.close() 732 | 733 | s = utils.connect(port=port, ip="127.0.0.1", udp=True) 734 | sppheader = struct.pack("!HIIIIIIIIHH", 0x56ec, 735 | 0,0,0xffff,0x04040404, 736 | 0,0,0xffff,0x04030201, 737 | 1,2) 738 | s.sendall(sppheader + b"kotamaala") 739 | self.assertIn("local-fwd PP conn", p.stdout_line()) 740 | self.assertEqual(b"kotamaala", s.recv(1024)[38:]) 741 | s.close() 742 | self.assertIn("4.4.4.4", read_log()) 743 | 744 | def test_tcp_pp_local_fwd_v6(self): 745 | ''' Test inbound TCP v6 proxy-protocol ''' 746 | g_echo_port, read_log = self.start_tcp_echo(guest=True, log=True) 747 | p = self.prun("-L tcppp://[::1]:0:[fd00::100]:%s" % g_echo_port) 748 | self.assertStartSync(p) 749 | port = self.assertListenLine(p, "local-fwd Local PP listen") 750 | 751 | with self.guest_netns(): 752 | self.assertTcpEcho(ip="::1", port=g_echo_port) 753 | self.assertIn("::1", read_log()) 754 | 755 | s = utils.connect(port=port, ip="::1") 756 | s.sendall(b"PROXY TCP4 abcd::1 dcba::1 1 2\r\n") 757 | self.assertIn("local-fwd PP conn", p.stdout_line()) 758 | s.sendall(b"alamakota") 759 | self.assertEqual(b"alamakota", s.recv(1024)) 760 | s.close() 761 | self.assertIn("abcd::1", read_log()) 762 | self.assertIn("local-fwd PP done", p.stdout_line()) 763 | 764 | def test_tcp_pp_local_fwd_v6tov4(self): 765 | ''' Test inbound TCP v6 proxy-protocol to v4 destination ''' 766 | g_echo_port, read_log = self.start_tcp_echo(guest=True, log=True) 767 | p = self.prun("-L tcppp://[::]:0:10.0.2.100:%s" % g_echo_port) 768 | self.assertStartSync(p) 769 | port = self.assertListenLine(p, "local-fwd Local PP listen") 770 | 771 | with self.guest_netns(): 772 | self.assertTcpEcho(ip="127.0.0.1", port=g_echo_port) 773 | self.assertIn("127.0.0.1", read_log()) 774 | 775 | s = utils.connect(port=port, ip="::1") 776 | s.sendall(b"PROXY TCP4 abcd::1 dcba::1 1 2\r\n") 777 | self.assertIn("local-fwd PP conn", p.stdout_line()) 778 | s.sendall(b"alamakota") 779 | self.assertEqual(b"alamakota", s.recv(1024)) 780 | s.close() 781 | # The source IPv6 address can't be spoofed to the IPv4 destination 782 | self.assertIn("10.0.2.2", read_log()) 783 | self.assertIn("local-fwd PP done", p.stdout_line()) 784 | 785 | def test_tcp_pp_local_fwd_v4tov6(self): 786 | ''' Test inbound TCP v4 proxy-protocol to v6 destination ''' 787 | g_echo_port, read_log = self.start_tcp_echo(guest=True, log=True) 788 | p = self.prun("-L tcppp://0.0.0.0:0:[fd00::100]:%s" % g_echo_port) 789 | self.assertStartSync(p) 790 | port = self.assertListenLine(p, "local-fwd Local PP listen") 791 | 792 | with self.guest_netns(): 793 | self.assertTcpEcho(ip="::1", port=g_echo_port) 794 | self.assertIn("::1", read_log()) 795 | 796 | s = utils.connect(port=port, ip="127.0.0.1") 797 | s.sendall(b"PROXY TCP4 1.2.3.4 5.6.7.8 1 2\r\n") 798 | self.assertIn("local-fwd PP conn", p.stdout_line()) 799 | s.sendall(b"alamakota") 800 | self.assertEqual(b"alamakota", s.recv(1024)) 801 | s.close() 802 | # The source IPv6 address can't be spoofed to the IPv4 destination 803 | self.assertIn("[fd00::2]", read_log()) 804 | self.assertIn("local-fwd PP done", p.stdout_line()) 805 | 806 | 807 | class RoutingTestSecurity(base.TestCase): 808 | @base.isolateHostNetwork() 809 | def test_disable_host_networks(self): 810 | ''' Test tcp routing security, specifically --disable-host-networks option. ''' 811 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 812 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 813 | p = self.prun("") 814 | self.assertStartSync(p) 815 | with self.guest_netns(): 816 | for dst in ("192.168.1.100", "3ffe::100"): 817 | self.assertTcpRefusedError(port=echo_tcp_port, ip=dst) 818 | s = utils.connect(port=echo_udp_port, ip=dst, udp=True) 819 | s.sendall(b"ala") 820 | s.close() 821 | 822 | # Connections from host, to have something in logs. The 823 | # connection attempts above should not trigger any logs. 824 | self.assertTcpEcho(ip="127.0.0.1", src='127.1.2.3', port=echo_tcp_port) 825 | self.assertIn("127.1.2.3", tcp_log()) 826 | 827 | self.assertUdpEcho(ip="127.0.0.1", src='127.1.2.4', port=echo_udp_port) 828 | self.assertIn("127.1.2.4", udp_log()) 829 | 830 | @base.isolateHostNetwork() 831 | def test_disabe_routing(self): 832 | '''Test tcp routing security, specifically by default routing should 833 | be disabled. This test is the same as test above.''' 834 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 835 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 836 | p = self.prun("") 837 | self.assertStartSync(p) 838 | with self.guest_netns(): 839 | for dst in ("192.168.1.100", "3ffe::100"): 840 | self.assertTcpRefusedError(port=echo_tcp_port, ip=dst) 841 | s = utils.connect(port=echo_udp_port, ip=dst, udp=True) 842 | s.sendall(b"ala") 843 | s.close() 844 | 845 | # Connections from host, to have something in logs. The 846 | # connection attempts above should not trigger any logs. 847 | self.assertTcpEcho(ip="127.0.0.1", src='127.1.2.3', port=echo_tcp_port) 848 | self.assertIn("127.1.2.3", tcp_log()) 849 | 850 | self.assertUdpEcho(ip="127.0.0.1", src='127.1.2.4', port=echo_udp_port) 851 | self.assertIn("127.1.2.4", udp_log()) 852 | 853 | @base.isolateHostNetwork() 854 | def test_source_ipv4(self): 855 | ''' Test --source-ipv4 option''' 856 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 857 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 858 | p = self.prun("--enable-host --source-ipv4=127.4.3.2") 859 | self.assertStartSync(p) 860 | with self.guest_netns(): 861 | self.assertTcpEcho(ip="192.168.1.100", port=echo_tcp_port) 862 | self.assertIn("Routing conn new", p.stdout_line()) 863 | self.assertIn("Routing conn done: l=", p.stdout_line()) 864 | self.assertIn("127.4.3.2", tcp_log()) 865 | 866 | self.assertUdpEcho(ip="192.168.1.100", port=echo_udp_port) 867 | self.assertIn("Routing conn new", p.stdout_line()) 868 | self.assertIn("127.4.3.2", udp_log()) 869 | 870 | @base.isolateHostNetwork() 871 | def test_source_ipv6(self): 872 | ''' Test --source-ipv6 option''' 873 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 874 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 875 | p = self.prun("-enable-host -source-ipv6=::1") 876 | self.assertStartSync(p) 877 | with self.guest_netns(): 878 | self.assertTcpEcho(ip="3ffe::100", port=echo_tcp_port) 879 | self.assertIn("Routing conn new", p.stdout_line()) 880 | self.assertIn("Routing conn done: l=", p.stdout_line()) 881 | self.assertIn("::1", tcp_log()) 882 | 883 | try: 884 | self.assertUdpEcho(ip="3ffe::100", port=echo_udp_port) 885 | except: 886 | pass 887 | self.assertIn("Routing conn new", p.stdout_line()) 888 | self.assertIn("::1", udp_log()) 889 | 890 | @base.isolateHostNetwork() 891 | def test_allow_range_all_ports(self): 892 | ''' Test --allow and --deny with disabled routing - all ports''' 893 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 894 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 895 | p = self.prun("--allow=tcp://192.168.1.100,udp://192.168.1.100,tcp://[3ffe::100],udp://[3ffe::100] --deny=tcp://192.168.1.100,tcp://[3ffe::100]") 896 | self.assertStartSync(p) 897 | with self.guest_netns(): 898 | for dst in ("192.168.1.100", "3ffe::100"): 899 | self.assertTcpRefusedError(port=echo_tcp_port, ip=dst) 900 | s = utils.connect(port=echo_udp_port, ip=dst, udp=True) 901 | s.sendall(b"ala") 902 | s.close() 903 | self.assertIn(dst, udp_log()) 904 | 905 | # Connections from host, to have something in logs. The 906 | # connection attempts above should not trigger any logs. 907 | self.assertTcpEcho(ip="127.0.0.1", src='127.1.2.3', port=echo_tcp_port) 908 | self.assertIn("127.1.2.3", tcp_log()) 909 | 910 | @base.isolateHostNetwork() 911 | def test_deny_range_some_ports(self): 912 | ''' Test --allow and --deny with --disable-routing - some ports ''' 913 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 914 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 915 | p = self.prun("--enable-host --allow=udp://192.168.1.100:100-200 --allow=tcp://192.168.1.100:100-200 --deny=tcp://192.168.1.100:%d,tcp://[3ffe::100]:%d-%d" % ( 916 | echo_tcp_port, echo_tcp_port, echo_tcp_port)) 917 | self.assertStartSync(p) 918 | with self.guest_netns(): 919 | for dst in ("192.168.1.100", "3ffe::100"): 920 | self.assertTcpRefusedError(port=echo_tcp_port, ip=dst) 921 | s = utils.connect(port=echo_udp_port, ip=dst, udp=True) 922 | s.sendall(b"ala") 923 | s.close() 924 | self.assertIn(dst, udp_log()) 925 | 926 | # Connections from host, to have something in logs. The 927 | # connection attempts above should not trigger any logs. 928 | self.assertTcpEcho(ip="127.0.0.1", src='127.1.2.3', port=echo_tcp_port) 929 | self.assertIn("127.1.2.3", tcp_log()) 930 | 931 | @base.isolateHostNetwork() 932 | def test_allow_cover_cases(self): 933 | ''' Test --allow parsing corner cases ''' 934 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 935 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 936 | p = self.prun("--allow=192.168.1.100:%d" % (echo_tcp_port,)) 937 | self.assertStartSync(p) 938 | with self.guest_netns(): 939 | self.assertTcpEcho(port=echo_tcp_port, ip="192.168.1.100") 940 | self.assertIn("192.168.1.100", tcp_log()) 941 | p.close() 942 | 943 | # Make it not a valid DNS label, to allow dns to fail 944 | # quick. Otherwise the thing will stall for > 20s on DNS 945 | # resolution. Remember we don't have access to host resolver 946 | # from the isolateHostNetwork test. 947 | p = self.prun("--allow=notahostó") 948 | e = p.stderr_line() 949 | self.assertIn('invalid value "notahostó" for flag -allow: lookup notahostó', e) 950 | p.close() 951 | 952 | p = self.prun("--allow=localhost:a") 953 | o = p.stdout_line() 954 | self.assertFalse(o) 955 | e = p.stderr_line() 956 | self.assertIn('invalid value "localhost:a" for flag -allow: strconv.ParseUint: parsing "a": invalid syntax', e) 957 | p.close() 958 | 959 | p = self.prun("--allow=localhost:0-b") 960 | o = p.stdout_line() 961 | self.assertFalse(o) 962 | e = p.stderr_line() 963 | self.assertIn('invalid value "localhost:0-b" for flag -allow: strconv.ParseUint: parsing "b": invalid syntax', e) 964 | p.close() 965 | 966 | p = self.prun("--allow=localhost:2-1") 967 | o = p.stdout_line() 968 | self.assertFalse(o) 969 | e = p.stderr_line() 970 | self.assertIn('invalid value "localhost:2-1" for flag -allow: min port must be smaller equal than max', e) 971 | p.close() 972 | 973 | @base.isolateHostNetwork() 974 | def test_remote_srv(self): 975 | ''' Test -R=tcp://:2222:tcp.echo.server@srv-8600:0 syntax ''' 976 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 977 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 978 | dns_port, dns = self.start_dns("tcp.echo.server.=localhost:%d" % echo_tcp_port, 979 | "udp.echo.server.=localhost:%d" % echo_udp_port) 980 | p = self.prun("-dns-ttl=0 -R=tcp://:2222:tcp.echo.server@srv-%d:0 -R=udp://:2222:udp.echo.server@srv-%d:0" % (dns_port, dns_port)) 981 | 982 | self.assertStartSync(p) 983 | self.assertIn("Accepting", p.stdout_line()) 984 | self.assertIn("Accepting", p.stdout_line()) 985 | 986 | with self.guest_netns(): 987 | self.assertTcpEcho(port=2222, ip="10.0.2.2") 988 | self.assertIn("%s" % echo_tcp_port, p.stdout_line()) 989 | self.assertIn("%s" % echo_tcp_port, p.stdout_line()) 990 | self.assertUdpEcho(port=2222, ip="10.0.2.2") 991 | self.assertIn("%s" % echo_udp_port, p.stdout_line()) 992 | 993 | dns.close() 994 | s = utils.connect(port=2222, ip="10.0.2.2") 995 | s.close() 996 | self.assertIn("dns lookup error", p.stdout_line()) 997 | s = utils.connect(port=2222, ip="10.0.2.2", udp=True) 998 | s.sendall(b"ala") 999 | s.close() 1000 | self.assertIn("dns lookup error" , p.stdout_line()) 1001 | 1002 | self.assertIn("127.0.0.1", tcp_log()) 1003 | self.assertTcpEcho(ip="127.0.0.1", src='127.1.2.4', port=echo_tcp_port) 1004 | self.assertIn("127.1.2.4", tcp_log()) 1005 | 1006 | self.assertIn("127.0.0.1", udp_log()) 1007 | self.assertUdpEcho(ip="127.0.0.1", src='127.1.2.4', port=echo_udp_port) 1008 | self.assertIn("127.1.2.4", udp_log()) 1009 | 1010 | @base.isolateHostNetwork() 1011 | def test_remote_srv_withut_dns(self): 1012 | '''Test -R=tcp://:2222:tcp.echo.server@srv-8600:0 syntax but when dns 1013 | server is down. Most importantly - starting the app with dns dwown. ''' 1014 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 1015 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 1016 | 1017 | dns_port = 1 1018 | p = self.prun("-dns-ttl=0 -R=tcp://:2222:tcp.echo.server@srv-%d:0 -R=udp://:2222:udp.echo.server@srv-%d:0" % (dns_port, dns_port)) 1019 | 1020 | self.assertStartSync(p) 1021 | self.assertIn("Accepting", p.stdout_line()) 1022 | self.assertIn("Accepting", p.stdout_line()) 1023 | 1024 | with self.guest_netns(): 1025 | s = utils.connect(port=2222, ip="10.0.2.2", timeout=0.3) 1026 | s.close() 1027 | self.assertIn("10.0.2.2:2222/tcp.echo.server@srv-1-failed remote-fwd dns lookup error", p.stdout_line()) 1028 | s = utils.connect(port=2222, ip="10.0.2.2", udp=True) 1029 | s.sendall(b"ala") 1030 | s.close() 1031 | self.assertIn("10.0.2.2:2222/udp.echo.server@srv-1-failed remote-fwd dns lookup error" , p.stdout_line()) 1032 | 1033 | p.close() 1034 | 1035 | # but when local binding, this hard fails 1036 | p = self.prun("-dns-ttl=0 -R=tcp://tcp.echo.server@srv-%d:0::2222" % (dns_port, )) 1037 | self.assertIn("Joininig", p.stderr_line()) 1038 | self.assertIn("Opening tun ", p.stderr_line()) 1039 | self.assertIn('[!] Failed to resolve bind address. dns lookup error of tcp addr: Failed to lookup SRV "tcp.echo.server"', p.stderr_line()) 1040 | 1041 | p.close() 1042 | self.assertEqual(p.rc, 246, "exit code should be 246") 1043 | 1044 | @base.isolateHostNetwork() 1045 | def test_remote_srv_ipv6(self): 1046 | ''' Test -R=tcp://:2222:[tcp.echo.server@srv-[::1]:8600]:0 syntax ''' 1047 | echo_tcp_port, tcp_log = self.start_tcp_echo(log=True) 1048 | echo_udp_port, udp_log = self.start_udp_echo(log=True) 1049 | dns_port, dns = self.start_dns("--addr=[::1]", 1050 | "tcp.echo.server.=localhost:%d" % echo_tcp_port, 1051 | "udp.echo.server.=localhost:%d" % echo_udp_port) 1052 | p = self.prun("-dns-ttl=0 -R=tcp://:2222:[tcp.echo.server@srv-[::1]:%d]:0 -R=udp://:2222:[udp.echo.server@srv-[::1]:%d]:0" % (dns_port, dns_port)) 1053 | 1054 | self.assertStartSync(p) 1055 | self.assertIn("Accepting", p.stdout_line()) 1056 | self.assertIn("Accepting", p.stdout_line()) 1057 | 1058 | with self.guest_netns(): 1059 | self.assertTcpEcho(port=2222, ip="10.0.2.2") 1060 | self.assertIn("%s" % echo_tcp_port, p.stdout_line()) 1061 | self.assertIn("%s" % echo_tcp_port, p.stdout_line()) 1062 | self.assertUdpEcho(port=2222, ip="10.0.2.2") 1063 | self.assertIn("%s" % echo_udp_port, p.stdout_line()) 1064 | 1065 | dns.close() 1066 | s = utils.connect(port=2222, ip="10.0.2.2") 1067 | s.close() 1068 | self.assertIn("dns lookup error", p.stdout_line()) 1069 | s = utils.connect(port=2222, ip="10.0.2.2", udp=True) 1070 | s.sendall(b"ala") 1071 | s.close() 1072 | self.assertIn("dns lookup error" , p.stdout_line()) 1073 | 1074 | self.assertIn("127.0.0.1", tcp_log()) 1075 | self.assertTcpEcho(ip="127.0.0.1", src='127.1.2.4', port=echo_tcp_port) 1076 | self.assertIn("127.1.2.4", tcp_log()) 1077 | 1078 | self.assertIn("127.0.0.1", udp_log()) 1079 | self.assertUdpEcho(ip="127.0.0.1", src='127.1.2.4', port=echo_udp_port) 1080 | self.assertIn("127.1.2.4", udp_log()) 1081 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | import re 4 | import shlex 5 | import string 6 | import socket 7 | 8 | 9 | IP_FREEBIND = 15 10 | 11 | def test_encode_shell(): 12 | a = shlex.split(encode_shell(['a', 'b', 'c'])) 13 | assert a == ['a', 'b', 'c'] 14 | a = shlex.split(encode_shell(['\\a', '!b', '\tc'])) 15 | assert a == ['\\a', '!b', '\tc'] 16 | a = shlex.split(encode_shell(['\\a \tc'])) 17 | assert a == ['\\a \tc'] 18 | a = shlex.split(encode_shell(['"\''])) 19 | assert a == ['"\''], repr(a) 20 | a = shlex.split(encode_shell(['"abc'])) 21 | assert a == ['"abc'] 22 | a = shlex.split(encode_shell(["'abc\""])) 23 | assert a == ["'abc\""] 24 | a = shlex.split('--test="masala chicken" --test=\'chicken masala\'') 25 | assert a == ["--test=masala chicken", "--test=chicken masala"] 26 | a = encode_shell(['--test=masala chicken', '--test=chicken masala']) 27 | assert a == "--test='masala chicken' --test='chicken masala'" 28 | 29 | 30 | # The opposite of shlex.split(). It doesn't matter how the stuff is 31 | # going to be encoded, as long as shlex() and potentially bash will 32 | # parse it the same way. With regard to tabs and special chars we 33 | # kindof lost, as passing them via bash is hard. But we should make 34 | # sure at least quotes and spaces work as intended. 35 | # 36 | # Thre is a special exception for parsing --param=argument syntax. 37 | # Although technicall sound, most likely you don't want to encode it 38 | # like that: ' "--param=the argument" ', you most likely want: 39 | # '--pram="the argument"', so there's an exception for it. 40 | 41 | PARAM = re.compile('^--(?P[a-z_-]+)[ =](?P.*)$') 42 | ACCEPTABLE_CHARS = set(string.printable) - set(string.whitespace) - set("'\"\\&#!`()[]{}$|") 43 | 44 | def encode_shell(params): 45 | r''' 46 | >>> test_encode_shell() 47 | ''' 48 | s = [] 49 | for token in params: 50 | m = PARAM.match(token) 51 | if m: 52 | m = m.groupdict() 53 | token = m['rest'] 54 | if not set(token) - ACCEPTABLE_CHARS: 55 | enc_token = token 56 | else: 57 | if "'" not in token: 58 | enc_token = "'" + token + "'" 59 | else: 60 | t = token.replace('`', '\\`').replace('"', '\\"') 61 | enc_token = '"' + t + '"' 62 | if not m: 63 | s.append(enc_token) 64 | else: 65 | s.append('--%s=%s' % (m['opt'], enc_token)) 66 | return ' '.join(s) 67 | 68 | 69 | 70 | def connect(ip='127.0.0.1', port=0, path=None, udp=False, src='', sport=0, 71 | cloexec=True, cleanup=None, timeout=2): 72 | if udp == False: 73 | p = socket.SOCK_STREAM 74 | else: 75 | p = socket.SOCK_DGRAM 76 | 77 | if path: 78 | s = socket.socket(socket.AF_UNIX, p) 79 | elif len(ip.split(':')) <= 2: 80 | s = socket.socket(socket.AF_INET, p) 81 | else: 82 | s = socket.socket(socket.AF_INET6, p) 83 | 84 | if cleanup: 85 | cleanup.addCleanup(s.close) 86 | 87 | s.set_inheritable(not cloexec) 88 | 89 | if src or sport > 0: 90 | s.setsockopt(socket.IPPROTO_IP, IP_FREEBIND, 1) 91 | s.bind((src, sport)) 92 | 93 | # to make tests fail, instead of halt indefintely, let's set the 94 | # default timeout to say 2 sec. 95 | s.settimeout(timeout) 96 | 97 | try: 98 | if not path: 99 | s.connect((ip, port)) 100 | else: 101 | path = path.replace("@", "\x00") 102 | s.connect(path) 103 | except socket.timeout as e: 104 | s.close() 105 | raise e 106 | 107 | return s 108 | -------------------------------------------------------------------------------- /udp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | ) 7 | 8 | type SyscallConner interface { 9 | SyscallConn() (syscall.RawConn, error) 10 | } 11 | 12 | func SetReuseaddr(fd SyscallConner) error { 13 | rawConn, err := fd.SyscallConn() 14 | if err != nil { 15 | return err 16 | } 17 | return rawConn.Control(func(fd uintptr) { 18 | syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 19 | }) 20 | } 21 | 22 | func MagicDialUDP(laddr, raddr *net.UDPAddr) (*KaUDPConn, error) { 23 | setDialerOptions := func(_, _ string, c syscall.RawConn) error { 24 | return c.Control(func(s_ uintptr) { 25 | s := int(s_) 26 | syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 27 | }) 28 | } 29 | 30 | dialer := net.Dialer{LocalAddr: laddr, Control: setDialerOptions} 31 | c, err := dialer.Dial(raddr.Network(), raddr.String()) 32 | if err != nil { 33 | return nil, err 34 | } 35 | cx := c.(*net.UDPConn) 36 | return &KaUDPConn{Conn: cx}, nil 37 | } 38 | -------------------------------------------------------------------------------- /unconn/unconn.go: -------------------------------------------------------------------------------- 1 | package unconn 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | ) 7 | 8 | const ( 9 | IPV6_RECVORIGDSTADDR = 74 10 | IPV6_ORIGDSTADDR = 74 11 | ) 12 | 13 | func Prime(udpConn *net.UDPConn) error { 14 | rawConn, err := udpConn.SyscallConn() 15 | if err != nil { 16 | return err 17 | } 18 | return rawConn.Control(func(_fd uintptr) { 19 | fd := int(_fd) 20 | // IP_RECVORIGDSTADDR triggers IP_ORIGDSTADDR CMSG on recv. Ions. 21 | syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) 22 | 23 | // IPV6_RECVORIGDSTADDR triggers IPV6_ORIGDSTADDR CMSG on 24 | // recv. It gives us server IP and sever Port for IPv6 25 | // connections. 26 | syscall.SetsockoptInt(fd, syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) 27 | }) 28 | } 29 | 30 | func ExtractCMSGDestinationAddr(oob []byte) (net.UDPAddr, bool) { 31 | scms, err := syscall.ParseSocketControlMessage(oob) 32 | if err != nil { 33 | return net.UDPAddr{}, false 34 | } 35 | 36 | var ( 37 | a net.UDPAddr 38 | ok bool 39 | ) 40 | for _, m := range scms { 41 | h := m.Header 42 | if h.Level == syscall.SOL_IP && h.Type == syscall.IP_ORIGDSTADDR { 43 | var ip [4]byte 44 | copy(ip[:], m.Data[4:8]) 45 | a.IP = net.IP(ip[:]) 46 | a.Port = (int(m.Data[2]) << 8) | int(m.Data[3]) 47 | ok = true 48 | } else if h.Level == syscall.SOL_IPV6 && h.Type == IPV6_ORIGDSTADDR { 49 | var ip [16]byte 50 | copy(ip[:], m.Data[8:24]) 51 | a.IP = net.IP(ip[:]) 52 | a.Port = (int(m.Data[2]) << 8) | int(m.Data[3]) 53 | ok = true 54 | } 55 | } 56 | return a, ok 57 | } 58 | 59 | func Write(udpConn *net.UDPConn, src net.IP, dst *net.UDPAddr, b []byte) (int, error) { 60 | var oob []byte 61 | var ifi []byte 62 | if src.To4() != nil { 63 | // Blob contains ready CMSG with IP_PKTINFO and dst_ip 64 | // is 127.0.0.2 and dst interface is uint32(1) 65 | oob = []byte("\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x01\x00\x00\x00\x7f\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00") 66 | ifi = oob[16:20] 67 | copy(oob[20:24], src.To4()) 68 | } else { 69 | // Blob contains ready CMSG with IPV6_PKTINFO and 70 | // dst_ip is ::2 and dst interface is uint32(1) 71 | oob = []byte("$\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01\x00\x00\x00\x00\x00\x00\x00") 72 | ifi = oob[32:36] 73 | copy(oob[16:32], src.To16()) 74 | } 75 | 76 | // Overwrite target interface to uint32(0) 77 | copy(ifi, []byte("\x00\x00\x00\x00")) 78 | 79 | n, _, err := udpConn.WriteMsgUDP(b, oob, dst) 80 | return n, err 81 | } 82 | 83 | func Read(udpConn *net.UDPConn, buf []byte) (int, *net.UDPAddr, *net.UDPAddr, error) { 84 | oob := make([]byte, 1024) 85 | n, oobn, _, raddr, err := udpConn.ReadMsgUDP(buf, oob) 86 | 87 | if err != nil { 88 | return 0, nil, nil, err 89 | } 90 | 91 | laddr, ok := ExtractCMSGDestinationAddr(oob[:oobn]) 92 | if ok == false { 93 | return n, nil, raddr, err 94 | } 95 | return n, &laddr, raddr, err 96 | } 97 | --------------------------------------------------------------------------------