├── .github ├── CODEOWNERS └── workflows │ └── build_test.yml ├── .gitignore ├── GNUmakefile ├── LICENSE ├── README.md ├── cmd └── sockaddr │ ├── .gitignore │ ├── GNUmakefile │ ├── README.md │ ├── command │ ├── autohelp.go │ ├── dump.go │ ├── eval.go │ ├── multi_arg.go │ ├── rfc.go │ ├── rfc_list.go │ ├── tech_support.go │ └── version.go │ ├── commands.go │ ├── main.go │ ├── regression │ ├── .gitignore │ ├── GNUmakefile │ ├── expected │ │ ├── sockaddr.out │ │ ├── sockaddr_-v.out │ │ ├── sockaddr_dump-00-help.out │ │ ├── sockaddr_dump-01.out │ │ ├── sockaddr_dump-02.out │ │ ├── sockaddr_dump-03.out │ │ ├── sockaddr_dump-04.out │ │ ├── sockaddr_dump-05.out │ │ ├── sockaddr_dump-06.out │ │ ├── sockaddr_dump-07.out │ │ ├── sockaddr_dump-08.out │ │ ├── sockaddr_dump-09.out │ │ ├── sockaddr_dump-10.out │ │ ├── sockaddr_dump-11.out │ │ ├── sockaddr_dump-12.out │ │ ├── sockaddr_dump-13.out │ │ ├── sockaddr_dump-14.out │ │ ├── sockaddr_eval-00-help.out │ │ ├── sockaddr_eval-01.out │ │ ├── sockaddr_eval-02.out │ │ ├── sockaddr_eval-03.out │ │ ├── sockaddr_eval-04.out │ │ ├── sockaddr_eval-05.out │ │ ├── sockaddr_rfc-00.out │ │ ├── sockaddr_rfc-01.out │ │ ├── sockaddr_rfc_list-00.out │ │ ├── sockaddr_rfc_list-01.out │ │ ├── sockaddr_rfc_list-02.out │ │ ├── sockaddr_version-00.out │ │ └── sockaddr_version-01.out │ ├── run_all.sh │ ├── run_one.sh │ ├── test_sockaddr.sh │ ├── test_sockaddr_dump-00-help.sh │ ├── test_sockaddr_dump-01.sh │ ├── test_sockaddr_dump-02.sh │ ├── test_sockaddr_dump-03.sh │ ├── test_sockaddr_dump-04.sh │ ├── test_sockaddr_dump-05.sh │ ├── test_sockaddr_dump-06.sh │ ├── test_sockaddr_dump-07.sh │ ├── test_sockaddr_dump-08.sh │ ├── test_sockaddr_dump-09.sh │ ├── test_sockaddr_dump-10.sh │ ├── test_sockaddr_dump-11.sh │ ├── test_sockaddr_dump-12.sh │ ├── test_sockaddr_dump-13.sh │ ├── test_sockaddr_dump-14.sh │ ├── test_sockaddr_eval-00-help.sh │ ├── test_sockaddr_eval-01.sh │ ├── test_sockaddr_eval-02.sh │ ├── test_sockaddr_eval-03.sh │ ├── test_sockaddr_eval-04.sh │ ├── test_sockaddr_eval-05.sh │ ├── test_sockaddr_rfc-00.sh │ ├── test_sockaddr_rfc-01.sh │ ├── test_sockaddr_rfc_list-00.sh │ ├── test_sockaddr_rfc_list-01.sh │ ├── test_sockaddr_rfc_list-02.sh │ ├── test_sockaddr_version-00.sh │ └── test_sockaddr_version-01.sh │ └── version.go ├── doc.go ├── go.mod ├── go.sum ├── ifaddr.go ├── ifaddr_test.go ├── ifaddrs.go ├── ifaddrs_test.go ├── ifattr.go ├── ifattr_test.go ├── ipaddr.go ├── ipaddr_test.go ├── ipaddrs.go ├── ipaddrs_test.go ├── ipv4addr.go ├── ipv4addr_test.go ├── ipv6addr.go ├── ipv6addr_test.go ├── rfc.go ├── rfc_test.go ├── route_info.go ├── route_info_aix.go ├── route_info_android.go ├── route_info_bsd.go ├── route_info_default.go ├── route_info_linux.go ├── route_info_solaris.go ├── route_info_test.go ├── route_info_test_windows.go ├── route_info_windows.go ├── route_info_zos.go ├── route_info_zos_test.go ├── sockaddr.go ├── sockaddr_test.go ├── sockaddrs.go ├── sockaddrs_test.go ├── template ├── GNUmakefile ├── README.md ├── doc.go ├── template.go └── template_test.go ├── unixsock.go └── unixsock_test.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Each line is a file pattern followed by one or more owners. 2 | # More on CODEOWNERS files: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 3 | 4 | # Default owner 5 | * @hashicorp/team-ip-compliance @hashicorp/nomad-eng 6 | 7 | # Add override rules below. Each line is a file/folder pattern followed by one or more owners. 8 | # Being an owner means those groups or individuals will be added as reviewers to PRs affecting 9 | # those areas of the code. 10 | # Examples: 11 | # /docs/ @docs-team 12 | # *.js @js-team 13 | # *.go @go-team 14 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test Workflow 2 | 3 | on: 4 | pull_request: 5 | branches: [ "master" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | max-parallel: 30 13 | matrix: 14 | version: 15 | - 'oldstable' 16 | - 'stable' 17 | steps: 18 | - name: Checkout Code 19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 20 | - name: Setup Go 21 | uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a 22 | with: 23 | go-version: ${{ matrix.version }} 24 | - name: Test Go 25 | run: go test -v -coverprofile=coverage.out ./... 26 | - name: Build Go 27 | run: go build ./... 28 | - name: Upload coverage report 29 | uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 30 | with: 31 | path: coverage.out 32 | name: coverage-report-${{matrix.version}} 33 | - name: Display coverage report 34 | run: go tool cover -func=coverage.out 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .cover.out* 26 | coverage.html 27 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | TOOLS= golang.org/x/tools/cover 2 | GOCOVER_TMPFILE?= $(GOCOVER_FILE).tmp 3 | GOCOVER_FILE?= .cover.out 4 | GOCOVERHTML?= coverage.html 5 | FIND=`/usr/bin/which 2> /dev/null gfind find | /usr/bin/grep -v ^no | /usr/bin/head -n 1` 6 | XARGS=`/usr/bin/which 2> /dev/null gxargs xargs | /usr/bin/grep -v ^no | /usr/bin/head -n 1` 7 | 8 | test:: $(GOCOVER_FILE) 9 | @$(MAKE) -C cmd/sockaddr test 10 | 11 | cover:: coverage_report 12 | 13 | $(GOCOVER_FILE):: 14 | @${FIND} . -type d ! -path '*cmd*' ! -path '*.git*' -print0 | ${XARGS} -0 -I % sh -ec "cd % && rm -f $(GOCOVER_TMPFILE) && go test -coverprofile=$(GOCOVER_TMPFILE)" 15 | 16 | @echo 'mode: set' > $(GOCOVER_FILE) 17 | @${FIND} . -type f ! -path '*cmd*' ! -path '*.git*' -name "$(GOCOVER_TMPFILE)" -print0 | ${XARGS} -0 -n1 cat $(GOCOVER_TMPFILE) | grep -v '^mode: ' >> ${PWD}/$(GOCOVER_FILE) 18 | 19 | $(GOCOVERHTML): $(GOCOVER_FILE) 20 | go tool cover -html=$(GOCOVER_FILE) -o $(GOCOVERHTML) 21 | 22 | coverage_report:: $(GOCOVER_FILE) 23 | go tool cover -html=$(GOCOVER_FILE) 24 | 25 | audit_tools:: 26 | @go get -u github.com/golang/lint/golint && echo "Installed golint:" 27 | @go get -u github.com/fzipp/gocyclo && echo "Installed gocyclo:" 28 | @go get -u github.com/remyoudompheng/go-misc/deadcode && echo "Installed deadcode:" 29 | @go get -u github.com/client9/misspell/cmd/misspell && echo "Installed misspell:" 30 | @go get -u github.com/gordonklaus/ineffassign && echo "Installed ineffassign:" 31 | 32 | audit:: 33 | deadcode 34 | go tool vet -all *.go 35 | go tool vet -shadow=true *.go 36 | golint *.go 37 | ineffassign . 38 | gocyclo -over 65 *.go 39 | misspell *.go 40 | 41 | clean:: 42 | rm -f $(GOCOVER_FILE) $(GOCOVERHTML) 43 | 44 | dev:: 45 | @go build 46 | @$(MAKE) -B -C cmd/sockaddr sockaddr 47 | 48 | install:: 49 | @go install 50 | @$(MAKE) -C cmd/sockaddr install 51 | 52 | doc:: 53 | @echo Visit: http://127.0.0.1:6161/pkg/github.com/hashicorp/go-sockaddr/ 54 | godoc -http=:6161 -goroot $GOROOT 55 | 56 | world:: 57 | @set -e; \ 58 | for os in solaris darwin freebsd linux windows android; do \ 59 | for arch in amd64; do \ 60 | printf "Building on %s-%s\n" "$${os}" "$${arch}" ; \ 61 | env GOOS="$${os}" GOARCH="$${arch}" go build -o /dev/null; \ 62 | done; \ 63 | done 64 | 65 | $(MAKE) -C cmd/sockaddr world 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-sockaddr 2 | 3 | ## `sockaddr` Library 4 | 5 | Socket address convenience functions for Go. `go-sockaddr` is a convenience 6 | library that makes doing the right thing with IP addresses easy. `go-sockaddr` 7 | is loosely modeled after the UNIX `sockaddr_t` and creates a union of the family 8 | of `sockaddr_t` types (see below for an ascii diagram). Library documentation 9 | is available 10 | at 11 | [https://godoc.org/github.com/hashicorp/go-sockaddr](https://godoc.org/github.com/hashicorp/go-sockaddr). 12 | The primary intent of the library was to make it possible to define heuristics 13 | for selecting the correct IP addresses when a configuration is evaluated at 14 | runtime. See 15 | the 16 | [docs](https://godoc.org/github.com/hashicorp/go-sockaddr), 17 | [`template` package](https://godoc.org/github.com/hashicorp/go-sockaddr/template), 18 | tests, 19 | and 20 | [CLI utility](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr) 21 | for details and hints as to how to use this library. 22 | 23 | For example, with this library it is possible to find an IP address that: 24 | 25 | * is attached to a default route 26 | ([`GetDefaultInterfaces()`](https://godoc.org/github.com/hashicorp/go-sockaddr#GetDefaultInterfaces)) 27 | * is contained within a CIDR block ([`IfByNetwork()`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByNetwork)) 28 | * is an RFC1918 address 29 | ([`IfByRFC("1918")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC)) 30 | * is ordered 31 | ([`OrderedIfAddrBy(args)`](https://godoc.org/github.com/hashicorp/go-sockaddr#OrderedIfAddrBy) where 32 | `args` includes, but is not limited 33 | to, 34 | [`AscIfType`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscIfType), 35 | [`AscNetworkSize`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscNetworkSize)) 36 | * excludes all IPv6 addresses 37 | ([`IfByType("^(IPv4)$")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByType)) 38 | * is larger than a `/32` 39 | ([`IfByMaskSize(32)`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByMaskSize)) 40 | * is not on a `down` interface 41 | ([`ExcludeIfs("flags", "down")`](https://godoc.org/github.com/hashicorp/go-sockaddr#ExcludeIfs)) 42 | * preferences an IPv6 address over an IPv4 address 43 | ([`SortIfByType()`](https://godoc.org/github.com/hashicorp/go-sockaddr#SortIfByType) + 44 | [`ReverseIfAddrs()`](https://godoc.org/github.com/hashicorp/go-sockaddr#ReverseIfAddrs)); and 45 | * excludes any IP in RFC6890 address 46 | ([`IfByRFC("6890")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC)) 47 | 48 | Or any combination or variation therein. 49 | 50 | There are also a few simple helper functions such as `GetPublicIP` and 51 | `GetPrivateIP` which both return strings and select the first public or private 52 | IP address on the default interface, respectively. Similarly, there is also a 53 | helper function called `GetInterfaceIP` which returns the first usable IP 54 | address on the named interface. 55 | 56 | ## `sockaddr` CLI 57 | 58 | Given the possible complexity of the `sockaddr` library, there is a CLI utility 59 | that accompanies the library, also 60 | called 61 | [`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr). 62 | The 63 | [`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr) 64 | utility exposes nearly all of the functionality of the library and can be used 65 | either as an administrative tool or testing tool. To install 66 | the 67 | [`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr), 68 | run: 69 | 70 | ```text 71 | $ go install github.com/hashicorp/go-sockaddr/cmd/sockaddr@latest 72 | ``` 73 | 74 | If you're familiar with UNIX's `sockaddr` struct's, the following diagram 75 | mapping the C `sockaddr` (top) to `go-sockaddr` structs (bottom) and 76 | interfaces will be helpful: 77 | 78 | ``` 79 | +-------------------------------------------------------+ 80 | | | 81 | | sockaddr | 82 | | SockAddr | 83 | | | 84 | | +--------------+ +----------------------------------+ | 85 | | | sockaddr_un | | | | 86 | | | SockAddrUnix | | sockaddr_in{,6} | | 87 | | +--------------+ | IPAddr | | 88 | | | | | 89 | | | +-------------+ +--------------+ | | 90 | | | | sockaddr_in | | sockaddr_in6 | | | 91 | | | | IPv4Addr | | IPv6Addr | | | 92 | | | +-------------+ +--------------+ | | 93 | | | | | 94 | | +----------------------------------+ | 95 | | | 96 | +-------------------------------------------------------+ 97 | ``` 98 | 99 | ## Inspiration and Design 100 | 101 | There were many subtle inspirations that led to this design, but the most direct 102 | inspiration for the filtering syntax was 103 | OpenBSD's 104 | [`pf.conf(5)`](https://www.freebsd.org/cgi/man.cgi?query=pf.conf&apropos=0&sektion=0&arch=default&format=html#PARAMETERS) firewall 105 | syntax that lets you select the first IP address on a given named interface. 106 | The original problem stemmed from: 107 | 108 | * needing to create immutable images using [Packer](https://www.packer.io) that 109 | ran the [Consul](https://www.consul.io) process (Consul can only use one IP 110 | address at a time); 111 | * images that may or may not have multiple interfaces or IP addresses at 112 | runtime; and 113 | * we didn't want to rely on configuration management to render out the correct 114 | IP address if the VM image was being used in an auto-scaling group. 115 | 116 | Instead we needed some way to codify a heuristic that would correctly select the 117 | right IP address but the input parameters were not known when the image was 118 | created. 119 | -------------------------------------------------------------------------------- /cmd/sockaddr/.gitignore: -------------------------------------------------------------------------------- 1 | /sockaddr 2 | /bin/ 3 | -------------------------------------------------------------------------------- /cmd/sockaddr/GNUmakefile: -------------------------------------------------------------------------------- 1 | BIN:=sockaddr 2 | SRCS:=$(shell find . -name '*.go' ! -path '*/vendor/*') 3 | GOPATH:=$(shell go env GOPATH) 4 | 5 | .DEFAULT_GOAL := dev 6 | 7 | .PHONY: dev 8 | dev: $(BIN) 9 | @install $(BIN) ${GOPATH}/bin/ 10 | 11 | $(BIN): $(SRCS) 12 | go build -o $@ 13 | 14 | .PHONY: clean 15 | clean:: 16 | rm -f $(BIN) bin/* regression/*.diff 17 | rmdir bin/ || true 18 | 19 | .PHONY: install 20 | install:: $(BIN) 21 | install sockaddr ${GOPATH}/bin/ 22 | 23 | .PHONY: test 24 | test:: $(BIN) 25 | @$(MAKE) -C regression 26 | 27 | .PHONY: world 28 | world:: 29 | mkdir -p bin 30 | gox -os="solaris darwin freebsd linux windows android" -arch="386 amd64 arm" -output="bin/sockaddr_{{.OS}}_{{.Arch}}" . 31 | -------------------------------------------------------------------------------- /cmd/sockaddr/README.md: -------------------------------------------------------------------------------- 1 | # `sockaddr(1)` 2 | 3 | `sockaddr` is a CLI utility that wraps and exposes `go-sockaddr` functionality 4 | from the command line. 5 | 6 | ```text 7 | $ go install github.com/hashicorp/go-sockaddr/cmd/sockaddr@latest 8 | ``` 9 | 10 | ```text 11 | % sockaddr -h 12 | usage: sockaddr [--version] [--help] [] 13 | 14 | Available commands are: 15 | dump Parses IP addresses 16 | eval Evaluates a sockaddr template 17 | rfc Test to see if an IP is part of a known RFC 18 | version Prints the sockaddr version 19 | ``` 20 | 21 | ## `sockaddr dump` 22 | 23 | ```text 24 | Usage: sockaddr dump [options] input [...] 25 | 26 | Parse address(es) or interface and dumps various output. 27 | 28 | Options: 29 | 30 | -4 Parse the input as IPv4 only 31 | -6 Parse the input as IPv6 only 32 | -H Machine readable output 33 | -I Parse the argument as an interface name 34 | -i Parse the input as IP address (either IPv4 or IPv6) 35 | -n Show only the value 36 | -o Name of an attribute to pass through 37 | -u Parse the input as a UNIX Socket only 38 | ``` 39 | 40 | ### `sockaddr dump` example output 41 | 42 | By default it prints out all available information unless the `-o` flag is 43 | specified. 44 | 45 | ```text 46 | % sockaddr dump 127.0.0.2/8 47 | Attribute Value 48 | type IPv4 49 | string 127.0.0.2/8 50 | host 127.0.0.2 51 | address 127.0.0.2 52 | port 0 53 | netmask 255.0.0.0 54 | network 127.0.0.0/8 55 | mask_bits 8 56 | binary 01111111000000000000000000000010 57 | hex 7f000002 58 | first_usable 127.0.0.1 59 | last_usable 127.255.255.254 60 | octets 127 0 0 2 61 | size 16777216 62 | broadcast 127.255.255.255 63 | uint32 2130706434 64 | DialPacket "udp4" "" 65 | DialStream "tcp4" "" 66 | ListenPacket "udp4" "" 67 | ListenStream "tcp4" "" 68 | $ sockaddr dump -H -o host,address,port -o mask_bits 127.0.0.3:8600 69 | host 127.0.0.3:8600 70 | address 127.0.0.3 71 | port 8600 72 | mask_bits 32 73 | $ sockaddr dump -H -n -o host,address,port -o mask_bits 127.0.0.3:8600 74 | 127.0.0.3:8600 75 | 127.0.0.3 76 | 8600 77 | 32 78 | $ sockaddr dump -o type,address,hex,network '[2001:db8::3/32]' 79 | Attribute Value 80 | type IPv6 81 | address 2001:db8::3 82 | network 2001:db8::/32 83 | hex 20010db8000000000000000000000003 84 | $ sockaddr dump /tmp/example.sock 85 | Attribute Value 86 | type UNIX 87 | string "/tmp/example.sock" 88 | path /tmp/example.sock 89 | DialPacket "unixgram" "/tmp/example.sock" 90 | DialStream "unix" "/tmp/example.sock" 91 | ListenPacket "unixgram" "/tmp/example.sock" 92 | ListenStream "unix" "/tmp/example.sock" 93 | ``` 94 | 95 | ## `sockaddr eval` 96 | 97 | ```text 98 | Usage: sockaddr eval [options] [template ...] 99 | 100 | Parse the sockaddr template and evaluates the output. 101 | 102 | The `sockaddr` library has the potential to be very complex, 103 | which is why the `sockaddr` command supports an `eval` 104 | subcommand in order to test configurations from the command 105 | line. The `eval` subcommand automatically wraps its input 106 | with the `{{` and `}}` template delimiters unless the `-r` 107 | command is specified, in which case `eval` parses the raw 108 | input. If the `template` argument passed to `eval` is a 109 | dash (`-`), then `sockaddr eval` will read from stdin and 110 | automatically sets the `-r` flag. 111 | 112 | Options: 113 | 114 | -d Debug output 115 | -n Suppress newlines between args 116 | -r Suppress wrapping the input with {{ }} delimiters 117 | ``` 118 | 119 | Here are a few impractical examples to get you started: 120 | 121 | ```text 122 | $ sockaddr eval 'GetAllInterfaces | include "flags" "forwardable" | include "up" | sort "default,type,size" | include "RFC" "6890" | attr "address"' 123 | 172.14.6.167 124 | $ sockaddr eval 'GetDefaultInterfaces | sort "type,size" | include "RFC" "6890" | limit 1 | join "address" " "' 125 | 172.14.6.167 126 | $ sockaddr eval 'GetPublicIP' 127 | 203.0.113.4 128 | $ sockaddr eval 'GetPrivateIP' 129 | 172.14.6.167 130 | $ sockaddr eval 'GetInterfaceIP "eth0"' 131 | 172.14.6.167 132 | $ sockaddr eval 'GetAllInterfaces | include "network" "172.14.6.0/24" | attr "address"' 133 | 172.14.6.167 134 | $ sockaddr eval 'GetPrivateInterfaces | join "type" " "' 135 | IPv4 IPv6 136 | $ sockaddr eval 'GetAllInterfaces | include "flags" "forwardable" | join "address" " "' 137 | 203.0.113.4 2001:0DB8::1 138 | $ sockaddr eval 'GetAllInterfaces | include "name" "lo0" | include "type" "IPv6" | sort "address" | join "address" " "' 139 | 100:: fe80::1 140 | $ sockaddr eval '. | include "rfc" "1918" | print | len | lt 2' 141 | true 142 | $ sockaddr eval -r '{{with $ifSet := include "name" "lo0" . }}{{ range include "type" "IPv6" $ifSet | sort "address" | reverse}}{{ . }} {{end}}{{end}}' 143 | fe80::1/64 {1 16384 lo0 up|loopback|multicast} 100:: {1 16384 lo0 up|loopback|multicast} 144 | $ sockaddr eval '. | include "name" "lo0" | include "type" "IPv6" | sort "address" | join "address" " "' 145 | 100:: fe80::1 146 | $ cat <<'EOF' | sockaddr eval - 147 | {{. | include "name" "lo0" | include "type" "IPv6" | sort "address" | join "address" " "}} 148 | EOF 149 | 100:: fe80::1 150 | $ sockaddr eval 'GetPrivateInterfaces | include "flags" "forwardable|up" | include "type" "IPv4" | math "network" "+2" | attr "address"' 151 | 172.14.6.2 152 | $ cat <<'EOF' | sudo tee -a /etc/profile 153 | export CONSUL_HTTP_ADDR="http://`sockaddr eval 'GetInterfaceIP \"eth0\"'`:8500" 154 | EOF 155 | ``` 156 | 157 | ## `sockaddr rfc` 158 | 159 | ```text 160 | $ sockaddr rfc 161 | Usage: sockaddr rfc [RFC Number] [IP Address] 162 | 163 | Tests a given IP address to see if it is part of a known 164 | RFC. If the IP address belongs to a known RFC, return exit 165 | code 0 and print the status. If the IP does not belong to 166 | an RFC, return 1. If the RFC is not known, return 2. 167 | 168 | Options: 169 | 170 | -s Silent, only return different exit codes 171 | $ sockaddr rfc 1918 192.168.1.10 172 | 192.168.1.10 is part of RFC 1918 173 | $ sockaddr rfc 6890 '[::1]' 174 | 100:: is part of RFC 6890 175 | $ sockaddr rfc list 176 | 919 177 | 1112 178 | 1122 179 | 1918 180 | 2544 181 | 2765 182 | 2928 183 | 3056 184 | 3068 185 | 3171 186 | 3330 187 | 3849 188 | 3927 189 | 4038 190 | 4193 191 | 4291 192 | 4380 193 | 4773 194 | 4843 195 | 5180 196 | 5735 197 | 5737 198 | 6052 199 | 6333 200 | 6598 201 | 6666 202 | 6890 203 | 7335 204 | ``` 205 | 206 | ## `sockaddr tech-support` 207 | 208 | If one of the helper methods that derives its output from `GetDefaultInterfaces` 209 | is misbehaving, submit the output from this command as an issue along with 210 | any miscellaneous details that are specific to your environment. 211 | 212 | ```text 213 | Usage: sockaddr tech-support [options] 214 | 215 | Print out network diagnostic information that can be used by 216 | support. 217 | 218 | The `sockaddr` library relies on OS-specific commands and 219 | output which can potentially be brittle. The `tech-support` 220 | subcommand emits all of the platform-specific network 221 | details required to debug why a given `sockaddr` API call is 222 | behaving differently than expected. The `-output` flag 223 | controls the output format. The default output mode is 224 | Markdown (`md`) however a raw mode (`raw`) is available to 225 | obtain the original output. 226 | 227 | Options: 228 | 229 | -output Encode the output using one of Markdown ("md") or Raw ("raw") 230 | ``` 231 | 232 | ## `sockaddr version` 233 | 234 | The lowly version stub. 235 | 236 | ```text 237 | $ sockaddr version 238 | sockaddr 0.1.0-dev 239 | ``` 240 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/autohelp.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | 9 | wordwrap "github.com/mitchellh/go-wordwrap" 10 | "github.com/ryanuber/columnize" 11 | ) 12 | 13 | // AutoHelp specifies the necessary methods required to have their help 14 | // completely generated for them. 15 | type AutoHelp interface { 16 | Usage() string 17 | Description() string 18 | InitOpts() 19 | VisitAllFlags(func(f *flag.Flag)) 20 | } 21 | 22 | // MakeHelp generates a help string based on the capabilities of the Command 23 | func MakeHelp(c AutoHelp) string { 24 | usageText := c.Usage() 25 | 26 | // If the length of Usage() is zero, then assume this is a hidden 27 | // command. 28 | if len(usageText) == 0 { 29 | return "" 30 | } 31 | 32 | descriptionText := wordwrap.WrapString(c.Description(), 60) 33 | descrLines := strings.Split(descriptionText, "\n") 34 | prefixedLines := make([]string, len(descrLines)) 35 | for i := range descrLines { 36 | prefixedLines[i] = " " + descrLines[i] 37 | } 38 | descriptionText = strings.Join(prefixedLines, "\n") 39 | 40 | c.InitOpts() 41 | flags := []*flag.Flag{} 42 | c.VisitAllFlags(func(f *flag.Flag) { 43 | flags = append(flags, f) 44 | }) 45 | optionsText := OptionsHelpOutput(flags) 46 | 47 | var helpOutput string 48 | switch { 49 | case len(optionsText) == 0 && len(descriptionText) == 0: 50 | helpOutput = usageText 51 | case len(optionsText) == 0: 52 | helpOutput = fmt.Sprintf(`Usage: %s 53 | 54 | %s`, 55 | usageText, descriptionText) 56 | case len(descriptionText) == 0 && len(optionsText) > 0: 57 | helpOutput = fmt.Sprintf(`Usage: %s 58 | 59 | Options: 60 | 61 | %s`, 62 | usageText, optionsText) 63 | default: 64 | helpOutput = fmt.Sprintf(`Usage: %s 65 | 66 | %s 67 | 68 | Options: 69 | 70 | %s`, 71 | usageText, descriptionText, optionsText) 72 | } 73 | 74 | return strings.TrimSpace(helpOutput) 75 | } 76 | 77 | // ByOptName implements sort.Interface for flag.Flag based on the Name field. 78 | type ByName []*flag.Flag 79 | 80 | func (a ByName) Len() int { return len(a) } 81 | func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 82 | func (a ByName) Less(i, j int) bool { 83 | // Bubble up single-char args to the top of the list 84 | switch { 85 | case len(a[i].Name) == 1 && len(a[j].Name) != 1: 86 | return true 87 | case len(a[i].Name) != 1 && len(a[j].Name) == 1: 88 | return false 89 | default: 90 | // Case-insensitive sort. Use case as a tie breaker, however. 91 | a1 := strings.ToLower(a[i].Name) 92 | a2 := strings.ToLower(a[j].Name) 93 | if a1 == a2 { 94 | return a[i].Name < a[j].Name 95 | } else { 96 | return a1 < a2 97 | } 98 | } 99 | } 100 | 101 | // OptionsHelpOutput returns a string of formatted options 102 | func OptionsHelpOutput(flags []*flag.Flag) string { 103 | sort.Sort(ByName(flags)) 104 | 105 | var output []string 106 | for _, f := range flags { 107 | if len(f.Usage) == 0 { 108 | continue 109 | } 110 | 111 | output = append(output, fmt.Sprintf("-%s | %s", f.Name, f.Usage)) 112 | } 113 | 114 | optionsOutput := columnize.Format(output, &columnize.Config{ 115 | Delim: "|", 116 | Glue: " ", 117 | Prefix: " ", 118 | Empty: "", 119 | }) 120 | return optionsOutput 121 | } 122 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/dump.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/hashicorp/errwrap" 8 | sockaddr "github.com/hashicorp/go-sockaddr" 9 | "github.com/mitchellh/cli" 10 | "github.com/ryanuber/columnize" 11 | ) 12 | 13 | type DumpCommand struct { 14 | Ui cli.Ui 15 | 16 | // attrNames is a list of attribute names to include in the output 17 | attrNames []string 18 | 19 | // flags is a list of options belonging to this command 20 | flags *flag.FlagSet 21 | 22 | // machineMode changes the output format to be machine friendly 23 | // (i.e. tab-separated values). 24 | machineMode bool 25 | 26 | // valueOnly changes the output format to include only values 27 | valueOnly bool 28 | 29 | // ifOnly parses the input as an interface name 30 | ifOnly bool 31 | 32 | // ipOnly parses the input as an IP address (either IPv4 or IPv6) 33 | ipOnly bool 34 | 35 | // v4Only parses the input exclusively as an IPv4 address 36 | v4Only bool 37 | 38 | // v6Only parses the input exclusively as an IPv6 address 39 | v6Only bool 40 | 41 | // unixOnly parses the input exclusively as a UNIX Socket 42 | unixOnly bool 43 | } 44 | 45 | // Description is the long-form command help. 46 | func (c *DumpCommand) Description() string { 47 | return `Parse address(es) or interface and dumps various output.` 48 | } 49 | 50 | // Help returns the full help output expected by `sockaddr -h cmd` 51 | func (c *DumpCommand) Help() string { 52 | return MakeHelp(c) 53 | } 54 | 55 | // InitOpts is responsible for setup of this command's configuration via the 56 | // command line. InitOpts() does not parse the arguments (see parseOpts()). 57 | func (c *DumpCommand) InitOpts() { 58 | c.flags = flag.NewFlagSet("dump", flag.ContinueOnError) 59 | c.flags.Usage = func() { c.Ui.Output(c.Help()) } 60 | c.flags.BoolVar(&c.machineMode, "H", false, "Machine readable output") 61 | c.flags.BoolVar(&c.valueOnly, "n", false, "Show only the value") 62 | c.flags.BoolVar(&c.v4Only, "4", false, "Parse the input as IPv4 only") 63 | c.flags.BoolVar(&c.v6Only, "6", false, "Parse the input as IPv6 only") 64 | c.flags.BoolVar(&c.ifOnly, "I", false, "Parse the argument as an interface name") 65 | c.flags.BoolVar(&c.ipOnly, "i", false, "Parse the input as IP address (either IPv4 or IPv6)") 66 | c.flags.BoolVar(&c.unixOnly, "u", false, "Parse the input as a UNIX Socket only") 67 | c.flags.Var((*MultiArg)(&c.attrNames), "o", "Name of an attribute to pass through") 68 | } 69 | 70 | // Run executes this command. 71 | func (c *DumpCommand) Run(args []string) int { 72 | if len(args) == 0 { 73 | c.Ui.Error(c.Help()) 74 | return 1 75 | } 76 | 77 | c.InitOpts() 78 | addrs, err := c.parseOpts(args) 79 | if err != nil { 80 | if errwrap.Contains(err, "flag: help requested") { 81 | return 0 82 | } 83 | return 1 84 | } 85 | for _, addr := range addrs { 86 | var sa sockaddr.SockAddr 87 | var ifAddrs sockaddr.IfAddrs 88 | var err error 89 | switch { 90 | case c.v4Only: 91 | sa, err = sockaddr.NewIPv4Addr(addr) 92 | case c.v6Only: 93 | sa, err = sockaddr.NewIPv6Addr(addr) 94 | case c.unixOnly: 95 | sa, err = sockaddr.NewUnixSock(addr) 96 | case c.ipOnly: 97 | sa, err = sockaddr.NewIPAddr(addr) 98 | case c.ifOnly: 99 | ifAddrs, err = sockaddr.GetAllInterfaces() 100 | if err != nil { 101 | break 102 | } 103 | 104 | ifAddrs, _, err = sockaddr.IfByName(addr, ifAddrs) 105 | default: 106 | sa, err = sockaddr.NewSockAddr(addr) 107 | } 108 | if err != nil { 109 | c.Ui.Error(fmt.Sprintf("Unable to parse %+q: %v", addr, err)) 110 | return 1 111 | } 112 | if sa != nil { 113 | c.dumpSockAddr(sa) 114 | } else if ifAddrs != nil { 115 | c.dumpIfAddrs(ifAddrs) 116 | } else { 117 | panic("bad") 118 | } 119 | } 120 | return 0 121 | } 122 | 123 | // Synopsis returns a terse description used when listing sub-commands. 124 | func (c *DumpCommand) Synopsis() string { 125 | return `Parses input as an IP or interface name(s) and dumps various information` 126 | } 127 | 128 | // Usage is the one-line usage description 129 | func (c *DumpCommand) Usage() string { 130 | return `sockaddr dump [options] input [...]` 131 | } 132 | 133 | // VisitAllFlags forwards the visitor function to the FlagSet 134 | func (c *DumpCommand) VisitAllFlags(fn func(*flag.Flag)) { 135 | c.flags.VisitAll(fn) 136 | } 137 | 138 | func (c *DumpCommand) dumpIfAddrs(ifAddrs sockaddr.IfAddrs) { 139 | for _, ifAddr := range ifAddrs { 140 | c.dumpSockAddr(ifAddr.SockAddr) 141 | } 142 | } 143 | 144 | func (c *DumpCommand) dumpSockAddr(sa sockaddr.SockAddr) { 145 | reservedAttrs := []sockaddr.AttrName{"Attribute"} 146 | const maxNumAttrs = 32 147 | 148 | output := make([]string, 0, maxNumAttrs+len(reservedAttrs)) 149 | allowedAttrs := make(map[sockaddr.AttrName]struct{}, len(c.attrNames)+len(reservedAttrs)) 150 | for _, attr := range reservedAttrs { 151 | allowedAttrs[attr] = struct{}{} 152 | } 153 | for _, attr := range c.attrNames { 154 | allowedAttrs[sockaddr.AttrName(attr)] = struct{}{} 155 | } 156 | 157 | // allowedAttr returns true if the attribute is allowed to be appended 158 | // to the output. 159 | allowedAttr := func(k sockaddr.AttrName) bool { 160 | if len(allowedAttrs) == len(reservedAttrs) { 161 | return true 162 | } 163 | 164 | _, found := allowedAttrs[k] 165 | return found 166 | } 167 | 168 | // outFmt is a small helper function to reduce the tedium below. outFmt 169 | // returns a new slice and expects the value to already be a string. 170 | outFmt := func(o []string, k sockaddr.AttrName, v interface{}) []string { 171 | if !allowedAttr(k) { 172 | return o 173 | } 174 | switch { 175 | case c.valueOnly: 176 | return append(o, fmt.Sprintf("%s", v)) 177 | case !c.valueOnly && c.machineMode: 178 | return append(o, fmt.Sprintf("%s\t%s", k, v)) 179 | case !c.valueOnly && !c.machineMode: 180 | fallthrough 181 | default: 182 | return append(o, fmt.Sprintf("%s | %s", k, v)) 183 | } 184 | } 185 | 186 | if !c.machineMode { 187 | output = outFmt(output, "Attribute", "Value") 188 | } 189 | 190 | // Attributes for all SockAddr types 191 | for _, attr := range sockaddr.SockAddrAttrs() { 192 | output = outFmt(output, attr, sockaddr.SockAddrAttr(sa, attr)) 193 | } 194 | 195 | // Attributes for all IP types (both IPv4 and IPv6) 196 | if sa.Type()&sockaddr.TypeIP != 0 { 197 | ip := *sockaddr.ToIPAddr(sa) 198 | for _, attr := range sockaddr.IPAttrs() { 199 | output = outFmt(output, attr, sockaddr.IPAddrAttr(ip, attr)) 200 | } 201 | } 202 | 203 | if sa.Type() == sockaddr.TypeIPv4 { 204 | ipv4 := *sockaddr.ToIPv4Addr(sa) 205 | for _, attr := range sockaddr.IPv4Attrs() { 206 | output = outFmt(output, attr, sockaddr.IPv4AddrAttr(ipv4, attr)) 207 | } 208 | } 209 | 210 | if sa.Type() == sockaddr.TypeIPv6 { 211 | ipv6 := *sockaddr.ToIPv6Addr(sa) 212 | for _, attr := range sockaddr.IPv6Attrs() { 213 | output = outFmt(output, attr, sockaddr.IPv6AddrAttr(ipv6, attr)) 214 | } 215 | } 216 | 217 | if sa.Type() == sockaddr.TypeUnix { 218 | us := *sockaddr.ToUnixSock(sa) 219 | for _, attr := range sockaddr.UnixSockAttrs() { 220 | output = outFmt(output, attr, sockaddr.UnixSockAttr(us, attr)) 221 | } 222 | } 223 | 224 | // Developer-focused arguments 225 | { 226 | arg1, arg2 := sa.DialPacketArgs() 227 | output = outFmt(output, "DialPacket", fmt.Sprintf("%+q %+q", arg1, arg2)) 228 | } 229 | { 230 | arg1, arg2 := sa.DialStreamArgs() 231 | output = outFmt(output, "DialStream", fmt.Sprintf("%+q %+q", arg1, arg2)) 232 | } 233 | { 234 | arg1, arg2 := sa.ListenPacketArgs() 235 | output = outFmt(output, "ListenPacket", fmt.Sprintf("%+q %+q", arg1, arg2)) 236 | } 237 | { 238 | arg1, arg2 := sa.ListenStreamArgs() 239 | output = outFmt(output, "ListenStream", fmt.Sprintf("%+q %+q", arg1, arg2)) 240 | } 241 | 242 | result := columnize.SimpleFormat(output) 243 | c.Ui.Output(result) 244 | } 245 | 246 | // parseOpts is responsible for parsing the options set in InitOpts(). Returns 247 | // a list of non-parsed flags. 248 | func (c *DumpCommand) parseOpts(args []string) ([]string, error) { 249 | if err := c.flags.Parse(args); err != nil { 250 | return nil, err 251 | } 252 | 253 | conflictingOptsCount := 0 254 | if c.v4Only { 255 | conflictingOptsCount++ 256 | } 257 | if c.v6Only { 258 | conflictingOptsCount++ 259 | } 260 | if c.unixOnly { 261 | conflictingOptsCount++ 262 | } 263 | if c.ifOnly { 264 | conflictingOptsCount++ 265 | } 266 | if c.ipOnly { 267 | conflictingOptsCount++ 268 | } 269 | if conflictingOptsCount > 1 { 270 | return nil, fmt.Errorf("Conflicting options specified, only one parsing mode may be specified at a time") 271 | } 272 | 273 | return c.flags.Args(), nil 274 | } 275 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/eval.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strings" 10 | 11 | "github.com/hashicorp/errwrap" 12 | "github.com/hashicorp/go-sockaddr/template" 13 | "github.com/mitchellh/cli" 14 | ) 15 | 16 | type EvalCommand struct { 17 | Ui cli.Ui 18 | 19 | // debugOutput emits framed output vs raw output. 20 | debugOutput bool 21 | 22 | // flags is a list of options belonging to this command 23 | flags *flag.FlagSet 24 | 25 | // rawInput disables wrapping the string in the text/template {{ }} 26 | // handlebars. 27 | rawInput bool 28 | 29 | // suppressNewline changes whether or not there's a newline between each 30 | // arg passed to the eval subcommand. 31 | suppressNewline bool 32 | } 33 | 34 | // Description is the long-form command help. 35 | func (c *EvalCommand) Description() string { 36 | return `Parse the sockaddr template and evaluates the output. 37 | 38 | ` + "The `sockaddr` library has the potential to be very complex, which is why the " + 39 | "`sockaddr` command supports an `eval` subcommand in order to test configurations " + 40 | "from the command line. The `eval` subcommand automatically wraps its input with " + 41 | "the `{{` and `}}` template delimiters unless the `-r` command is specified, in " + 42 | "which case `eval` parses the raw input. If the `template` argument passed to " + 43 | "`eval` is a dash (`-`), then `sockaddr eval` will read from stdin and " + 44 | "automatically sets the `-r` flag." 45 | 46 | } 47 | 48 | // Help returns the full help output expected by `sockaddr -h cmd` 49 | func (c *EvalCommand) Help() string { 50 | return MakeHelp(c) 51 | } 52 | 53 | // InitOpts is responsible for setup of this command's configuration via the 54 | // command line. InitOpts() does not parse the arguments (see parseOpts()). 55 | func (c *EvalCommand) InitOpts() { 56 | c.flags = flag.NewFlagSet("eval", flag.ContinueOnError) 57 | c.flags.Usage = func() { c.Ui.Output(c.Help()) } 58 | c.flags.BoolVar(&c.debugOutput, "d", false, "Debug output") 59 | c.flags.BoolVar(&c.suppressNewline, "n", false, "Suppress newlines between args") 60 | c.flags.BoolVar(&c.rawInput, "r", false, "Suppress wrapping the input with {{ }} delimiters") 61 | } 62 | 63 | // Run executes this command. 64 | func (c *EvalCommand) Run(args []string) int { 65 | if len(args) == 0 { 66 | c.Ui.Error(c.Help()) 67 | return 1 68 | } 69 | 70 | c.InitOpts() 71 | tmpls, err := c.parseOpts(args) 72 | if err != nil { 73 | if errwrap.Contains(err, "flag: help requested") { 74 | return 0 75 | } 76 | return 1 77 | } 78 | inputs, outputs := make([]string, len(tmpls)), make([]string, len(tmpls)) 79 | var rawInput, readStdin bool 80 | for i, in := range tmpls { 81 | if readStdin { 82 | break 83 | } 84 | 85 | rawInput = c.rawInput 86 | if in == "-" { 87 | rawInput = true 88 | var f io.Reader = os.Stdin 89 | var buf bytes.Buffer 90 | if _, err := io.Copy(&buf, f); err != nil { 91 | c.Ui.Error(fmt.Sprintf("[ERROR]: Error reading from stdin: %v", err)) 92 | return 1 93 | } 94 | in = buf.String() 95 | if len(in) == 0 { 96 | return 0 97 | } 98 | readStdin = true 99 | } 100 | inputs[i] = in 101 | 102 | if !rawInput { 103 | in = `{{` + in + `}}` 104 | inputs[i] = in 105 | } 106 | 107 | out, err := template.Parse(in) 108 | if err != nil { 109 | c.Ui.Error(fmt.Sprintf("ERROR[%d] in: %q\n[%d] msg: %v\n", i, in, i, err)) 110 | return 1 111 | } 112 | outputs[i] = out 113 | } 114 | 115 | if c.debugOutput { 116 | for i, out := range outputs { 117 | c.Ui.Output(fmt.Sprintf("[%d] in: %q\n[%d] out: %q\n", i, inputs[i], i, out)) 118 | if i != len(outputs)-1 { 119 | if c.debugOutput { 120 | c.Ui.Output(fmt.Sprintf("---\n")) 121 | } 122 | } 123 | } 124 | } else { 125 | sep := "\n" 126 | if c.suppressNewline { 127 | sep = "" 128 | } 129 | c.Ui.Output(strings.Join(outputs, sep)) 130 | } 131 | 132 | return 0 133 | } 134 | 135 | // Synopsis returns a terse description used when listing sub-commands. 136 | func (c *EvalCommand) Synopsis() string { 137 | return `Evaluates a sockaddr template` 138 | } 139 | 140 | // Usage is the one-line usage description 141 | func (c *EvalCommand) Usage() string { 142 | return `sockaddr eval [options] [template ...]` 143 | } 144 | 145 | // VisitAllFlags forwards the visitor function to the FlagSet 146 | func (c *EvalCommand) VisitAllFlags(fn func(*flag.Flag)) { 147 | c.flags.VisitAll(fn) 148 | } 149 | 150 | // parseOpts is responsible for parsing the options set in InitOpts(). Returns 151 | // a list of non-parsed flags. 152 | func (c *EvalCommand) parseOpts(args []string) ([]string, error) { 153 | if err := c.flags.Parse(args); err != nil { 154 | return nil, err 155 | } 156 | 157 | return c.flags.Args(), nil 158 | } 159 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/multi_arg.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import "regexp" 4 | 5 | type MultiArg []string 6 | 7 | func (v *MultiArg) String() string { 8 | return "" 9 | } 10 | 11 | func (v *MultiArg) Set(raw string) error { 12 | parts := regexp.MustCompile(`[\s]*,[\s]*`).Split(raw, -1) 13 | for _, part := range parts { 14 | *v = append(*v, part) 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/rfc.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/hashicorp/errwrap" 9 | sockaddr "github.com/hashicorp/go-sockaddr" 10 | "github.com/mitchellh/cli" 11 | ) 12 | 13 | type RFCCommand struct { 14 | Ui cli.Ui 15 | 16 | // flags is a list of options belonging to this command 17 | flags *flag.FlagSet 18 | 19 | // silentMode prevents any output and only returns exit code 1 when the 20 | // IP address is NOT a member of the known RFC. Unknown RFCs return a 21 | // status code of 2. 22 | silentMode bool 23 | } 24 | 25 | // Description is the long-form command help. 26 | func (c *RFCCommand) Description() string { 27 | return `Tests a given IP address to see if it is part of a known RFC. If the IP address belongs to a known RFC, return exit code 0 and print the status. If the IP does not belong to an RFC, return 1. If the RFC is not known, return 2.` 28 | } 29 | 30 | // Help returns the full help output expected by `sockaddr -h cmd` 31 | func (c *RFCCommand) Help() string { 32 | return MakeHelp(c) 33 | } 34 | 35 | // InitOpts is responsible for setup of this command's configuration via the 36 | // command line. InitOpts() does not parse the arguments (see parseOpts()). 37 | func (c *RFCCommand) InitOpts() { 38 | c.flags = flag.NewFlagSet("rfc", flag.ContinueOnError) 39 | c.flags.Usage = func() { c.Ui.Output(c.Help()) } 40 | c.flags.BoolVar(&c.silentMode, "s", false, "Silent, only return different exit codes") 41 | } 42 | 43 | // Run executes this command. 44 | func (c *RFCCommand) Run(args []string) int { 45 | if len(args) == 0 { 46 | c.Ui.Error(c.Help()) 47 | return 1 48 | } 49 | 50 | c.InitOpts() 51 | unprocessedArgs, err := c.parseOpts(args) 52 | if err != nil { 53 | if errwrap.Contains(err, "flag: help requested") { 54 | return 0 55 | } 56 | return 1 57 | } 58 | 59 | switch numArgs := len(unprocessedArgs); { 60 | case numArgs != 2 && numArgs != 0: 61 | c.Ui.Error(`ERROR: Need an RFC Number and an IP address to test.`) 62 | c.Ui.Error(c.Help()) 63 | fallthrough 64 | case numArgs == 0: 65 | return 1 66 | } 67 | 68 | // Parse the RFC Number 69 | rfcNum, err := strconv.ParseUint(unprocessedArgs[0], 10, 32) 70 | if err != nil { 71 | c.Ui.Error(fmt.Sprintf("ERROR: Invalid RFC Number %+q: %v", unprocessedArgs[0], err)) 72 | return 2 73 | } 74 | 75 | // Parse the IP address 76 | ipAddr, err := sockaddr.NewIPAddr(unprocessedArgs[1]) 77 | if err != nil { 78 | c.Ui.Error(fmt.Sprintf("ERROR: Invalid IP address %+q: %v", unprocessedArgs[1], err)) 79 | return 3 80 | } 81 | 82 | switch inRFC := sockaddr.IsRFC(uint(rfcNum), ipAddr); { 83 | case inRFC && !c.silentMode: 84 | c.Ui.Output(fmt.Sprintf("%s is part of RFC %d", ipAddr, rfcNum)) 85 | fallthrough 86 | case inRFC: 87 | return 0 88 | case !inRFC && !c.silentMode: 89 | c.Ui.Output(fmt.Sprintf("%s is not part of RFC %d", ipAddr, rfcNum)) 90 | fallthrough 91 | case !inRFC: 92 | return 1 93 | default: 94 | panic("bad") 95 | } 96 | } 97 | 98 | // Synopsis returns a terse description used when listing sub-commands. 99 | func (c *RFCCommand) Synopsis() string { 100 | return `Test to see if an IP is part of a known RFC` 101 | } 102 | 103 | // Usage is the one-line usage description 104 | func (c *RFCCommand) Usage() string { 105 | return `sockaddr rfc [RFC Number] [IP Address]` 106 | } 107 | 108 | // VisitAllFlags forwards the visitor function to the FlagSet 109 | func (c *RFCCommand) VisitAllFlags(fn func(*flag.Flag)) { 110 | c.flags.VisitAll(fn) 111 | } 112 | 113 | // parseOpts is responsible for parsing the options set in InitOpts(). Returns 114 | // a list of non-parsed flags. 115 | func (c *RFCCommand) parseOpts(args []string) ([]string, error) { 116 | if err := c.flags.Parse(args); err != nil { 117 | return nil, err 118 | } 119 | 120 | return c.flags.Args(), nil 121 | } 122 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/rfc_list.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "sort" 7 | 8 | "github.com/hashicorp/errwrap" 9 | sockaddr "github.com/hashicorp/go-sockaddr" 10 | "github.com/mitchellh/cli" 11 | ) 12 | 13 | type RFCListCommand struct { 14 | Ui cli.Ui 15 | 16 | // flags is a list of options belonging to this command 17 | flags *flag.FlagSet 18 | } 19 | 20 | // Description is the long-form command help. 21 | func (c *RFCListCommand) Description() string { 22 | return `Lists all known RFCs.` 23 | } 24 | 25 | // Help returns the full help output expected by `sockaddr -h cmd` 26 | func (c *RFCListCommand) Help() string { 27 | return MakeHelp(c) 28 | } 29 | 30 | // InitOpts is responsible for setup of this command's configuration via the 31 | // command line. InitOpts() does not parse the arguments (see parseOpts()). 32 | func (c *RFCListCommand) InitOpts() { 33 | c.flags = flag.NewFlagSet("list", flag.ContinueOnError) 34 | c.flags.Usage = func() { c.Ui.Output(c.Help()) } 35 | } 36 | 37 | type rfcNums []uint 38 | 39 | func (s rfcNums) Len() int { return len(s) } 40 | func (s rfcNums) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 41 | func (s rfcNums) Less(i, j int) bool { return s[i] < s[j] } 42 | 43 | // Run executes this command. 44 | func (c *RFCListCommand) Run(args []string) int { 45 | if len(args) != 0 { 46 | c.Ui.Error(c.Help()) 47 | return 1 48 | } 49 | 50 | c.InitOpts() 51 | _, err := c.parseOpts(args) 52 | if err != nil { 53 | if errwrap.Contains(err, "flag: help requested") { 54 | return 0 55 | } 56 | return 1 57 | } 58 | 59 | var rfcs rfcNums 60 | sockaddr.VisitAllRFCs(func(rfcNum uint, sas sockaddr.SockAddrs) { 61 | rfcs = append(rfcs, rfcNum) 62 | }) 63 | 64 | sort.Sort(rfcs) 65 | 66 | for _, rfcNum := range rfcs { 67 | c.Ui.Output(fmt.Sprintf("%d", rfcNum)) 68 | } 69 | 70 | return 0 71 | } 72 | 73 | // Synopsis returns a terse description used when listing sub-commands. 74 | func (c *RFCListCommand) Synopsis() string { 75 | return `Lists all known RFCs` 76 | } 77 | 78 | // Usage is the one-line usage description 79 | func (c *RFCListCommand) Usage() string { 80 | return `sockaddr rfc list` 81 | } 82 | 83 | // VisitAllFlags forwards the visitor function to the FlagSet 84 | func (c *RFCListCommand) VisitAllFlags(fn func(*flag.Flag)) { 85 | c.flags.VisitAll(fn) 86 | } 87 | 88 | func (c *RFCListCommand) parseOpts(args []string) ([]string, error) { 89 | if err := c.flags.Parse(args); err != nil { 90 | return nil, err 91 | } 92 | 93 | return c.flags.Args(), nil 94 | } 95 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/tech_support.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os/exec" 8 | "runtime" 9 | 10 | "github.com/hashicorp/errwrap" 11 | sockaddr "github.com/hashicorp/go-sockaddr" 12 | "github.com/mitchellh/cli" 13 | ) 14 | 15 | type TechSupportCommand struct { 16 | Ui cli.Ui 17 | 18 | // outputMode controls the type of output encoding. 19 | outputMode string 20 | 21 | // flags is a list of options belonging to this command 22 | flags *flag.FlagSet 23 | } 24 | 25 | // Description is the long-form command help. 26 | func (c *TechSupportCommand) Description() string { 27 | return `Print out network diagnostic information that can be used by support. 28 | 29 | ` + "The `sockaddr` library relies on OS-specific commands and output which can potentially be " + 30 | "brittle. The `tech-support` subcommand emits all of the platform-specific " + 31 | "network details required to debug why a given `sockaddr` API call is behaving " + 32 | "differently than expected. The `-output` flag controls the output format. " + 33 | "The default output mode is Markdown (`md`) however a raw mode (`raw`) is " + 34 | "available to obtain the original output." 35 | } 36 | 37 | // Help returns the full help output expected by `sockaddr -h cmd` 38 | func (c *TechSupportCommand) Help() string { 39 | return MakeHelp(c) 40 | } 41 | 42 | // InitOpts is responsible for setup of this command's configuration via the 43 | // command line. InitOpts() does not parse the arguments (see parseOpts()). 44 | func (c *TechSupportCommand) InitOpts() { 45 | c.flags = flag.NewFlagSet("tech-support", flag.ContinueOnError) 46 | c.flags.Usage = func() { c.Ui.Output(c.Help()) } 47 | c.flags.StringVar(&c.outputMode, "output", "md", `Encode the output using one of Markdown ("md") or Raw ("raw")`) 48 | } 49 | 50 | // Run executes this command. 51 | func (c *TechSupportCommand) Run(args []string) int { 52 | c.InitOpts() 53 | rest, err := c.parseOpts(args) 54 | if err != nil { 55 | if errwrap.Contains(err, "flag: help requested") { 56 | return 0 57 | } 58 | return 1 59 | } 60 | if len(rest) != 0 { 61 | c.Ui.Error(c.Help()) 62 | return 1 63 | } 64 | 65 | ri, err := sockaddr.NewRouteInfo() 66 | if err != nil { 67 | c.Ui.Error(fmt.Sprintf("error loading route information: %v", err)) 68 | return 1 69 | } 70 | 71 | const initNumCmds = 4 72 | type cmdResult struct { 73 | cmd []string 74 | out string 75 | } 76 | output := make(map[string]cmdResult, initNumCmds) 77 | ri.VisitCommands(func(name string, cmd []string) { 78 | out, err := exec.Command(cmd[0], cmd[1:]...).Output() 79 | if err != nil { 80 | out = []byte(fmt.Sprintf("ERROR: command %q failed: %v", name, err)) 81 | } 82 | 83 | output[name] = cmdResult{ 84 | cmd: cmd, 85 | out: string(out), 86 | } 87 | }) 88 | 89 | out := c.rowWriterOutputFactory() 90 | 91 | for cmdName, result := range output { 92 | switch c.outputMode { 93 | case "md": 94 | c.Ui.Output(fmt.Sprintf("## cmd: `%s`", cmdName)) 95 | c.Ui.Output("") 96 | c.Ui.Output(fmt.Sprintf("Command: `%#v`", result.cmd)) 97 | c.Ui.Output("```") 98 | c.Ui.Output(result.out) 99 | c.Ui.Output("```") 100 | c.Ui.Output("") 101 | case "raw": 102 | c.Ui.Output(fmt.Sprintf("cmd: %q: %#v", cmdName, result.cmd)) 103 | c.Ui.Output("") 104 | c.Ui.Output(result.out) 105 | c.Ui.Output("") 106 | default: 107 | c.Ui.Error(fmt.Sprintf("Unsupported output type: %q", c.outputMode)) 108 | return 1 109 | } 110 | 111 | out("s", "GOOS", runtime.GOOS) 112 | out("s", "GOARCH", runtime.GOARCH) 113 | out("s", "Compiler", runtime.Compiler) 114 | out("s", "Version", runtime.Version()) 115 | ifs, err := net.Interfaces() 116 | if err != nil { 117 | out("v", "net.Interfaces", err) 118 | } else { 119 | for i, intf := range ifs { 120 | out("s", fmt.Sprintf("net.Interfaces[%d].Name", i), intf.Name) 121 | out("s", fmt.Sprintf("net.Interfaces[%d].Flags", i), intf.Flags) 122 | out("+v", fmt.Sprintf("net.Interfaces[%d].Raw", i), intf) 123 | addrs, err := intf.Addrs() 124 | if err != nil { 125 | out("v", fmt.Sprintf("net.Interfaces[%d].Addrs", i), err) 126 | } else { 127 | for j, addr := range addrs { 128 | out("s", fmt.Sprintf("net.Interfaces[%d].Addrs[%d]", i, j), addr) 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | return 0 136 | } 137 | 138 | // Synopsis returns a terse description used when listing sub-commands. 139 | func (c *TechSupportCommand) Synopsis() string { 140 | return `Dumps diagnostic information about a platform's network` 141 | } 142 | 143 | // Usage is the one-line usage description 144 | func (c *TechSupportCommand) Usage() string { 145 | return `sockaddr tech-support [options]` 146 | } 147 | 148 | // VisitAllFlags forwards the visitor function to the FlagSet 149 | func (c *TechSupportCommand) VisitAllFlags(fn func(*flag.Flag)) { 150 | c.flags.VisitAll(fn) 151 | } 152 | 153 | // parseOpts is responsible for parsing the options set in InitOpts(). Returns 154 | // a list of non-parsed flags. 155 | func (c *TechSupportCommand) parseOpts(args []string) ([]string, error) { 156 | if err := c.flags.Parse(args); err != nil { 157 | return nil, err 158 | } 159 | 160 | switch c.outputMode { 161 | case "md", "markdown": 162 | c.outputMode = "md" 163 | case "raw": 164 | default: 165 | return nil, fmt.Errorf(`Invalid output mode %q, supported output types are "md" (default) and "raw"`, c.outputMode) 166 | } 167 | return c.flags.Args(), nil 168 | } 169 | 170 | func (c *TechSupportCommand) rowWriterOutputFactory() func(valueVerb, key string, val interface{}) { 171 | type _Fmt string 172 | type _Verb string 173 | var lineNoFmt string 174 | var keyVerb _Verb 175 | var fmtMap map[_Verb]_Fmt 176 | switch c.outputMode { 177 | case "md": 178 | lineNoFmt = "%02d." 179 | keyVerb = "s" 180 | fmtMap = map[_Verb]_Fmt{ 181 | "s": "`%s`", 182 | "-s": "%s", 183 | "v": "`%v`", 184 | "+v": "`%#v`", 185 | } 186 | case "raw": 187 | lineNoFmt = "%02d:" 188 | keyVerb = "-s" 189 | fmtMap = map[_Verb]_Fmt{ 190 | "s": "%q", 191 | "-s": "%s", 192 | "v": "%v", 193 | "+v": "%#v", 194 | } 195 | default: 196 | panic(fmt.Sprintf("Unsupported output type: %q", c.outputMode)) 197 | } 198 | 199 | var count int 200 | return func(valueVerb, key string, val interface{}) { 201 | count++ 202 | 203 | keyFmt, ok := fmtMap[keyVerb] 204 | if !ok { 205 | panic(fmt.Sprintf("Invalid key verb: %q", keyVerb)) 206 | } 207 | 208 | valFmt, ok := fmtMap[_Verb(valueVerb)] 209 | if !ok { 210 | panic(fmt.Sprintf("Invalid value verb: %q", valueVerb)) 211 | } 212 | 213 | outputModeFmt := fmt.Sprintf("%s %s:\t%s", lineNoFmt, keyFmt, valFmt) 214 | c.Ui.Output(fmt.Sprintf(outputModeFmt, count, key, val)) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /cmd/sockaddr/command/version.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mitchellh/cli" 7 | ) 8 | 9 | // VersionCommand is a Command implementation prints the version. 10 | type VersionCommand struct { 11 | HumanVersion string 12 | Ui cli.Ui 13 | } 14 | 15 | func (c *VersionCommand) Help() string { 16 | return "" 17 | } 18 | 19 | func (c *VersionCommand) Run(_ []string) int { 20 | c.Ui.Output(fmt.Sprintf("sockaddr %s", c.HumanVersion)) 21 | 22 | return 0 23 | } 24 | 25 | func (c *VersionCommand) Synopsis() string { 26 | return "Prints the sockaddr version" 27 | } 28 | -------------------------------------------------------------------------------- /cmd/sockaddr/commands.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/hashicorp/go-sockaddr/cmd/sockaddr/command" 7 | "github.com/mitchellh/cli" 8 | ) 9 | 10 | // Commands is the mapping of all the available CLI commands. 11 | var Commands map[string]cli.CommandFactory 12 | 13 | func init() { 14 | ui := &cli.BasicUi{Writer: os.Stdout} 15 | 16 | Commands = map[string]cli.CommandFactory{ 17 | "dump": func() (cli.Command, error) { 18 | return &command.DumpCommand{ 19 | Ui: ui, 20 | }, nil 21 | }, 22 | "eval": func() (cli.Command, error) { 23 | return &command.EvalCommand{ 24 | Ui: ui, 25 | }, nil 26 | }, 27 | "rfc": func() (cli.Command, error) { 28 | return &command.RFCCommand{ 29 | Ui: ui, 30 | }, nil 31 | }, 32 | "rfc list": func() (cli.Command, error) { 33 | return &command.RFCListCommand{ 34 | Ui: ui, 35 | }, nil 36 | }, 37 | "tech-support": func() (cli.Command, error) { 38 | return &command.TechSupportCommand{ 39 | Ui: ui, 40 | }, nil 41 | }, 42 | "version": func() (cli.Command, error) { 43 | return &command.VersionCommand{ 44 | HumanVersion: GetHumanVersion(), 45 | Ui: ui, 46 | }, nil 47 | }, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/sockaddr/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/mitchellh/cli" 10 | ) 11 | 12 | func main() { 13 | os.Exit(realMain()) 14 | } 15 | 16 | func realMain() int { 17 | log.SetOutput(ioutil.Discard) 18 | 19 | // Get the command line args. We shortcut "--version" and "-v" to just 20 | // show the version. 21 | args := os.Args[1:] 22 | for _, arg := range args { 23 | if arg == "--" { 24 | break 25 | } 26 | if arg == "-v" || arg == "--version" { 27 | newArgs := make([]string, len(args)+1) 28 | newArgs[0] = "version" 29 | copy(newArgs[1:], args) 30 | args = newArgs 31 | break 32 | } 33 | } 34 | 35 | cli := &cli.CLI{ 36 | Args: args, 37 | Commands: Commands, 38 | HelpFunc: cli.BasicHelpFunc("sockaddr"), 39 | } 40 | exitCode, err := cli.Run() 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) 43 | return 1 44 | } 45 | 46 | return exitCode 47 | } 48 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/.gitignore: -------------------------------------------------------------------------------- 1 | /*.diff 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/GNUmakefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := test 2 | 3 | clean:: 4 | rm -f *.diff *.out 5 | 6 | test:: 7 | @rm -f *.diff 8 | @./run_all.sh 9 | @printf "All tests ran successfully.\n" 10 | 11 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr.out: -------------------------------------------------------------------------------- 1 | Usage: sockaddr [--version] [--help] [] 2 | 3 | Available commands are: 4 | dump Parses input as an IP or interface name(s) and dumps various information 5 | eval Evaluates a sockaddr template 6 | rfc Test to see if an IP is part of a known RFC 7 | tech-support Dumps diagnostic information about a platform's network 8 | version Prints the sockaddr version 9 | 10 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_-v.out: -------------------------------------------------------------------------------- 1 | sockaddr 0.1.0-dev 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-00-help.out: -------------------------------------------------------------------------------- 1 | Usage: sockaddr dump [options] input [...] 2 | 3 | Parse address(es) or interface and dumps various output. 4 | 5 | Options: 6 | 7 | -4 Parse the input as IPv4 only 8 | -6 Parse the input as IPv6 only 9 | -H Machine readable output 10 | -I Parse the argument as an interface name 11 | -i Parse the input as IP address (either IPv4 or IPv6) 12 | -n Show only the value 13 | -o Name of an attribute to pass through 14 | -u Parse the input as a UNIX Socket only 15 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-01.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv4 3 | string 127.0.0.1 4 | host 127.0.0.1 5 | address 127.0.0.1 6 | port 0 7 | netmask 255.255.255.255 8 | network 127.0.0.1 9 | mask_bits 32 10 | binary 01111111000000000000000000000001 11 | hex 7f000001 12 | first_usable 127.0.0.1 13 | last_usable 127.0.0.1 14 | octets 127 0 0 1 15 | size 1 16 | broadcast 127.0.0.1 17 | uint32 2130706433 18 | DialPacket "udp4" "" 19 | DialStream "tcp4" "" 20 | ListenPacket "udp4" "127.0.0.1:0" 21 | ListenStream "tcp4" "127.0.0.1:0" 22 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-02.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv4 3 | string 127.0.0.2/8 4 | host 127.0.0.2 5 | address 127.0.0.2 6 | port 0 7 | netmask 255.0.0.0 8 | network 127.0.0.0 9 | mask_bits 8 10 | binary 01111111000000000000000000000010 11 | hex 7f000002 12 | first_usable 127.0.0.1 13 | last_usable 127.255.255.254 14 | octets 127 0 0 2 15 | size 16777216 16 | broadcast 127.255.255.255 17 | uint32 2130706434 18 | DialPacket "udp4" "" 19 | DialStream "tcp4" "" 20 | ListenPacket "udp4" "" 21 | ListenStream "tcp4" "" 22 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-03.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv6 3 | string 2001:db8::3 4 | host 2001:db8::3 5 | address 2001:db8::3 6 | port 0 7 | netmask ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 8 | network 2001:db8::3 9 | mask_bits 128 10 | binary 00100000000000010000110110111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011 11 | hex 20010db8000000000000000000000003 12 | first_usable 2001:db8::3 13 | last_usable 2001:db8::3 14 | octets 32 1 13 184 0 0 0 0 0 0 0 0 0 0 0 3 15 | size 1 16 | uint128 42540766411282592856903984951653826563 17 | DialPacket "udp6" "" 18 | DialStream "tcp6" "" 19 | ListenPacket "udp6" "[2001:db8::3]:0" 20 | ListenStream "tcp6" "[2001:db8::3]:0" 21 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-04.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv6 3 | string 2001:db8::4/64 4 | host 2001:db8::4 5 | address 2001:db8::4 6 | port 0 7 | netmask ffff:ffff:ffff:ffff:: 8 | network 2001:db8:: 9 | mask_bits 64 10 | binary 00100000000000010000110110111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 11 | hex 20010db8000000000000000000000004 12 | first_usable 2001:db8:: 13 | last_usable 2001:db8::ffff:ffff:ffff:ffff 14 | octets 32 1 13 184 0 0 0 0 0 0 0 0 0 0 0 4 15 | size 18446744073709551616 16 | uint128 42540766411282592856903984951653826564 17 | DialPacket "udp6" "" 18 | DialStream "tcp6" "" 19 | ListenPacket "udp6" "" 20 | ListenStream "tcp6" "" 21 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-05.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type UNIX 3 | string "/tmp/example" 4 | path /tmp/example 5 | DialPacket "unixgram" "/tmp/example" 6 | DialStream "unix" "/tmp/example" 7 | ListenPacket "unixgram" "/tmp/example" 8 | ListenStream "unix" "/tmp/example" 9 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-06.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv6 3 | string [2001:db8::6]:22 4 | host [2001:db8::6]:22 5 | address 2001:db8::6 6 | port 22 7 | netmask ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 8 | network 2001:db8::6 9 | mask_bits 128 10 | binary 00100000000000010000110110111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000110 11 | hex 20010db8000000000000000000000006 12 | first_usable 2001:db8::6 13 | last_usable 2001:db8::6 14 | octets 32 1 13 184 0 0 0 0 0 0 0 0 0 0 0 6 15 | size 1 16 | uint128 42540766411282592856903984951653826566 17 | DialPacket "udp6" "[2001:db8::6]:22" 18 | DialStream "tcp6" "[2001:db8::6]:22" 19 | ListenPacket "udp6" "[2001:db8::6]:22" 20 | ListenStream "tcp6" "[2001:db8::6]:22" 21 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-07.out: -------------------------------------------------------------------------------- 1 | type IPv6 2 | string [2001:db8::7]:22 3 | host [2001:db8::7]:22 4 | address 2001:db8::7 5 | port 22 6 | netmask ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 7 | network 2001:db8::7 8 | mask_bits 128 9 | binary 00100000000000010000110110111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111 10 | hex 20010db8000000000000000000000007 11 | first_usable 2001:db8::7 12 | last_usable 2001:db8::7 13 | octets 32 1 13 184 0 0 0 0 0 0 0 0 0 0 0 7 14 | size 1 15 | uint128 42540766411282592856903984951653826567 16 | DialPacket "udp6" "[2001:db8::7]:22" 17 | DialStream "tcp6" "[2001:db8::7]:22" 18 | ListenPacket "udp6" "[2001:db8::7]:22" 19 | ListenStream "tcp6" "[2001:db8::7]:22" 20 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-08.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv6 3 | string [2001:db8::8]:22 4 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-09.out: -------------------------------------------------------------------------------- 1 | Value 2 | IPv6 3 | [2001:db8::8]:22 4 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-10.out: -------------------------------------------------------------------------------- 1 | IPv6 2 | [2001:db8::8]:22 3 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-11.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv4 3 | string 192.168.0.1 4 | host 192.168.0.1 5 | address 192.168.0.1 6 | port 0 7 | netmask 255.255.255.255 8 | network 192.168.0.1 9 | mask_bits 32 10 | binary 11000000101010000000000000000001 11 | hex c0a80001 12 | first_usable 192.168.0.1 13 | last_usable 192.168.0.1 14 | octets 192 168 0 1 15 | size 1 16 | broadcast 192.168.0.1 17 | uint32 3232235521 18 | DialPacket "udp4" "" 19 | DialStream "tcp4" "" 20 | ListenPacket "udp4" "192.168.0.1:0" 21 | ListenStream "tcp4" "192.168.0.1:0" 22 | Attribute Value 23 | type IPv4 24 | string 192.168.0.1 25 | host 192.168.0.1 26 | address 192.168.0.1 27 | port 0 28 | netmask 255.255.255.255 29 | network 192.168.0.1 30 | mask_bits 32 31 | binary 11000000101010000000000000000001 32 | hex c0a80001 33 | first_usable 192.168.0.1 34 | last_usable 192.168.0.1 35 | octets 192 168 0 1 36 | size 1 37 | broadcast 192.168.0.1 38 | uint32 3232235521 39 | DialPacket "udp4" "" 40 | DialStream "tcp4" "" 41 | ListenPacket "udp4" "192.168.0.1:0" 42 | ListenStream "tcp4" "192.168.0.1:0" 43 | Attribute Value 44 | type IPv4 45 | string 192.168.0.1 46 | host 192.168.0.1 47 | address 192.168.0.1 48 | port 0 49 | netmask 255.255.255.255 50 | network 192.168.0.1 51 | mask_bits 32 52 | binary 11000000101010000000000000000001 53 | hex c0a80001 54 | first_usable 192.168.0.1 55 | last_usable 192.168.0.1 56 | octets 192 168 0 1 57 | size 1 58 | broadcast 192.168.0.1 59 | uint32 3232235521 60 | DialPacket "udp4" "" 61 | DialStream "tcp4" "" 62 | ListenPacket "udp4" "192.168.0.1:0" 63 | ListenStream "tcp4" "192.168.0.1:0" 64 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-12.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv4 3 | string 192.168.0.1/16 4 | host 192.168.0.1 5 | address 192.168.0.1 6 | port 0 7 | netmask 255.255.0.0 8 | network 192.168.0.0 9 | mask_bits 16 10 | binary 11000000101010000000000000000001 11 | hex c0a80001 12 | first_usable 192.168.0.1 13 | last_usable 192.168.255.254 14 | octets 192 168 0 1 15 | size 65536 16 | broadcast 192.168.255.255 17 | uint32 3232235521 18 | DialPacket "udp4" "" 19 | DialStream "tcp4" "" 20 | ListenPacket "udp4" "" 21 | ListenStream "tcp4" "" 22 | Attribute Value 23 | type IPv4 24 | string 192.168.0.1/16 25 | host 192.168.0.1 26 | address 192.168.0.1 27 | port 0 28 | netmask 255.255.0.0 29 | network 192.168.0.0 30 | mask_bits 16 31 | binary 11000000101010000000000000000001 32 | hex c0a80001 33 | first_usable 192.168.0.1 34 | last_usable 192.168.255.254 35 | octets 192 168 0 1 36 | size 65536 37 | broadcast 192.168.255.255 38 | uint32 3232235521 39 | DialPacket "udp4" "" 40 | DialStream "tcp4" "" 41 | ListenPacket "udp4" "" 42 | ListenStream "tcp4" "" 43 | Attribute Value 44 | type IPv4 45 | string 192.168.0.1/16 46 | host 192.168.0.1 47 | address 192.168.0.1 48 | port 0 49 | netmask 255.255.0.0 50 | network 192.168.0.0 51 | mask_bits 16 52 | binary 11000000101010000000000000000001 53 | hex c0a80001 54 | first_usable 192.168.0.1 55 | last_usable 192.168.255.254 56 | octets 192 168 0 1 57 | size 65536 58 | broadcast 192.168.255.255 59 | uint32 3232235521 60 | DialPacket "udp4" "" 61 | DialStream "tcp4" "" 62 | ListenPacket "udp4" "" 63 | ListenStream "tcp4" "" 64 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-13.out: -------------------------------------------------------------------------------- 1 | Attribute Value 2 | type IPv4 3 | string 0.0.0.0/1 4 | host 0.0.0.0 5 | address 0.0.0.0 6 | port 0 7 | netmask 128.0.0.0 8 | network 0.0.0.0 9 | mask_bits 1 10 | binary 00000000000000000000000000000000 11 | hex 00000000 12 | first_usable 0.0.0.1 13 | last_usable 127.255.255.254 14 | octets 0 0 0 0 15 | size 2147483648 16 | broadcast 127.255.255.255 17 | uint32 0 18 | DialPacket "udp4" "" 19 | DialStream "tcp4" "" 20 | ListenPacket "udp4" "" 21 | ListenStream "tcp4" "" 22 | Unable to parse "0:0:0:0:0:0::/97": Unable to convert 0:0:0:0:0:0::/97 to an IPv4 address 23 | Attribute Value 24 | type IPv6 25 | string ::/97 26 | host :: 27 | address :: 28 | port 0 29 | netmask ffff:ffff:ffff:ffff:ffff:ffff:8000:0 30 | network :: 31 | mask_bits 97 32 | binary 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 33 | hex 00000000000000000000000000000000 34 | first_usable :: 35 | last_usable ::7fff:ffff 36 | octets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 37 | size 2147483648 38 | uint128 0 39 | DialPacket "udp6" "" 40 | DialStream "tcp6" "" 41 | ListenPacket "udp6" "" 42 | ListenStream "tcp6" "" 43 | Attribute Value 44 | type IPv6 45 | string ::/97 46 | host :: 47 | address :: 48 | port 0 49 | netmask ffff:ffff:ffff:ffff:ffff:ffff:8000:0 50 | network :: 51 | mask_bits 97 52 | binary 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 53 | hex 00000000000000000000000000000000 54 | first_usable :: 55 | last_usable ::7fff:ffff 56 | octets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 57 | size 2147483648 58 | uint128 0 59 | DialPacket "udp6" "" 60 | DialStream "tcp6" "" 61 | ListenPacket "udp6" "" 62 | ListenStream "tcp6" "" 63 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_dump-14.out: -------------------------------------------------------------------------------- 1 | Unable to parse "::c0a8:1": Unable to string convert "::c0a8:1" to an IPv4 address 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_eval-00-help.out: -------------------------------------------------------------------------------- 1 | Usage: sockaddr eval [options] [template ...] 2 | 3 | Parse the sockaddr template and evaluates the output. 4 | 5 | The `sockaddr` library has the potential to be very complex, 6 | which is why the `sockaddr` command supports an `eval` 7 | subcommand in order to test configurations from the command 8 | line. The `eval` subcommand automatically wraps its input 9 | with the `{{` and `}}` template delimiters unless the `-r` 10 | command is specified, in which case `eval` parses the raw 11 | input. If the `template` argument passed to `eval` is a 12 | dash (`-`), then `sockaddr eval` will read from stdin and 13 | automatically sets the `-r` flag. 14 | 15 | Options: 16 | 17 | -d Debug output 18 | -n Suppress newlines between args 19 | -r Suppress wrapping the input with {{ }} delimiters 20 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_eval-01.out: -------------------------------------------------------------------------------- 1 | [127.0.0.1/8 {1 16384 lo0 up|loopback|multicast} ::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}] 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_eval-02.out: -------------------------------------------------------------------------------- 1 | [127.0.0.1/8 {1 16384 lo0 up|loopback|multicast} ::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}] 2 | [127.0.0.1/8 {1 16384 lo0 up|loopback|multicast} ::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}] 3 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_eval-03.out: -------------------------------------------------------------------------------- 1 | ::1 fe80::1 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_eval-04.out: -------------------------------------------------------------------------------- 1 | [127.0.0.1/8 {1 16384 lo0 up|loopback|multicast} ::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}] 2 | 3 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_eval-05.out: -------------------------------------------------------------------------------- 1 | up|broadcast|multicast 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_rfc-00.out: -------------------------------------------------------------------------------- 1 | Usage: sockaddr rfc [RFC Number] [IP Address] 2 | 3 | Tests a given IP address to see if it is part of a known 4 | RFC. If the IP address belongs to a known RFC, return exit 5 | code 0 and print the status. If the IP does not belong to 6 | an RFC, return 1. If the RFC is not known, return 2. 7 | 8 | Options: 9 | 10 | -s Silent, only return different exit codes 11 | 12 | Subcommands: 13 | list Lists all known RFCs 14 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_rfc-01.out: -------------------------------------------------------------------------------- 1 | Usage: sockaddr rfc [RFC Number] [IP Address] 2 | 3 | Tests a given IP address to see if it is part of a known 4 | RFC. If the IP address belongs to a known RFC, return exit 5 | code 0 and print the status. If the IP does not belong to 6 | an RFC, return 1. If the RFC is not known, return 2. 7 | 8 | Options: 9 | 10 | -s Silent, only return different exit codes 11 | 12 | Subcommands: 13 | list Lists all known RFCs 14 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_rfc_list-00.out: -------------------------------------------------------------------------------- 1 | Usage: sockaddr rfc list 2 | 3 | Lists all known RFCs. 4 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_rfc_list-01.out: -------------------------------------------------------------------------------- 1 | 919 2 | 1112 3 | 1122 4 | 1918 5 | 2544 6 | 2765 7 | 2928 8 | 3056 9 | 3068 10 | 3171 11 | 3330 12 | 3849 13 | 3927 14 | 4038 15 | 4193 16 | 4291 17 | 4380 18 | 4773 19 | 4843 20 | 5180 21 | 5735 22 | 5737 23 | 6052 24 | 6333 25 | 6598 26 | 6666 27 | 6890 28 | 7335 29 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_rfc_list-02.out: -------------------------------------------------------------------------------- 1 | Usage: sockaddr rfc list 2 | 3 | Lists all known RFCs. 4 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_version-00.out: -------------------------------------------------------------------------------- 1 | sockaddr 0.1.0-dev 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/expected/sockaddr_version-01.out: -------------------------------------------------------------------------------- 1 | sockaddr 0.1.0-dev 2 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/run_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | FIND=`/usr/bin/which 2> /dev/null gfind find | /usr/bin/grep -v ^no | /usr/bin/head -n 1` 4 | XARGS=`/usr/bin/which 2> /dev/null gxargs xargs | /usr/bin/grep -v ^no | /usr/bin/head -n 1` 5 | set -e 6 | set -u 7 | 8 | num_cpus=$(getconf NPROCESSORS_ONLN) 9 | set +e 10 | ${FIND} . -maxdepth 1 -name 'test_*.sh' -print0 | ${XARGS} -0 -n1 -P${num_cpus} ./run_one.sh 11 | set -e 12 | 13 | # rune_one.sh generates the .diff files 14 | diffs=$(find . -name '*.diff') 15 | if [ -z "${diffs}" ]; then 16 | exit 0 17 | fi 18 | 19 | printf "The following tests failed (check the respective .diff file for details):\n\n" 20 | for d in ${diffs}; do 21 | printf "\t%s\n" "$(basename ${d} .diff)" 22 | done 23 | exit 1 24 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/run_one.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e -u -- 2 | 3 | set -e 4 | set -u 5 | 6 | verbose="" 7 | if [ "$1" = "-v" ]; then 8 | verbose="true" 9 | shift 10 | fi 11 | 12 | if [ $# -ne 1 ]; then 13 | printf "Usage: %s [ test script ]\n\n" "$(basename $0)" 14 | printf "ERROR: Need a single test script to execute\n" 15 | exit 1 16 | fi 17 | 18 | # chdir(2) to the directory where the script resides 19 | cd "$(dirname "$0")" 20 | 21 | exact_name="$(basename ${1} .sh)" 22 | test_name="$(echo ${exact_name} | sed -e s@^test_@@)" 23 | test_script="${exact_name}.sh" 24 | test_out="${test_name}.out" 25 | expected_out="expected/${test_name}.out" 26 | 27 | if [ ! -r "${test_script}" ]; then 28 | printf "ERROR: Test script %s does not exist\n" "${test_script}" 29 | exit 2 30 | fi 31 | 32 | if [ -n "${verbose}" ]; then 33 | cat "${test_script}" | tail -n 1 34 | fi 35 | 36 | set +e 37 | "./${test_script}" > "${test_out}" 2>&1 38 | 39 | if [ ! -r "${expected_out}" ]; then 40 | printf "ERROR: Expected test output (%s) does not exist\n" "${expected_out}" 41 | exit 2 42 | fi 43 | 44 | cmp -s "${expected_out}" "${test_out}" 45 | result=$? 46 | set -e 47 | 48 | if [ "${result}" -eq 0 ]; then 49 | if [ -n "${verbose}" ]; then 50 | cat "${test_out}" 51 | fi 52 | rm -f "${test_out}" 53 | exit 0 54 | fi 55 | 56 | diff_out="${test_name}.diff" 57 | set +e 58 | diff -u "${test_out}" "${expected_out}" > "${diff_out}" 59 | set -e 60 | 61 | # If run as an interactive TTY, pass along the diff to the caller 62 | if [ -t 0 -o -n "${verbose}" ]; then 63 | cat "${diff_out}" 64 | fi 65 | 66 | exit 1 67 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-00-help.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump 127.0.0.1 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-02.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump 127.0.0.2/8 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-03.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump '[2001:db8::3]' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump '2001:db8::4/64' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-05.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump /tmp/example 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-06.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump '[2001:db8::6]:22' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-07.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump -H '[2001:db8::7]:22' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-08.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump -o string,type '[2001:db8::8]:22' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-09.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump -n -o string,type '[2001:db8::8]:22' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-10.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr dump -H -n -o string,type '[2001:db8::8]:22' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-11.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | # Verified via: cat sockaddr_dump-11.out | sort | uniq -c 6 | ../sockaddr dump '192.168.0.1' 7 | ../sockaddr dump '::ffff:192.168.0.1' 8 | ../sockaddr dump '0:0:0:0:0:ffff:192.168.0.1' 9 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-12.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | # Verified via: cat sockaddr_dump-12.out | sort | uniq -c 6 | ../sockaddr dump '192.168.0.1/16' 7 | ../sockaddr dump '::ffff:192.168.0.1/112' 8 | ../sockaddr dump '0:0:0:0:0:ffff:192.168.0.1/112' 9 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-13.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | exec 2>&1 4 | # This should succeed because it is a mapped address 5 | ../sockaddr dump -4 '0:0:0:0:0:ffff::/97' 6 | 7 | # This should fail even though it is an IPv4 compatible address 8 | ../sockaddr dump -4 '0:0:0:0:0:0::/97' 9 | 10 | # These should succeed as an IPv6 addresses 11 | ../sockaddr dump -6 '0:0:0:0:0:0::/97' 12 | ../sockaddr dump -i '0:0:0:0:0:0::/97' 13 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_dump-14.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | ../sockaddr dump -4 '::c0a8:1' 6 | ../sockaddr dump -6 '::c0a8:1' 7 | ../sockaddr dump -4 '::c0a8:1/112' 8 | ../sockaddr dump -6 '::c0a8:1/112' 9 | ../sockaddr dump -4 '0:0:0:0:0:ffff:c0a8:1/112' 10 | ../sockaddr dump -6 '0:0:0:0:0:ffff:c0a8:1/112' 11 | ../sockaddr dump -4 '[0:0:0:0:0:ffff:c0a8:1/112]' 12 | ../sockaddr dump -6 '[0:0:0:0:0:ffff:c0a8:1/112]' 13 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_eval-00-help.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr eval 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_eval-01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr eval 'GetAllInterfaces | include "name" "lo0" | printf "%v"' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_eval-02.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr eval 'GetAllInterfaces | include "name" "lo0" | printf "%v"' 'GetAllInterfaces | include "name" "lo0" | printf "%v"' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_eval-03.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr eval '. | include "name" "lo0" | include "type" "IPv6" | sort "address" | join "address" " "' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_eval-04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | cat <<'EOF' | exec ../sockaddr eval - 6 | {{GetAllInterfaces | include "name" "lo0" | printf "%v"}} 7 | EOF 8 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_eval-05.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | ../sockaddr eval 'GetPrivateInterfaces | include "flags" "up|multicast" | attr "flags"' 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_rfc-00.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr -h rfc 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_rfc-01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr rfc -h list 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_rfc_list-00.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr -h rfc list 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_rfc_list-01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr rfc list 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_rfc_list-02.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr rfc list -h 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_version-00.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr version 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/regression/test_sockaddr_version-01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -- 2 | 3 | set -e 4 | exec 2>&1 5 | exec ../sockaddr version 6 | -------------------------------------------------------------------------------- /cmd/sockaddr/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // The git commit that was compiled. This will be filled in by the compiler. 9 | var ( 10 | GitCommit string 11 | GitDescribe string 12 | ) 13 | 14 | // The main version number that is being run at the moment. 15 | const Version = "0.2.0" 16 | 17 | // A pre-release marker for the version. If this is "" (empty string) 18 | // then it means that it is a final release. Otherwise, this is a pre-release 19 | // such as "dev" (in development), "beta", "rc1", etc. 20 | const VersionPrerelease = "dev" 21 | 22 | // GetHumanVersion composes the parts of the version in a way that's suitable 23 | // for displaying to humans. 24 | func GetHumanVersion() string { 25 | version := Version 26 | if GitDescribe != "" { 27 | version = GitDescribe 28 | } 29 | 30 | release := VersionPrerelease 31 | if GitDescribe == "" && release == "" { 32 | release = "dev" 33 | } 34 | if release != "" { 35 | version += fmt.Sprintf("-%s", release) 36 | if GitCommit != "" { 37 | version += fmt.Sprintf(" (%s)", GitCommit) 38 | } 39 | } 40 | 41 | // Strip off any single quotes added by the git information. 42 | return strings.Replace(version, "'", "", -1) 43 | } 44 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package sockaddr is a Go implementation of the UNIX socket family data types and 3 | related helper functions. 4 | */ 5 | package sockaddr 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/go-sockaddr 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/hashicorp/errwrap v1.1.0 7 | github.com/mitchellh/cli v1.1.5 8 | github.com/mitchellh/go-wordwrap v1.0.1 9 | github.com/ryanuber/columnize v2.1.2+incompatible 10 | ) 11 | 12 | require ( 13 | github.com/Masterminds/goutils v1.1.1 // indirect 14 | github.com/Masterminds/semver/v3 v3.1.1 // indirect 15 | github.com/Masterminds/sprig/v3 v3.2.1 // indirect 16 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 // indirect 17 | github.com/bgentry/speakeasy v0.2.0 // indirect 18 | github.com/fatih/color v1.17.0 // indirect 19 | github.com/google/uuid v1.1.2 // indirect 20 | github.com/hashicorp/go-multierror v1.0.0 // indirect 21 | github.com/huandu/xstrings v1.3.2 // indirect 22 | github.com/imdario/mergo v0.3.11 // indirect 23 | github.com/mattn/go-colorable v0.1.13 // indirect 24 | github.com/mattn/go-isatty v0.0.20 // indirect 25 | github.com/mitchellh/copystructure v1.0.0 // indirect 26 | github.com/mitchellh/reflectwalk v1.0.0 // indirect 27 | github.com/posener/complete v1.1.1 // indirect 28 | github.com/shopspring/decimal v1.2.0 // indirect 29 | github.com/spf13/cast v1.3.1 // indirect 30 | golang.org/x/crypto v0.32.0 // indirect 31 | golang.org/x/sys v0.29.0 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 2 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 3 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= 4 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 5 | github.com/Masterminds/sprig/v3 v3.2.1 h1:n6EPaDyLSvCEa3frruQvAiHuNp2dhBlMSmkEr+HuzGc= 6 | github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= 7 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= 8 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 9 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 10 | github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= 11 | github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 16 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 17 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 18 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 19 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 20 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 21 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 22 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 23 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 24 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 25 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 26 | github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 27 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= 28 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 29 | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= 30 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 31 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 32 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 33 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 34 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 35 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 36 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 37 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 38 | github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= 39 | github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= 40 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= 41 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 42 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 43 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 44 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= 45 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 46 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 47 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 48 | github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= 49 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 50 | github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= 51 | github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 52 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 53 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 54 | github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= 55 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 56 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 57 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 58 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 59 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 60 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 61 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 62 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 63 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 64 | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 65 | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 66 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 67 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 68 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 69 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 70 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 71 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 72 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 73 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 74 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 75 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 76 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 77 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 78 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 79 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 80 | -------------------------------------------------------------------------------- /ifaddr.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import "strings" 4 | 5 | // ifAddrAttrMap is a map of the IfAddr type-specific attributes. 6 | var ifAddrAttrMap map[AttrName]func(IfAddr) string 7 | var ifAddrAttrs []AttrName 8 | 9 | func init() { 10 | ifAddrAttrInit() 11 | } 12 | 13 | // GetPrivateIP returns a string with a single IP address that is part of RFC 14 | // 6890 and has a default route. If the system can't determine its IP address 15 | // or find an RFC 6890 IP address, an empty string will be returned instead. 16 | // This function is the `eval` equivalent of: 17 | // 18 | // ``` 19 | // $ sockaddr eval -r '{{GetPrivateInterfaces | attr "address"}}' 20 | /// ``` 21 | func GetPrivateIP() (string, error) { 22 | privateIfs, err := GetPrivateInterfaces() 23 | if err != nil { 24 | return "", err 25 | } 26 | if len(privateIfs) < 1 { 27 | return "", nil 28 | } 29 | 30 | ifAddr := privateIfs[0] 31 | ip := *ToIPAddr(ifAddr.SockAddr) 32 | return ip.NetIP().String(), nil 33 | } 34 | 35 | // GetPrivateIPs returns a string with all IP addresses that are part of RFC 36 | // 6890 (regardless of whether or not there is a default route, unlike 37 | // GetPublicIP). If the system can't find any RFC 6890 IP addresses, an empty 38 | // string will be returned instead. This function is the `eval` equivalent of: 39 | // 40 | // ``` 41 | // $ sockaddr eval -r '{{GetAllInterfaces | include "RFC" "6890" | join "address" " "}}' 42 | /// ``` 43 | func GetPrivateIPs() (string, error) { 44 | ifAddrs, err := GetAllInterfaces() 45 | if err != nil { 46 | return "", err 47 | } else if len(ifAddrs) < 1 { 48 | return "", nil 49 | } 50 | 51 | ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP) 52 | if len(ifAddrs) == 0 { 53 | return "", nil 54 | } 55 | 56 | OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs) 57 | 58 | ifAddrs, _, err = IfByRFC("6890", ifAddrs) 59 | if err != nil { 60 | return "", err 61 | } else if len(ifAddrs) == 0 { 62 | return "", nil 63 | } 64 | 65 | _, ifAddrs, err = IfByRFC(ForwardingBlacklistRFC, ifAddrs) 66 | if err != nil { 67 | return "", err 68 | } else if len(ifAddrs) == 0 { 69 | return "", nil 70 | } 71 | 72 | ips := make([]string, 0, len(ifAddrs)) 73 | for _, ifAddr := range ifAddrs { 74 | ip := *ToIPAddr(ifAddr.SockAddr) 75 | s := ip.NetIP().String() 76 | ips = append(ips, s) 77 | } 78 | 79 | return strings.Join(ips, " "), nil 80 | } 81 | 82 | // GetPublicIP returns a string with a single IP address that is NOT part of RFC 83 | // 6890 and has a default route. If the system can't determine its IP address 84 | // or find a non RFC 6890 IP address, an empty string will be returned instead. 85 | // This function is the `eval` equivalent of: 86 | // 87 | // ``` 88 | // $ sockaddr eval -r '{{GetPublicInterfaces | attr "address"}}' 89 | /// ``` 90 | func GetPublicIP() (string, error) { 91 | publicIfs, err := GetPublicInterfaces() 92 | if err != nil { 93 | return "", err 94 | } else if len(publicIfs) < 1 { 95 | return "", nil 96 | } 97 | 98 | ifAddr := publicIfs[0] 99 | ip := *ToIPAddr(ifAddr.SockAddr) 100 | return ip.NetIP().String(), nil 101 | } 102 | 103 | // GetPublicIPs returns a string with all IP addresses that are NOT part of RFC 104 | // 6890 (regardless of whether or not there is a default route, unlike 105 | // GetPublicIP). If the system can't find any non RFC 6890 IP addresses, an 106 | // empty string will be returned instead. This function is the `eval` 107 | // equivalent of: 108 | // 109 | // ``` 110 | // $ sockaddr eval -r '{{GetAllInterfaces | exclude "RFC" "6890" | join "address" " "}}' 111 | /// ``` 112 | func GetPublicIPs() (string, error) { 113 | ifAddrs, err := GetAllInterfaces() 114 | if err != nil { 115 | return "", err 116 | } else if len(ifAddrs) < 1 { 117 | return "", nil 118 | } 119 | 120 | ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP) 121 | if len(ifAddrs) == 0 { 122 | return "", nil 123 | } 124 | 125 | OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs) 126 | 127 | _, ifAddrs, err = IfByRFC("6890", ifAddrs) 128 | if err != nil { 129 | return "", err 130 | } else if len(ifAddrs) == 0 { 131 | return "", nil 132 | } 133 | 134 | ips := make([]string, 0, len(ifAddrs)) 135 | for _, ifAddr := range ifAddrs { 136 | ip := *ToIPAddr(ifAddr.SockAddr) 137 | s := ip.NetIP().String() 138 | ips = append(ips, s) 139 | } 140 | 141 | return strings.Join(ips, " "), nil 142 | } 143 | 144 | // GetInterfaceIP returns a string with a single IP address sorted by the size 145 | // of the network (i.e. IP addresses with a smaller netmask, larger network 146 | // size, are sorted first). This function is the `eval` equivalent of: 147 | // 148 | // ``` 149 | // $ sockaddr eval -r '{{GetAllInterfaces | include "name" <> | sort "type,size" | include "flag" "forwardable" | attr "address" }}' 150 | /// ``` 151 | func GetInterfaceIP(namedIfRE string) (string, error) { 152 | ifAddrs, err := GetAllInterfaces() 153 | if err != nil { 154 | return "", err 155 | } 156 | 157 | ifAddrs, _, err = IfByName(namedIfRE, ifAddrs) 158 | if err != nil { 159 | return "", err 160 | } 161 | 162 | ifAddrs, _, err = IfByFlag("forwardable", ifAddrs) 163 | if err != nil { 164 | return "", err 165 | } 166 | 167 | ifAddrs, err = SortIfBy("+type,+size", ifAddrs) 168 | if err != nil { 169 | return "", err 170 | } 171 | 172 | if len(ifAddrs) == 0 { 173 | return "", err 174 | } 175 | 176 | ip := ToIPAddr(ifAddrs[0].SockAddr) 177 | if ip == nil { 178 | return "", err 179 | } 180 | 181 | return IPAddrAttr(*ip, "address"), nil 182 | } 183 | 184 | // GetInterfaceIPs returns a string with all IPs, sorted by the size of the 185 | // network (i.e. IP addresses with a smaller netmask, larger network size, are 186 | // sorted first), on a named interface. This function is the `eval` equivalent 187 | // of: 188 | // 189 | // ``` 190 | // $ sockaddr eval -r '{{GetAllInterfaces | include "name" <> | sort "type,size" | join "address" " "}}' 191 | /// ``` 192 | func GetInterfaceIPs(namedIfRE string) (string, error) { 193 | ifAddrs, err := GetAllInterfaces() 194 | if err != nil { 195 | return "", err 196 | } 197 | 198 | ifAddrs, _, err = IfByName(namedIfRE, ifAddrs) 199 | if err != nil { 200 | return "", err 201 | } 202 | 203 | ifAddrs, err = SortIfBy("+type,+size", ifAddrs) 204 | if err != nil { 205 | return "", err 206 | } 207 | 208 | if len(ifAddrs) == 0 { 209 | return "", err 210 | } 211 | 212 | ips := make([]string, 0, len(ifAddrs)) 213 | for _, ifAddr := range ifAddrs { 214 | ip := *ToIPAddr(ifAddr.SockAddr) 215 | s := ip.NetIP().String() 216 | ips = append(ips, s) 217 | } 218 | 219 | return strings.Join(ips, " "), nil 220 | } 221 | 222 | // IfAddrAttrs returns a list of attributes supported by the IfAddr type 223 | func IfAddrAttrs() []AttrName { 224 | return ifAddrAttrs 225 | } 226 | 227 | // IfAddrAttr returns a string representation of an attribute for the given 228 | // IfAddr. 229 | func IfAddrAttr(ifAddr IfAddr, attrName AttrName) string { 230 | fn, found := ifAddrAttrMap[attrName] 231 | if !found { 232 | return "" 233 | } 234 | 235 | return fn(ifAddr) 236 | } 237 | 238 | // ifAddrAttrInit is called once at init() 239 | func ifAddrAttrInit() { 240 | // Sorted for human readability 241 | ifAddrAttrs = []AttrName{ 242 | "flags", 243 | "name", 244 | } 245 | 246 | ifAddrAttrMap = map[AttrName]func(ifAddr IfAddr) string{ 247 | "flags": func(ifAddr IfAddr) string { 248 | return ifAddr.Interface.Flags.String() 249 | }, 250 | "name": func(ifAddr IfAddr) string { 251 | return ifAddr.Interface.Name 252 | }, 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /ifattr.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // IfAddr is a union of a SockAddr and a net.Interface. 9 | type IfAddr struct { 10 | SockAddr 11 | net.Interface 12 | } 13 | 14 | // Attr returns the named attribute as a string 15 | func (ifAddr IfAddr) Attr(attrName AttrName) (string, error) { 16 | val := IfAddrAttr(ifAddr, attrName) 17 | if val != "" { 18 | return val, nil 19 | } 20 | 21 | return Attr(ifAddr.SockAddr, attrName) 22 | } 23 | 24 | // Attr returns the named attribute as a string 25 | func Attr(sa SockAddr, attrName AttrName) (string, error) { 26 | switch sockType := sa.Type(); { 27 | case sockType&TypeIP != 0: 28 | ip := *ToIPAddr(sa) 29 | attrVal := IPAddrAttr(ip, attrName) 30 | if attrVal != "" { 31 | return attrVal, nil 32 | } 33 | 34 | if sockType == TypeIPv4 { 35 | ipv4 := *ToIPv4Addr(sa) 36 | attrVal := IPv4AddrAttr(ipv4, attrName) 37 | if attrVal != "" { 38 | return attrVal, nil 39 | } 40 | } else if sockType == TypeIPv6 { 41 | ipv6 := *ToIPv6Addr(sa) 42 | attrVal := IPv6AddrAttr(ipv6, attrName) 43 | if attrVal != "" { 44 | return attrVal, nil 45 | } 46 | } 47 | 48 | case sockType == TypeUnix: 49 | us := *ToUnixSock(sa) 50 | attrVal := UnixSockAttr(us, attrName) 51 | if attrVal != "" { 52 | return attrVal, nil 53 | } 54 | } 55 | 56 | // Non type-specific attributes 57 | switch attrName { 58 | case "string": 59 | return sa.String(), nil 60 | case "type": 61 | return sa.Type().String(), nil 62 | } 63 | 64 | return "", fmt.Errorf("unsupported attribute name %q", attrName) 65 | } 66 | -------------------------------------------------------------------------------- /ifattr_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sockaddr "github.com/hashicorp/go-sockaddr" 7 | ) 8 | 9 | func TestIfAttr_net(t *testing.T) { 10 | ifAddrs, err := sockaddr.GetAllInterfaces() 11 | if err != nil { 12 | t.Fatalf("Unable to proceed: %v", err) 13 | } 14 | 15 | for _, ifAddr := range ifAddrs { 16 | testSockAddrAttr(t, ifAddr) 17 | } 18 | } 19 | 20 | func TestIfAttr_unix(t *testing.T) { 21 | newUnixSock := func(path string) sockaddr.UnixSock { 22 | sa, err := sockaddr.NewUnixSock(path) 23 | if err != nil { 24 | t.Fatalf("unable to create new unix socket: %v", err) 25 | } 26 | return sa 27 | } 28 | unixSockets := []sockaddr.SockAddr{ 29 | newUnixSock("/tmp/test"), 30 | } 31 | 32 | for _, sa := range unixSockets { 33 | testSockAddrAttr(t, sa) 34 | } 35 | } 36 | 37 | func testSockAddrAttr(t *testing.T, sai interface{}) { 38 | attrNamesPerType := []struct { 39 | name sockaddr.AttrName 40 | ipv4Pass bool 41 | ipv6Pass bool 42 | unixPass bool 43 | }{ 44 | // Universal 45 | {"type", true, true, true}, 46 | {"string", true, true, true}, 47 | // IP 48 | {"name", true, true, false}, 49 | {"size", true, true, false}, 50 | {"flags", true, true, false}, 51 | {"host", true, true, false}, 52 | {"address", true, true, false}, 53 | {"port", true, true, false}, 54 | {"netmask", true, true, false}, 55 | {"network", true, true, false}, 56 | {"mask_bits", true, true, false}, 57 | {"binary", true, true, false}, 58 | {"hex", true, true, false}, 59 | {"first_usable", true, true, false}, 60 | {"last_usable", true, true, false}, 61 | {"octets", true, true, false}, 62 | // IPv4 63 | {"broadcast", true, false, false}, 64 | {"uint32", true, false, false}, 65 | // IPv6 66 | {"uint128", false, true, false}, 67 | // Unix 68 | {"path", false, false, true}, 69 | } 70 | 71 | for _, attrTest := range attrNamesPerType { 72 | switch v := sai.(type) { 73 | case sockaddr.IfAddr: 74 | saType := v.Type() 75 | _, err := v.Attr(attrTest.name) 76 | switch saType { 77 | case sockaddr.TypeIPv4: 78 | if err == nil && attrTest.ipv4Pass || err != nil && !attrTest.ipv4Pass { 79 | // pass 80 | } 81 | // fallthrough 82 | case sockaddr.TypeIPv6: 83 | if err == nil && attrTest.ipv6Pass || err != nil && !attrTest.ipv6Pass { 84 | // pass 85 | } 86 | // fallthrough 87 | case sockaddr.TypeUnix: 88 | if err == nil && attrTest.unixPass || err != nil && !attrTest.unixPass { 89 | // pass 90 | } 91 | // fallthrough 92 | default: 93 | t.Errorf("Unable to fetch attr name %q: %v", attrTest.name, err) 94 | } 95 | case sockaddr.SockAddr: 96 | val, err := sockaddr.Attr(v, attrTest.name) 97 | _ = err 98 | 99 | pass := len(val) > 0 100 | switch { 101 | case v.Type() == sockaddr.TypeIPv4 && attrTest.ipv4Pass == pass, 102 | v.Type() == sockaddr.TypeIPv6 && attrTest.ipv6Pass == pass, 103 | v.Type() == sockaddr.TypeUnix && attrTest.unixPass == pass: 104 | // pass 105 | default: 106 | t.Errorf("Unable to fetch attr name %q from %v / %v + %+q", attrTest.name, v, v.Type(), val) 107 | } 108 | default: 109 | t.Fatalf("unsupported type %T %v", sai, sai) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ipaddr.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | // Constants for the sizes of IPv3, IPv4, and IPv6 address types. 11 | const ( 12 | IPv3len = 6 13 | IPv4len = 4 14 | IPv6len = 16 15 | ) 16 | 17 | // IPAddr is a generic IP address interface for IPv4 and IPv6 addresses, 18 | // networks, and socket endpoints. 19 | type IPAddr interface { 20 | SockAddr 21 | AddressBinString() string 22 | AddressHexString() string 23 | Cmp(SockAddr) int 24 | CmpAddress(SockAddr) int 25 | CmpPort(SockAddr) int 26 | FirstUsable() IPAddr 27 | Host() IPAddr 28 | IPPort() IPPort 29 | LastUsable() IPAddr 30 | Maskbits() int 31 | NetIP() *net.IP 32 | NetIPMask() *net.IPMask 33 | NetIPNet() *net.IPNet 34 | Network() IPAddr 35 | Octets() []int 36 | } 37 | 38 | // IPPort is the type for an IP port number for the TCP and UDP IP transports. 39 | type IPPort uint16 40 | 41 | // IPPrefixLen is a typed integer representing the prefix length for a given 42 | // IPAddr. 43 | type IPPrefixLen byte 44 | 45 | // ipAddrAttrMap is a map of the IPAddr type-specific attributes. 46 | var ipAddrAttrMap map[AttrName]func(IPAddr) string 47 | var ipAddrAttrs []AttrName 48 | 49 | func init() { 50 | ipAddrInit() 51 | } 52 | 53 | // NewIPAddr creates a new IPAddr from a string. Returns nil if the string is 54 | // not an IPv4 or an IPv6 address. 55 | func NewIPAddr(addr string) (IPAddr, error) { 56 | ipv4Addr, err := NewIPv4Addr(addr) 57 | if err == nil { 58 | return ipv4Addr, nil 59 | } 60 | 61 | ipv6Addr, err := NewIPv6Addr(addr) 62 | if err == nil { 63 | return ipv6Addr, nil 64 | } 65 | 66 | return nil, fmt.Errorf("invalid IPAddr %v", addr) 67 | } 68 | 69 | // IPAddrAttr returns a string representation of an attribute for the given 70 | // IPAddr. 71 | func IPAddrAttr(ip IPAddr, selector AttrName) string { 72 | fn, found := ipAddrAttrMap[selector] 73 | if !found { 74 | return "" 75 | } 76 | 77 | return fn(ip) 78 | } 79 | 80 | // IPAttrs returns a list of attributes supported by the IPAddr type 81 | func IPAttrs() []AttrName { 82 | return ipAddrAttrs 83 | } 84 | 85 | // MustIPAddr is a helper method that must return an IPAddr or panic on invalid 86 | // input. 87 | func MustIPAddr(addr string) IPAddr { 88 | ip, err := NewIPAddr(addr) 89 | if err != nil { 90 | panic(fmt.Sprintf("Unable to create an IPAddr from %+q: %v", addr, err)) 91 | } 92 | return ip 93 | } 94 | 95 | // ipAddrInit is called once at init() 96 | func ipAddrInit() { 97 | // Sorted for human readability 98 | ipAddrAttrs = []AttrName{ 99 | "host", 100 | "address", 101 | "port", 102 | "netmask", 103 | "network", 104 | "mask_bits", 105 | "binary", 106 | "hex", 107 | "first_usable", 108 | "last_usable", 109 | "octets", 110 | } 111 | 112 | ipAddrAttrMap = map[AttrName]func(ip IPAddr) string{ 113 | "address": func(ip IPAddr) string { 114 | return ip.NetIP().String() 115 | }, 116 | "binary": func(ip IPAddr) string { 117 | return ip.AddressBinString() 118 | }, 119 | "first_usable": func(ip IPAddr) string { 120 | return ip.FirstUsable().String() 121 | }, 122 | "hex": func(ip IPAddr) string { 123 | return ip.AddressHexString() 124 | }, 125 | "host": func(ip IPAddr) string { 126 | return ip.Host().String() 127 | }, 128 | "last_usable": func(ip IPAddr) string { 129 | return ip.LastUsable().String() 130 | }, 131 | "mask_bits": func(ip IPAddr) string { 132 | return fmt.Sprintf("%d", ip.Maskbits()) 133 | }, 134 | "netmask": func(ip IPAddr) string { 135 | switch v := ip.(type) { 136 | case IPv4Addr: 137 | ipv4Mask := IPv4Addr{ 138 | Address: IPv4Address(v.Mask), 139 | Mask: IPv4HostMask, 140 | } 141 | return ipv4Mask.String() 142 | case IPv6Addr: 143 | ipv6Mask := new(big.Int) 144 | ipv6Mask.Set(v.Mask) 145 | ipv6MaskAddr := IPv6Addr{ 146 | Address: IPv6Address(ipv6Mask), 147 | Mask: ipv6HostMask, 148 | } 149 | return ipv6MaskAddr.String() 150 | default: 151 | return fmt.Sprintf("", ip) 152 | } 153 | }, 154 | "network": func(ip IPAddr) string { 155 | return ip.Network().NetIP().String() 156 | }, 157 | "octets": func(ip IPAddr) string { 158 | octets := ip.Octets() 159 | octetStrs := make([]string, 0, len(octets)) 160 | for _, octet := range octets { 161 | octetStrs = append(octetStrs, fmt.Sprintf("%d", octet)) 162 | } 163 | return strings.Join(octetStrs, " ") 164 | }, 165 | "port": func(ip IPAddr) string { 166 | return fmt.Sprintf("%d", ip.IPPort()) 167 | }, 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /ipaddr_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hashicorp/go-sockaddr" 8 | ) 9 | 10 | func TestSockAddr_IPAddr_CmpAddress(t *testing.T) { 11 | tests := []struct { 12 | a string 13 | b string 14 | cmp int 15 | }{ 16 | { // 0: Same IPAddr (v4), same port 17 | a: "208.67.222.222:0", 18 | b: "208.67.222.222/32", 19 | cmp: 0, 20 | }, 21 | { // 1: Same IPAddr (v6), same port 22 | a: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:0", 23 | b: "2607:f0d0:1002:0051:0000:0000:0000:0004/128", 24 | cmp: 0, 25 | }, 26 | { // 2: Same IPAddr (v4), different port 27 | a: "208.67.222.222:4646", 28 | b: "208.67.222.222/32", 29 | cmp: 0, 30 | }, 31 | { // 3: Same IPAddr (v6), different port 32 | a: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:4646", 33 | b: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:4647", 34 | cmp: 0, 35 | }, 36 | { // 4: Different IPAddr (v4), same port 37 | a: "208.67.220.220:4648", 38 | b: "208.67.222.222:4648", 39 | cmp: -1, 40 | }, 41 | { // 5: Different IPAddr (v6), same port 42 | a: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:4648", 43 | b: "[2607:f0d0:1002:0052:0000:0000:0000:0004]:4648", 44 | cmp: -1, 45 | }, 46 | { // 6: Different IPAddr (v4), different port 47 | a: "208.67.220.220:8600", 48 | b: "208.67.222.222:4648", 49 | cmp: -1, 50 | }, 51 | { // 7: Different IPAddr (v6), different port 52 | a: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:8500", 53 | b: "[2607:f0d0:1002:0052:0000:0000:0000:0004]:4648", 54 | cmp: -1, 55 | }, 56 | { // 8: Incompatible IPAddr (v4 vs v6), same port 57 | a: "208.67.220.220:8600", 58 | b: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:8600", 59 | cmp: 0, 60 | }, 61 | { // 9: Incompatible IPAddr (v4 vs v6), different port 62 | a: "208.67.220.220:8500", 63 | b: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:8600", 64 | cmp: 0, 65 | }, 66 | { // 10: Incompatible SockAddr types 67 | a: "128.95.120.1:123", 68 | b: "/tmp/foo.sock", 69 | cmp: 0, 70 | }, 71 | { // 11: Incompatible SockAddr types 72 | a: "[::]:123", 73 | b: "/tmp/foo.sock", 74 | cmp: 0, 75 | }, 76 | } 77 | 78 | for idx, test := range tests { 79 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 80 | saA, err := sockaddr.NewSockAddr(test.a) 81 | if err != nil { 82 | t.Fatalf("[%d] Unable to create a SockAddr from %+q: %v", idx, test.a, err) 83 | } 84 | saB, err := sockaddr.NewSockAddr(test.b) 85 | if err != nil { 86 | t.Fatalf("[%d] Unable to create an SockAddr from %+q: %v", idx, test.b, err) 87 | } 88 | 89 | ipA, ok := saA.(sockaddr.IPAddr) 90 | if !ok { 91 | t.Fatalf("[%d] Unable to convert SockAddr %+q to an IPAddr", idx, test.a) 92 | } 93 | 94 | if x := ipA.CmpAddress(saB); x != test.cmp { 95 | t.Errorf("[%d] IPAddr.CmpAddress() failed with %+q with %+q (expected %d, received %d)", idx, ipA, saB, test.cmp, x) 96 | } 97 | 98 | ipB, ok := saB.(sockaddr.IPAddr) 99 | if !ok { 100 | // Return success for comparing non-IPAddr types 101 | return 102 | } 103 | if x := ipA.CmpAddress(ipB); x != test.cmp { 104 | t.Errorf("[%d] IPAddr.CmpAddress() failed with %+q with %+q (expected %d, received %d)", idx, ipA, ipB, test.cmp, x) 105 | } 106 | if x := ipB.CmpAddress(ipA); x*-1 != test.cmp { 107 | t.Errorf("[%d] IPAddr.CmpAddress() failed with %+q with %+q (expected %d, received %d)", idx, ipB, ipA, test.cmp, x) 108 | } 109 | 110 | if x := ipB.CmpAddress(saA); x*-1 != test.cmp { 111 | t.Errorf("[%d] IPAddr.CmpAddress() failed with %+q with %+q (expected %d, received %d)", idx, ipB, saA, test.cmp, x) 112 | } 113 | }) 114 | } 115 | } 116 | 117 | func TestSockAddr_IPAddr_CmpPort(t *testing.T) { 118 | tests := []struct { 119 | a string 120 | b string 121 | cmp int 122 | }{ 123 | { // 0: Same IPv4Addr, same port 124 | a: "208.67.222.222:0", 125 | b: "208.67.222.222/32", 126 | cmp: 0, 127 | }, 128 | { // 1: Different IPv4Addr, same port 129 | a: "208.67.220.220:0", 130 | b: "208.67.222.222/32", 131 | cmp: 0, 132 | }, 133 | { // 2: Same IPv4Addr, different port 134 | a: "208.67.222.222:80", 135 | b: "208.67.222.222:443", 136 | cmp: -1, 137 | }, 138 | { // 3: Different IPv4Addr, different port 139 | a: "208.67.220.220:8600", 140 | b: "208.67.222.222:53", 141 | cmp: 1, 142 | }, 143 | { // 4: Same IPv6Addr, same port 144 | a: "[::]:0", 145 | b: "::/128", 146 | cmp: 0, 147 | }, 148 | { // 5: Different IPv6Addr, same port 149 | a: "[::]:0", 150 | b: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:0", 151 | cmp: 0, 152 | }, 153 | { // 6: Same IPv6Addr, different port 154 | a: "[::]:8400", 155 | b: "[::]:8600", 156 | cmp: -1, 157 | }, 158 | { // 7: Different IPv6Addr, different port 159 | a: "[::]:8600", 160 | b: "[2607:f0d0:1002:0051:0000:0000:0000:0004]:53", 161 | cmp: 1, 162 | }, 163 | { // 8: Mixed IPAddr types, same port 164 | a: "[::]:53", 165 | b: "208.67.220.220:53", 166 | cmp: 0, 167 | }, 168 | { // 9: Mixed IPAddr types, different port 169 | a: "[::]:53", 170 | b: "128.95.120.1:123", 171 | cmp: -1, 172 | }, 173 | { // 10: Incompatible SockAddr types 174 | a: "128.95.120.1:123", 175 | b: "/tmp/foo.sock", 176 | cmp: 0, 177 | }, 178 | { // 11: Incompatible SockAddr types 179 | a: "[::]:123", 180 | b: "/tmp/foo.sock", 181 | cmp: 0, 182 | }, 183 | } 184 | 185 | for idx, test := range tests { 186 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 187 | saA, err := sockaddr.NewSockAddr(test.a) 188 | if err != nil { 189 | t.Fatalf("[%d] Unable to create a SockAddr from %+q: %v", idx, test.a, err) 190 | } 191 | saB, err := sockaddr.NewSockAddr(test.b) 192 | if err != nil { 193 | t.Fatalf("[%d] Unable to create an SockAddr from %+q: %v", idx, test.b, err) 194 | } 195 | 196 | ipA, ok := saA.(sockaddr.IPAddr) 197 | if !ok { 198 | t.Fatalf("[%d] Unable to convert SockAddr %+q to an IPAddr", idx, test.a) 199 | } 200 | 201 | if x := ipA.CmpPort(saB); x != test.cmp { 202 | t.Errorf("[%d] IPAddr.CmpPort() failed with %+q with %+q (expected %d, received %d)", idx, ipA, saB, test.cmp, x) 203 | } 204 | 205 | ipB, ok := saB.(sockaddr.IPAddr) 206 | if !ok { 207 | // Return success for comparing non-IPAddr types 208 | return 209 | } 210 | if x := ipA.CmpPort(ipB); x != test.cmp { 211 | t.Errorf("[%d] IPAddr.CmpPort() failed with %+q with %+q (expected %d, received %d)", idx, ipA, ipB, test.cmp, x) 212 | } 213 | if x := ipB.CmpPort(ipA); x*-1 != test.cmp { 214 | t.Errorf("[%d] IPAddr.CmpPort() failed with %+q with %+q (expected %d, received %d)", idx, ipB, ipA, test.cmp, x) 215 | } 216 | 217 | if x := ipB.CmpPort(saA); x*-1 != test.cmp { 218 | t.Errorf("[%d] IPAddr.CmpPort() failed with %+q with %+q (expected %d, received %d)", idx, ipB, saA, test.cmp, x) 219 | } 220 | }) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /ipaddrs.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import "bytes" 4 | 5 | type IPAddrs []IPAddr 6 | 7 | func (s IPAddrs) Len() int { return len(s) } 8 | func (s IPAddrs) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 9 | 10 | // // SortIPAddrsByCmp is a type that satisfies sort.Interface and can be used 11 | // // by the routines in this package. The SortIPAddrsByCmp type is used to 12 | // // sort IPAddrs by Cmp() 13 | // type SortIPAddrsByCmp struct{ IPAddrs } 14 | 15 | // // Less reports whether the element with index i should sort before the 16 | // // element with index j. 17 | // func (s SortIPAddrsByCmp) Less(i, j int) bool { 18 | // // Sort by Type, then address, then port number. 19 | // return Less(s.IPAddrs[i], s.IPAddrs[j]) 20 | // } 21 | 22 | // SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and 23 | // can be used by the routines in this package. The 24 | // SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest 25 | // network (most specific to largest network). 26 | type SortIPAddrsByNetworkSize struct{ IPAddrs } 27 | 28 | // Less reports whether the element with index i should sort before the 29 | // element with index j. 30 | func (s SortIPAddrsByNetworkSize) Less(i, j int) bool { 31 | // Sort masks with a larger binary value (i.e. fewer hosts per network 32 | // prefix) after masks with a smaller value (larger number of hosts per 33 | // prefix). 34 | switch bytes.Compare([]byte(*s.IPAddrs[i].NetIPMask()), []byte(*s.IPAddrs[j].NetIPMask())) { 35 | case 0: 36 | // Fall through to the second test if the net.IPMasks are the 37 | // same. 38 | break 39 | case 1: 40 | return true 41 | case -1: 42 | return false 43 | default: 44 | panic("bad, m'kay?") 45 | } 46 | 47 | // Sort IPs based on the length (i.e. prefer IPv4 over IPv6). 48 | iLen := len(*s.IPAddrs[i].NetIP()) 49 | jLen := len(*s.IPAddrs[j].NetIP()) 50 | if iLen != jLen { 51 | return iLen > jLen 52 | } 53 | 54 | // Sort IPs based on their network address from lowest to highest. 55 | switch bytes.Compare(s.IPAddrs[i].NetIPNet().IP, s.IPAddrs[j].NetIPNet().IP) { 56 | case 0: 57 | break 58 | case 1: 59 | return false 60 | case -1: 61 | return true 62 | default: 63 | panic("lol wut?") 64 | } 65 | 66 | // If a host does not have a port set, it always sorts after hosts 67 | // that have a port (e.g. a host with a /32 and port number is more 68 | // specific and should sort first over a host with a /32 but no port 69 | // set). 70 | if s.IPAddrs[i].IPPort() == 0 && s.IPAddrs[j].IPPort() != 0 { 71 | return false 72 | } 73 | if s.IPAddrs[i].IPPort() != 0 && s.IPAddrs[j].IPPort() == 0 { 74 | return true 75 | } 76 | 77 | return s.IPAddrs[i].IPPort() < s.IPAddrs[j].IPPort() 78 | } 79 | 80 | // SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and 81 | // can be used by the routines in this package. The 82 | // SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest 83 | // network (most specific to largest network). 84 | type SortIPAddrsBySpecificMaskLen struct{ IPAddrs } 85 | 86 | // Less reports whether the element with index i should sort before the 87 | // element with index j. 88 | func (s SortIPAddrsBySpecificMaskLen) Less(i, j int) bool { 89 | return s.IPAddrs[i].Maskbits() > s.IPAddrs[j].Maskbits() 90 | } 91 | 92 | // SortIPAddrsByBroadMaskLen is a type that satisfies sort.Interface and can 93 | // be used by the routines in this package. The SortIPAddrsByBroadMaskLen 94 | // type is used to sort IPAddrs by largest network (i.e. largest subnets 95 | // first). 96 | type SortIPAddrsByBroadMaskLen struct{ IPAddrs } 97 | 98 | // Less reports whether the element with index i should sort before the 99 | // element with index j. 100 | func (s SortIPAddrsByBroadMaskLen) Less(i, j int) bool { 101 | return s.IPAddrs[i].Maskbits() < s.IPAddrs[j].Maskbits() 102 | } 103 | -------------------------------------------------------------------------------- /ipaddrs_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr_test 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/hashicorp/go-sockaddr" 9 | ) 10 | 11 | type GoodTestIPAddrTest struct { 12 | sockAddrs sockaddr.SockAddrs 13 | sortedBySpecificMasklen sockaddr.SockAddrs 14 | sortedByBroadMasklen sockaddr.SockAddrs 15 | sortedByNetwork sockaddr.SockAddrs 16 | } 17 | type GoodTestIPAddrTests []*GoodTestIPAddrTest 18 | 19 | func makeTestIPAddrs(t *testing.T) GoodTestIPAddrTests { 20 | goodTestInputs := []struct { 21 | sockAddrs []string 22 | sortedBySpecificMasklen []string 23 | sortedByBroadMasklen []string 24 | sortedByNetwork []string 25 | }{ 26 | { 27 | sockAddrs: []string{ 28 | "10.0.0.0/8", 29 | "172.16.1.3/12", 30 | "192.168.0.0/16", 31 | "128.95.120.1/32", 32 | "192.168.1.10/24", 33 | "240.0.0.1/4", 34 | }, 35 | sortedBySpecificMasklen: []string{ 36 | "128.95.120.1/32", 37 | "192.168.1.10/24", 38 | "192.168.0.0/16", 39 | "172.16.1.3/12", 40 | "10.0.0.0/8", 41 | "240.0.0.1/4", 42 | }, 43 | sortedByBroadMasklen: []string{ 44 | "240.0.0.1/4", 45 | "10.0.0.0/8", 46 | "172.16.1.3/12", 47 | "192.168.0.0/16", 48 | "192.168.1.10/24", 49 | "128.95.120.1/32", 50 | }, 51 | sortedByNetwork: []string{ 52 | "10.0.0.0/8", 53 | "128.95.120.1/32", 54 | "172.16.1.3/12", 55 | "192.168.0.0/16", 56 | "192.168.1.10/24", 57 | "240.0.0.1/4", 58 | }, 59 | }, 60 | } 61 | gfs := make(GoodTestIPAddrTests, 0, len(goodTestInputs)) 62 | for idx, gfi := range goodTestInputs { 63 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 64 | gf := new(GoodTestIPAddrTest) 65 | gf.sockAddrs = make(sockaddr.SockAddrs, 0, len(gfi.sockAddrs)) 66 | for _, n := range gfi.sockAddrs { 67 | sa, err := sockaddr.NewSockAddr(n) 68 | if err != nil { 69 | t.Fatalf("Expected valid network") 70 | } 71 | gf.sockAddrs = append(gf.sockAddrs, sa) 72 | } 73 | 74 | gf.sortedBySpecificMasklen = make(sockaddr.SockAddrs, 0, len(gfi.sortedBySpecificMasklen)) 75 | for _, n := range gfi.sortedBySpecificMasklen { 76 | na, err := sockaddr.NewSockAddr(n) 77 | if err != nil { 78 | t.Fatalf("Expected valid network") 79 | } 80 | gf.sortedBySpecificMasklen = append(gf.sortedBySpecificMasklen, na) 81 | } 82 | 83 | if len(gf.sockAddrs) != len(gf.sortedBySpecificMasklen) { 84 | t.Fatalf("Expected same number of sortedBySpecificMasklen networks") 85 | } 86 | 87 | gf.sortedByBroadMasklen = make(sockaddr.SockAddrs, 0, len(gfi.sortedByBroadMasklen)) 88 | for _, n := range gfi.sortedByBroadMasklen { 89 | na, err := sockaddr.NewSockAddr(n) 90 | if err != nil { 91 | t.Fatalf("Expected valid network") 92 | } 93 | gf.sortedByBroadMasklen = append(gf.sortedByBroadMasklen, na) 94 | } 95 | 96 | if len(gf.sockAddrs) != len(gf.sortedByBroadMasklen) { 97 | t.Fatalf("Expected same number of sortedByBroadMasklen networks") 98 | } 99 | 100 | gf.sortedByNetwork = make(sockaddr.SockAddrs, 0, len(gfi.sortedByNetwork)) 101 | for _, n := range gfi.sortedByNetwork { 102 | na, err := sockaddr.NewSockAddr(n) 103 | if err != nil { 104 | t.Fatalf("Expected valid network") 105 | } 106 | gf.sortedByNetwork = append(gf.sortedByNetwork, na) 107 | } 108 | 109 | if len(gf.sockAddrs) != len(gf.sortedByNetwork) { 110 | t.Fatalf("Expected same number of sortedByNetwork networks") 111 | } 112 | }) 113 | } 114 | 115 | return gfs 116 | } 117 | 118 | func TestSockAddr_IPAddrs_BySpecificMaskLen(t *testing.T) { 119 | testInputs := sockAddrStringInputs{ 120 | { 121 | inputAddrs: []string{"10.0.0.0/8", 122 | "172.16.1.3/12", 123 | "192.168.0.0/16", 124 | "128.95.120.1/32", 125 | "192.168.1.10/24", 126 | "240.0.0.1/4", 127 | }, 128 | sortedAddrs: []string{ 129 | "128.95.120.1/32", 130 | "192.168.1.10/24", 131 | "192.168.0.0/16", 132 | "172.16.1.3/12", 133 | "10.0.0.0/8", 134 | "240.0.0.1/4", 135 | }, 136 | }, 137 | } 138 | 139 | for idx, test := range testInputs { 140 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 141 | inputAddrs := convertToSockAddrs(t, test.inputAddrs) 142 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 143 | sockaddrs := append(sockaddr.SockAddrs(nil), inputAddrs...) 144 | filteredAddrs, _ := sockaddrs.FilterByType(sockaddr.TypeIPv4) 145 | ipv4Addrs := make([]sockaddr.IPv4Addr, 0, len(filteredAddrs)) 146 | for _, x := range filteredAddrs { 147 | switch v := x.(type) { 148 | case sockaddr.IPv4Addr: 149 | ipv4Addrs = append(ipv4Addrs, v) 150 | default: 151 | t.Fatalf("invalid type") 152 | } 153 | } 154 | 155 | ipAddrs := make([]sockaddr.IPAddr, 0, len(filteredAddrs)) 156 | for _, x := range filteredAddrs { 157 | ipAddr, ok := x.(sockaddr.IPAddr) 158 | if !ok { 159 | t.Fatalf("Unable to typecast to IPAddr") 160 | } 161 | ipAddrs = append(ipAddrs, ipAddr) 162 | } 163 | sort.Sort(sockaddr.SortIPAddrsBySpecificMaskLen{ipAddrs}) 164 | 165 | var lastLen int = 32 166 | for i, netaddr := range ipAddrs { 167 | maskLen := netaddr.Maskbits() 168 | if lastLen < maskLen { 169 | t.Fatalf("Sort by specific mask length failed") 170 | } 171 | lastLen = maskLen 172 | 173 | if sortedAddrs[i] != netaddr { 174 | t.Errorf("Expected %s, received %s in iteration %d", sortedAddrs[i], netaddr, i) 175 | } 176 | } 177 | }) 178 | } 179 | } 180 | 181 | func TestSockAddr_IPAddrs_ByBroadMaskLen(t *testing.T) { 182 | testInputs := sockAddrStringInputs{ 183 | { 184 | inputAddrs: []string{"10.0.0.0/8", 185 | "172.16.1.3/12", 186 | "192.168.0.0/16", 187 | "128.95.120.1/32", 188 | "192.168.1.10/24", 189 | "240.0.0.1/4", 190 | }, 191 | sortedAddrs: []string{ 192 | "240.0.0.1/4", 193 | "10.0.0.0/8", 194 | "172.16.1.3/12", 195 | "192.168.0.0/16", 196 | "192.168.1.10/24", 197 | "128.95.120.1/32", 198 | }, 199 | }, 200 | } 201 | 202 | for idx, test := range testInputs { 203 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 204 | inputAddrs := convertToSockAddrs(t, test.inputAddrs) 205 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 206 | sockaddrs := append(sockaddr.SockAddrs(nil), inputAddrs...) 207 | filteredAddrs, _ := sockaddrs.FilterByType(sockaddr.TypeIP) 208 | ipAddrs := make([]sockaddr.IPAddr, 0, len(filteredAddrs)) 209 | for _, x := range filteredAddrs { 210 | ipAddr, ok := x.(sockaddr.IPAddr) 211 | if !ok { 212 | t.Fatalf("Unable to typecast to IPAddr") 213 | } 214 | ipAddrs = append(ipAddrs, ipAddr) 215 | } 216 | sort.Sort(sockaddr.SortIPAddrsByBroadMaskLen{ipAddrs}) 217 | 218 | var lastLen int 219 | for i, netaddr := range ipAddrs { 220 | maskLen := netaddr.Maskbits() 221 | if lastLen > maskLen { 222 | t.Fatalf("Sort by specific mask length failed") 223 | } 224 | lastLen = maskLen 225 | 226 | if sortedAddrs[i] != netaddr { 227 | t.Errorf("Expected %s, received %s in iteration %d", sortedAddrs[i], netaddr, i) 228 | } 229 | } 230 | }) 231 | } 232 | } 233 | 234 | func TestSockAddr_IPAddrs_IPAddrsByNetwork(t *testing.T) { 235 | testInputs := sockAddrStringInputs{ 236 | { 237 | inputAddrs: []string{ 238 | "10.0.0.0/8", 239 | "172.16.1.3/12", 240 | "192.168.0.0/16", 241 | "128.95.120.1/32", 242 | "192.168.1.10/24", 243 | "240.0.0.1/4", 244 | }, 245 | sortedAddrs: []string{ 246 | "10.0.0.0/8", 247 | "128.95.120.1/32", 248 | "172.16.1.3/12", 249 | "192.168.0.0/16", 250 | "192.168.1.10/24", 251 | "240.0.0.1/4", 252 | }, 253 | }, 254 | } 255 | 256 | for idx, test := range testInputs { 257 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 258 | inputAddrs := convertToSockAddrs(t, test.inputAddrs) 259 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 260 | sockaddrs := append(sockaddr.SockAddrs(nil), inputAddrs...) 261 | ipaddrs, _ := sockaddrs.FilterByType(sockaddr.TypeIP) 262 | sockaddr.OrderedAddrBy(sockaddr.AscAddress).Sort(ipaddrs) 263 | 264 | var lastIpUint sockaddr.IPv4Address 265 | for i, sa := range ipaddrs { 266 | ipv4 := *sockaddr.ToIPv4Addr(sa) 267 | if lastIpUint > ipv4.Address { 268 | t.Fatalf("Sort by network failed") 269 | } 270 | lastIpUint = ipv4.Address 271 | 272 | if !ipv4.Equal(sortedAddrs[i]) { 273 | t.Errorf("[%d] Sort equality failed: expected %s, received %s", i, sortedAddrs[i], ipv4) 274 | } 275 | } 276 | }) 277 | } 278 | } 279 | 280 | func TestSockAddr_IPAddrs_IPAddrsByNetworkSize(t *testing.T) { 281 | testInputs := sockAddrStringInputs{ 282 | { 283 | inputAddrs: []string{ 284 | "10.0.0.0/8", 285 | "172.16.1.3/12", 286 | "128.95.120.2:53", 287 | "128.95.120.2/32", 288 | "192.168.0.0/16", 289 | "128.95.120.1/32", 290 | "192.168.1.10/24", 291 | "128.95.120.2:8600", 292 | "240.0.0.1/4", 293 | }, 294 | sortedAddrs: []string{ 295 | "128.95.120.1/32", 296 | "128.95.120.2:53", 297 | "128.95.120.2:8600", 298 | "128.95.120.2/32", 299 | "192.168.1.10/24", 300 | "192.168.0.0/16", 301 | "172.16.1.3/12", 302 | "10.0.0.0/8", 303 | "240.0.0.1/4", 304 | }, 305 | }, 306 | } 307 | 308 | for idx, test := range testInputs { 309 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 310 | inputAddrs := convertToSockAddrs(t, test.inputAddrs) 311 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 312 | 313 | sockaddrs := append(sockaddr.SockAddrs(nil), inputAddrs...) 314 | filteredAddrs, _ := sockaddrs.FilterByType(sockaddr.TypeIP) 315 | ipAddrs := make([]sockaddr.IPAddr, 0, len(filteredAddrs)) 316 | for _, x := range filteredAddrs { 317 | ipAddr, ok := x.(sockaddr.IPAddr) 318 | if !ok { 319 | t.Fatalf("Unable to typecast to IPAddr") 320 | } 321 | ipAddrs = append(ipAddrs, ipAddr) 322 | } 323 | sort.Sort(sockaddr.SortIPAddrsByNetworkSize{ipAddrs}) 324 | 325 | // var prevAddr sockaddr.IPAddr 326 | for i, ipAddr := range ipAddrs { 327 | // if i == 0 { 328 | // prevAddr = ipAddr 329 | // continue 330 | // } 331 | 332 | // if prevAddr.Cmp(ipAddr) > 0 { 333 | // t.Logf("[%d] Prev:\t%v", i, prevAddr) 334 | // t.Logf("[%d] ipAddr:\t%v", i, ipAddr) 335 | // t.Fatalf("Sort by network failed") 336 | // } 337 | // prevAddr = ipAddr 338 | 339 | if !ipAddr.Equal(sortedAddrs[i]) { 340 | t.Errorf("[%d] Sort equality failed: expected %s, received %s", i, sortedAddrs[i], ipAddr) 341 | } 342 | } 343 | }) 344 | } 345 | } 346 | 347 | // func TestSockAddr_IPAddrs_IPAddrsByCmp(t *testing.T) { 348 | // testInputs := testIPAddrsInputs{ 349 | // { 350 | // sockAddrs: []string{ 351 | // "10.0.0.0/8", 352 | // "172.16.1.3/12", 353 | // "128.95.120.2:53", 354 | // "128.95.120.2/32", 355 | // "192.168.0.0/16", 356 | // "128.95.120.1/32", 357 | // "192.168.1.10/24", 358 | // "128.95.120.2:8600", 359 | // "240.0.0.1/4", 360 | // }, 361 | // sortedSockAddrs: []string{ 362 | // "128.95.120.1/32", 363 | // "128.95.120.2:53", 364 | // "128.95.120.2:8600", 365 | // "128.95.120.2/32", 366 | // "192.168.1.10/24", 367 | // "192.168.0.0/16", 368 | // "172.16.1.3/12", 369 | // "10.0.0.0/8", 370 | // "240.0.0.1/4", 371 | // }, 372 | // }, 373 | // } 374 | 375 | // for _, test := range makeTestsFromInput(t, testInputs) { 376 | // sockaddrs := append(sockaddr.SockAddrs(nil), test.sockAddrs...) 377 | // ipAddrs := sockaddrs.FilterByTypeIPAddr() 378 | // sort.Sort(sockaddr.SortIPAddrsByCmp{ipAddrs}) 379 | // t.Logf("Here: %+v", ipAddrs) 380 | 381 | // var prevAddr sockaddr.IPAddr 382 | // for i, ipAddr := range ipAddrs { 383 | // if i == 0 { 384 | // prevAddr = ipAddr 385 | // continue 386 | // } 387 | 388 | // if prevAddr.Cmp(ipAddr) > 0 { 389 | // t.Logf("[%d] Prev:\t%v", i, prevAddr) 390 | // t.Logf("[%d] ipAddr:\t%v", i, ipAddr) 391 | // t.Fatalf("Sort by network failed") 392 | // } 393 | // prevAddr = ipAddr 394 | 395 | // if !ipAddr.Equal(test.sortedSockAddrs[i]) { 396 | // t.Errorf("[%d] Sort equality failed: expected %s, received %s", i, test.sortedSockAddrs[i], ipAddr) 397 | // } 398 | // } 399 | // } 400 | // } 401 | 402 | func TestSockAddr_IPAddrs_IPAddrsByCmp(t *testing.T) { 403 | testInputs := sockAddrStringInputs{ 404 | { 405 | inputAddrs: []string{ 406 | "10.0.0.0/8", 407 | "172.16.1.3/12", 408 | "128.95.120.2:53", 409 | "128.95.120.2:53", 410 | "128.95.120.2/32", 411 | "192.168.0.0/16", 412 | "128.95.120.1/32", 413 | "192.168.1.10/24", 414 | "128.95.120.2:8600", 415 | "0:0:0:0:0:0:0:0", 416 | "0:0:0:0:0:0:0:1", 417 | "2607:f0d0:1002:0051:0000:0000:0000:0004", 418 | "2607:f0d0:1002:0051:0000:0000:0000:0003", 419 | "2607:f0d0:1002:0051:0000:0000:0000:0005", 420 | "[2607:f0d0:1002:0051:0000:0000:0000:0004]:8600", 421 | "240.0.0.1/4", 422 | }, 423 | sortedAddrs: []string{ 424 | "10.0.0.0/8", 425 | "172.16.1.3/12", 426 | "192.168.0.0/16", 427 | "192.168.1.10/24", 428 | "240.0.0.1/4", 429 | "128.95.120.1/32", 430 | "128.95.120.2/32", 431 | "128.95.120.2:53", 432 | "128.95.120.2:53", 433 | "128.95.120.2:8600", 434 | "0:0:0:0:0:0:0:0", 435 | "0:0:0:0:0:0:0:1", 436 | "2607:f0d0:1002:0051:0000:0000:0000:0003", 437 | "2607:f0d0:1002:0051:0000:0000:0000:0004", 438 | "[2607:f0d0:1002:0051:0000:0000:0000:0004]:8600", 439 | "2607:f0d0:1002:0051:0000:0000:0000:0005", 440 | }, 441 | }, 442 | } 443 | 444 | for idx, test := range testInputs { 445 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 446 | shuffleStrings(test.inputAddrs) 447 | 448 | inputAddrs := convertToSockAddrs(t, test.inputAddrs) 449 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 450 | 451 | sockaddr.OrderedAddrBy(sockaddr.AscType, sockaddr.AscPrivate, sockaddr.AscAddress, sockaddr.AscPort).Sort(inputAddrs) 452 | 453 | for i, sockAddr := range inputAddrs { 454 | if !sockAddr.Equal(sortedAddrs[i]) { 455 | t.Errorf("[%d] Sort equality failed: expected %s, received %s", i, sortedAddrs[i], sockAddr) 456 | } 457 | } 458 | }) 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /rfc_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sockaddr "github.com/hashicorp/go-sockaddr" 7 | ) 8 | 9 | func TestVisitAllRFCs(t *testing.T) { 10 | const expectedNumRFCs = 28 11 | numRFCs := 0 12 | sockaddr.VisitAllRFCs(func(rfcNum uint, sas sockaddr.SockAddrs) { 13 | numRFCs++ 14 | }) 15 | if numRFCs != expectedNumRFCs { 16 | t.Fatalf("wrong number of RFCs: %d", numRFCs) 17 | } 18 | } 19 | 20 | func TestIsRFC(t *testing.T) { 21 | tests := []struct { 22 | name string 23 | sa sockaddr.SockAddr 24 | rfcNum uint 25 | result bool 26 | }{ 27 | { 28 | name: "rfc1918 pass", 29 | sa: sockaddr.MustIPv4Addr("192.168.0.0/16"), 30 | rfcNum: 1918, 31 | result: true, 32 | }, 33 | { 34 | name: "rfc1918 fail", 35 | sa: sockaddr.MustIPv4Addr("1.2.3.4"), 36 | rfcNum: 1918, 37 | result: false, 38 | }, 39 | { 40 | name: "rfc1918 pass", 41 | sa: sockaddr.MustIPv4Addr("192.168.1.1"), 42 | rfcNum: 1918, 43 | result: true, 44 | }, 45 | { 46 | name: "invalid rfc", 47 | sa: sockaddr.MustIPv4Addr("192.168.0.0/16"), 48 | rfcNum: 999999999999, 49 | result: false, 50 | }, 51 | } 52 | 53 | for i, test := range tests { 54 | if test.name == "" { 55 | t.Fatalf("test %d needs a name", i) 56 | } 57 | 58 | result := sockaddr.IsRFC(test.rfcNum, test.sa) 59 | if result != test.result { 60 | t.Fatalf("expected a match") 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /route_info.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNoInterface = errors.New("No default interface found (unsupported platform)") 7 | ErrNoRoute = errors.New("no route info found (unsupported platform)") 8 | ) 9 | 10 | // RouteInterface specifies an interface for obtaining memoized route table and 11 | // network information from a given OS. 12 | type RouteInterface interface { 13 | // GetDefaultInterfaceName returns the name of the interface that has a 14 | // default route or an error and an empty string if a problem was 15 | // encountered. 16 | GetDefaultInterfaceName() (string, error) 17 | } 18 | 19 | type routeInfo struct { 20 | cmds map[string][]string 21 | } 22 | 23 | // VisitCommands visits each command used by the platform-specific RouteInfo 24 | // implementation. 25 | func (ri routeInfo) VisitCommands(fn func(name string, cmd []string)) { 26 | for k, v := range ri.cmds { 27 | cmds := append([]string(nil), v...) 28 | fn(k, cmds) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /route_info_aix.go: -------------------------------------------------------------------------------- 1 | //go:build aix 2 | 3 | package sockaddr 4 | 5 | import ( 6 | "errors" 7 | "os/exec" 8 | ) 9 | 10 | var cmds map[string][]string = map[string][]string{ 11 | "route": {"/usr/sbin/route", "-n", "get", "default"}, 12 | } 13 | 14 | // NewRouteInfo returns a BSD-specific implementation of the RouteInfo 15 | // interface. 16 | func NewRouteInfo() (routeInfo, error) { 17 | return routeInfo{ 18 | cmds: cmds, 19 | }, nil 20 | } 21 | 22 | // GetDefaultInterfaceName returns the interface name attached to the default 23 | // route on the default interface. 24 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 25 | out, err := exec.Command(cmds["route"][0], cmds["route"][1:]...).Output() 26 | if err != nil { 27 | return "", err 28 | } 29 | 30 | var ifName string 31 | if ifName, err = parseDefaultIfNameFromRoute(string(out)); err != nil { 32 | return "", errors.New("No default interface found") 33 | } 34 | return ifName, nil 35 | } 36 | -------------------------------------------------------------------------------- /route_info_android.go: -------------------------------------------------------------------------------- 1 | //go:build android 2 | 3 | package sockaddr 4 | 5 | import ( 6 | "errors" 7 | "os/exec" 8 | ) 9 | 10 | // NewRouteInfo returns a Android-specific implementation of the RouteInfo 11 | // interface. 12 | func NewRouteInfo() (routeInfo, error) { 13 | return routeInfo{ 14 | cmds: map[string][]string{"ip": {"/system/bin/ip", "route", "get", "8.8.8.8"}}, 15 | }, nil 16 | } 17 | 18 | // GetDefaultInterfaceName returns the interface name attached to the default 19 | // route on the default interface. 20 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 21 | out, err := exec.Command(ri.cmds["ip"][0], ri.cmds["ip"][1:]...).Output() 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | 27 | var ifName string 28 | if ifName, err = parseDefaultIfNameFromIPCmdAndroid(string(out)); err != nil { 29 | return "", errors.New("No default interface found") 30 | } 31 | return ifName, nil 32 | } 33 | -------------------------------------------------------------------------------- /route_info_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || dragonfly || freebsd || netbsd || openbsd 2 | // +build darwin dragonfly freebsd netbsd openbsd 3 | 4 | package sockaddr 5 | 6 | import "os/exec" 7 | 8 | var cmds = map[string][]string{ 9 | "route": {"/sbin/route", "-n", "get", "default"}, 10 | } 11 | 12 | // NewRouteInfo returns a BSD-specific implementation of the RouteInfo 13 | // interface. 14 | func NewRouteInfo() (routeInfo, error) { 15 | return routeInfo{ 16 | cmds: cmds, 17 | }, nil 18 | } 19 | 20 | // GetDefaultInterfaceName returns the interface name attached to the default 21 | // route on the default interface. 22 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 23 | out, err := exec.Command(cmds["route"][0], cmds["route"][1:]...).Output() 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | var ifName string 29 | if ifName, err = parseDefaultIfNameFromRoute(string(out)); err != nil { 30 | return "", err 31 | } 32 | return ifName, nil 33 | } 34 | -------------------------------------------------------------------------------- /route_info_default.go: -------------------------------------------------------------------------------- 1 | //go:build nacl || plan9 || js || wasip1 2 | // +build nacl plan9 js wasip1 3 | 4 | package sockaddr 5 | 6 | // getDefaultIfName is the default interface function for unsupported platforms. 7 | func getDefaultIfName() (string, error) { 8 | return "", ErrNoInterface 9 | } 10 | 11 | func NewRouteInfo() (routeInfo, error) { 12 | return routeInfo{}, ErrNoRoute 13 | } 14 | 15 | // GetDefaultInterfaceName returns the interface name attached to the default 16 | // route on the default interface. 17 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 18 | return "", ErrNoInterface 19 | } 20 | -------------------------------------------------------------------------------- /route_info_linux.go: -------------------------------------------------------------------------------- 1 | //go:build !android 2 | // +build !android 3 | 4 | package sockaddr 5 | 6 | import ( 7 | "errors" 8 | "os/exec" 9 | ) 10 | 11 | // NewRouteInfo returns a Linux-specific implementation of the RouteInfo 12 | // interface. 13 | func NewRouteInfo() (routeInfo, error) { 14 | // CoreOS Container Linux moved ip to /usr/bin/ip, so look it up on 15 | // $PATH and fallback to /sbin/ip on error. 16 | path, _ := exec.LookPath("ip") 17 | if path == "" { 18 | path = "/sbin/ip" 19 | } 20 | 21 | return routeInfo{ 22 | cmds: map[string][]string{"ip": {path, "route"}}, 23 | }, nil 24 | } 25 | 26 | // GetDefaultInterfaceName returns the interface name attached to the default 27 | // route on the default interface. 28 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 29 | out, err := exec.Command(ri.cmds["ip"][0], ri.cmds["ip"][1:]...).Output() 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | var ifName string 35 | if ifName, err = parseDefaultIfNameFromIPCmd(string(out)); err != nil { 36 | return "", errors.New("No default interface found") 37 | } 38 | return ifName, nil 39 | } 40 | -------------------------------------------------------------------------------- /route_info_solaris.go: -------------------------------------------------------------------------------- 1 | //go:build solaris 2 | 3 | package sockaddr 4 | 5 | import ( 6 | "errors" 7 | "os/exec" 8 | ) 9 | 10 | var cmds map[string][]string = map[string][]string{ 11 | "route": {"/usr/sbin/route", "-n", "get", "default"}, 12 | } 13 | 14 | // NewRouteInfo returns a BSD-specific implementation of the RouteInfo 15 | // interface. 16 | func NewRouteInfo() (routeInfo, error) { 17 | return routeInfo{ 18 | cmds: cmds, 19 | }, nil 20 | } 21 | 22 | // GetDefaultInterfaceName returns the interface name attached to the default 23 | // route on the default interface. 24 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 25 | out, err := exec.Command(cmds["route"][0], cmds["route"][1:]...).Output() 26 | if err != nil { 27 | return "", err 28 | } 29 | 30 | var ifName string 31 | if ifName, err = parseDefaultIfNameFromRoute(string(out)); err != nil { 32 | return "", errors.New("No default interface found") 33 | } 34 | return ifName, nil 35 | } 36 | -------------------------------------------------------------------------------- /route_info_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import "testing" 4 | 5 | func Test_parseBSDDefaultIfName(t *testing.T) { 6 | testCases := []struct { 7 | name string 8 | routeOut string 9 | want string 10 | }{ 11 | { 12 | name: "macOS Sierra 10.12 - Common", 13 | routeOut: ` route to: default 14 | destination: default 15 | mask: default 16 | gateway: 10.23.9.1 17 | interface: en0 18 | flags: 19 | recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire 20 | 0 0 0 0 0 0 1500 0 21 | `, 22 | want: "en0", 23 | }, 24 | } 25 | 26 | for _, tc := range testCases { 27 | t.Run(tc.name, func(t *testing.T) { 28 | got, err := parseDefaultIfNameFromRoute(tc.routeOut) 29 | if err != nil { 30 | t.Fatalf("unable to parse default interface from route output: %v", err) 31 | } 32 | 33 | if got != tc.want { 34 | t.Errorf("got %s; want %s", got, tc.want) 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func Test_parseLinuxDefaultIfName(t *testing.T) { 41 | testCases := []struct { 42 | name string 43 | routeOut string 44 | want string 45 | }{ 46 | { 47 | name: "Linux Ubuntu 14.04 - Common", 48 | routeOut: `default via 10.1.2.1 dev eth0 49 | 10.1.2.0/24 dev eth0 proto kernel scope link src 10.1.2.5 50 | `, 51 | want: "eth0", 52 | }, 53 | { 54 | name: "Chromebook - 8743.85.0 (Official Build) stable-channel gandof, Milestone 54", 55 | routeOut: `default via 192.168.1.1 dev wlan0 metric 1 56 | 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.174 57 | `, 58 | want: "wlan0", 59 | }, 60 | } 61 | 62 | for _, tc := range testCases { 63 | t.Run(tc.name, func(t *testing.T) { 64 | got, err := parseDefaultIfNameFromIPCmd(tc.routeOut) 65 | if err != nil { 66 | t.Fatalf("unable to parse default interface from route output: %v", err) 67 | } 68 | 69 | if got != tc.want { 70 | t.Errorf("got %+q; want %+q", got, tc.want) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func Test_parseWindowsDefaultIfName(t *testing.T) { 77 | testCases := []struct { 78 | name string 79 | routeOut string 80 | ipconfigOut string 81 | want string 82 | }{ 83 | { 84 | name: "Windows 10 - Enterprise", 85 | routeOut: `=========================================================================== 86 | Interface List 87 | 10...08 00 27 a2 e9 51 ......Intel(R) PRO/1000 MT Desktop Adapter 88 | 13...08 00 27 35 02 ed ......Intel(R) PRO/1000 MT Desktop Adapter #2 89 | 1...........................Software Loopback Interface 1 90 | 5...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter 91 | 8...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #3 92 | =========================================================================== 93 | 94 | IPv4 Route Table 95 | =========================================================================== 96 | Active Routes: 97 | Network Destination Netmask Gateway Interface Metric 98 | 0.0.0.0 0.0.0.0 10.0.2.2 10.0.2.15 25 99 | 10.0.2.0 255.255.255.0 On-link 10.0.2.15 281 100 | 10.0.2.15 255.255.255.255 On-link 10.0.2.15 281 101 | 10.0.2.255 255.255.255.255 On-link 10.0.2.15 281 102 | 127.0.0.0 255.0.0.0 On-link 127.0.0.1 331 103 | 127.0.0.1 255.255.255.255 On-link 127.0.0.1 331 104 | 127.255.255.255 255.255.255.255 On-link 127.0.0.1 331 105 | 192.168.56.0 255.255.255.0 On-link 192.168.56.100 281 106 | 192.168.56.100 255.255.255.255 On-link 192.168.56.100 281 107 | 192.168.56.255 255.255.255.255 On-link 192.168.56.100 281 108 | 224.0.0.0 240.0.0.0 On-link 127.0.0.1 331 109 | 224.0.0.0 240.0.0.0 On-link 192.168.56.100 281 110 | 224.0.0.0 240.0.0.0 On-link 10.0.2.15 281 111 | 255.255.255.255 255.255.255.255 On-link 127.0.0.1 331 112 | 255.255.255.255 255.255.255.255 On-link 192.168.56.100 281 113 | 255.255.255.255 255.255.255.255 On-link 10.0.2.15 281 114 | =========================================================================== 115 | Persistent Routes: 116 | None 117 | 118 | IPv6 Route Table 119 | =========================================================================== 120 | Active Routes: 121 | If Metric Network Destination Gateway 122 | 1 331 ::1/128 On-link 123 | 13 281 fe80::/64 On-link 124 | 10 281 fe80::/64 On-link 125 | 13 281 fe80::60cc:155f:77a4:ab99/128 126 | On-link 127 | 10 281 fe80::cccc:710e:f5bb:3088/128 128 | On-link 129 | 1 331 ff00::/8 On-link 130 | 13 281 ff00::/8 On-link 131 | 10 281 ff00::/8 On-link 132 | =========================================================================== 133 | Persistent Routes: 134 | None 135 | `, 136 | ipconfigOut: `Windows IP Configuration 137 | 138 | 139 | Ethernet adapter Ethernet: 140 | 141 | Connection-specific DNS Suffix . : host.example.org 142 | Link-local IPv6 Address . . . . . : fe80::cccc:710e:f5bb:3088%10 143 | IPv4 Address. . . . . . . . . . . : 10.0.2.15 144 | Subnet Mask . . . . . . . . . . . : 255.255.255.0 145 | Default Gateway . . . . . . . . . : 10.0.2.2 146 | 147 | Ethernet adapter Ethernet 2: 148 | 149 | Connection-specific DNS Suffix . : 150 | Link-local IPv6 Address . . . . . : fe80::60cc:155f:77a4:ab99%13 151 | IPv4 Address. . . . . . . . . . . : 192.168.56.100 152 | Subnet Mask . . . . . . . . . . . : 255.255.255.0 153 | Default Gateway . . . . . . . . . : 154 | 155 | Tunnel adapter isatap.host.example.org: 156 | 157 | Media State . . . . . . . . . . . : Media disconnected 158 | Connection-specific DNS Suffix . : 159 | 160 | Tunnel adapter Reusable ISATAP Interface {F3F2E4A5-8823-40E5-87EA-1F6881BACC95}: 161 | 162 | Media State . . . . . . . . . . . : Media disconnected 163 | Connection-specific DNS Suffix . : host.example.org 164 | `, 165 | want: "Ethernet", 166 | }, 167 | } 168 | 169 | for _, tc := range testCases { 170 | t.Run(tc.name, func(t *testing.T) { 171 | got, err := parseDefaultIfNameWindows(tc.routeOut, tc.ipconfigOut) 172 | if err != nil { 173 | t.Fatalf("unable to parse default interface from route output: %v", err) 174 | } 175 | 176 | if got != tc.want { 177 | t.Errorf("got %s; want %s", got, tc.want) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func Test_VisitComands(t *testing.T) { 184 | ri, err := NewRouteInfo() 185 | if err != nil { 186 | t.Fatalf("bad: %v", err) 187 | } 188 | 189 | var count int 190 | ri.VisitCommands(func(name string, cmd []string) { 191 | count++ 192 | }) 193 | if count == 0 { 194 | t.Fatalf("Expected more than 0 items") 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /route_info_test_windows.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import "testing" 4 | 5 | func Test_parseWindowsDefaultIfName_new_vs_old(t *testing.T) { 6 | if !hasPowershell() { 7 | t.Skip("this test requires powershell.") 8 | return 9 | } 10 | ri, err := NewRouteInfo() 11 | if err != nil { 12 | t.Fatalf("bad: %v", err) 13 | } 14 | psVer, err1 := ri.GetDefaultInterfaceName() 15 | legacyVer, err2 := ri.GetDefaultInterfaceNameLegacy() 16 | if err1 != nil { 17 | t.Errorf("err != nil for GetDefaultInterfaceName - %v", err1) 18 | } 19 | if err2 != nil { 20 | t.Errorf("err != nil for GetDefaultInterfaceNameLegacy - %v", err2) 21 | } 22 | if psVer != legacyVer { 23 | t.Errorf("got %s; want %s", psVer, legacyVer) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /route_info_windows.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | ) 7 | 8 | var cmds map[string][]string = map[string][]string{ 9 | "defaultInterface": {"powershell", "Get-NetRoute -DestinationPrefix '0.0.0.0/0' | select -ExpandProperty InterfaceAlias"}, 10 | // These commands enable GetDefaultInterfaceNameLegacy and should be removed 11 | // when it is. 12 | "netstat": {"netstat", "-rn"}, 13 | "ipconfig": {"ipconfig"}, 14 | } 15 | 16 | // NewRouteInfo returns a BSD-specific implementation of the RouteInfo 17 | // interface. 18 | func NewRouteInfo() (routeInfo, error) { 19 | return routeInfo{ 20 | cmds: cmds, 21 | }, nil 22 | } 23 | 24 | // GetDefaultInterfaceName returns the interface name attached to the default 25 | // route on the default interface. 26 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 27 | if !hasPowershell() { 28 | // No powershell, fallback to legacy method 29 | return ri.GetDefaultInterfaceNameLegacy() 30 | } 31 | 32 | ifNameOut, err := exec.Command(cmds["defaultInterface"][0], cmds["defaultInterface"][1:]...).Output() 33 | if err != nil { 34 | return "", err 35 | } 36 | 37 | ifName := strings.TrimSpace(string(ifNameOut[:])) 38 | return ifName, nil 39 | } 40 | 41 | // GetDefaultInterfaceNameLegacy provides legacy behavior for GetDefaultInterfaceName 42 | // on Windows machines without powershell. 43 | func (ri routeInfo) GetDefaultInterfaceNameLegacy() (string, error) { 44 | ifNameOut, err := exec.Command(cmds["netstat"][0], cmds["netstat"][1:]...).Output() 45 | if err != nil { 46 | return "", err 47 | } 48 | 49 | ipconfigOut, err := exec.Command(cmds["ipconfig"][0], cmds["ipconfig"][1:]...).Output() 50 | if err != nil { 51 | return "", err 52 | } 53 | 54 | ifName, err := parseDefaultIfNameWindows(string(ifNameOut), string(ipconfigOut)) 55 | if err != nil { 56 | return "", err 57 | } 58 | 59 | return ifName, nil 60 | } 61 | 62 | func hasPowershell() bool { 63 | _, err := exec.LookPath("powershell") 64 | return (err != nil) 65 | } 66 | -------------------------------------------------------------------------------- /route_info_zos.go: -------------------------------------------------------------------------------- 1 | // +build zos 2 | 3 | package sockaddr 4 | 5 | import ( 6 | "errors" 7 | "os/exec" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var defaultRouteRE *regexp.Regexp = regexp.MustCompile(`^Default +([0-9\.\:]+) +([^ ]+) +([0-9]+) +([^ ]+)`) 13 | 14 | func NewRouteInfo() (routeInfo, error) { 15 | return routeInfo{}, nil 16 | } 17 | 18 | // zosGetDefaultInterfaceName executes the onetstat command and returns its output 19 | func zosGetDefaultInterfaceName() (string, error) { 20 | out, err := exec.Command("/bin/onetstat", "-r").Output() 21 | if err != nil { 22 | return "", err 23 | } 24 | return string(out), nil 25 | } 26 | 27 | // zosProcessOneStatOutput processes the output of onetstat -r and returns the default interface name 28 | func zosProcessOnetstatOutput(output string) (string, error) { 29 | linesout := strings.Split(output, "\n") 30 | for _, line := range linesout { 31 | result := defaultRouteRE.FindStringSubmatch(line) 32 | if result != nil { 33 | return result[4], nil 34 | } 35 | } 36 | return "", errors.New("no default interface found") 37 | } 38 | 39 | // GetDefaultInterfaceName returns the interface name attached to the default route 40 | func (ri routeInfo) GetDefaultInterfaceName() (string, error) { 41 | output, err := zosGetDefaultInterfaceName() 42 | if err != nil { 43 | return "", err 44 | } 45 | return zosProcessOnetstatOutput(output) 46 | } 47 | -------------------------------------------------------------------------------- /route_info_zos_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import "testing" 4 | 5 | func Test_zosProcessOnetstatOutput(t *testing.T) { 6 | dummyOutput := `MVS TCP/IP NETSTAT CS V2R5 TCPIP Name: TCPIP 12:52:35 7 | IPv4 Destinations 8 | Destination Gateway Flags Refcnt Interface 9 | ----------- ------- ----- ------ --------- 10 | Default 0.0.0.0 UGS 0000000000 TCPIPLINK 11 | 0.0.0.0 0.0.0.0 US 0000000000 TCPIPLINK 12 | 0.0.0.0 0.0.0.0 UH 0000000000 TCPIPLINK 13 | 0.0.0.0 0.0.0.0 UH 0000000000 LOOPBACK 14 | 0.0.0.0 0.0.0.0 H 0000000000 EZAZCX 15 | 0.0.0.0 0.0.0.0 H 0000000000 EZASAMEMVS 16 | IPv6 Destinations 17 | DestIP: Default 18 | Gw: :: 19 | Intf: TCPIPLINK6 Refcnt: 0000000000 20 | Flgs: UGS MTU: 2000 21 | DestIP: :: 22 | Gw: :: 23 | Intf: LOOPBACK6 Refcnt: 0000000000 24 | Flgs: UH MTU: 65535 25 | DestIP: :: 26 | Gw: :: 27 | Intf: TCPIPLINK6 Refcnt: 0000000000 28 | Flgs: US MTU: 2000 29 | DestIP: :: 30 | Gw: :: 31 | Intf: TCPIPLINK6 Refcnt: 0000000000 32 | Flgs: UH MTU: 9008 33 | DestIP: :: 34 | Gw: :: 35 | Intf: TCPIPLINK6 Refcnt: 0000000000 36 | Flgs: UH MTU: 9008` 37 | 38 | if _, err := zosProcessOnetstatOutput(dummyOutput); err != nil { 39 | t.Errorf("err != nil for zosProcessOnetstatOutput - %v", err) 40 | } 41 | 42 | if result, err := zosProcessOnetstatOutput(""); err == nil { 43 | t.Errorf("got %s; want \"\"", result) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sockaddr.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type SockAddrType int 10 | type AttrName string 11 | 12 | const ( 13 | TypeUnknown SockAddrType = 0x0 14 | TypeUnix = 0x1 15 | TypeIPv4 = 0x2 16 | TypeIPv6 = 0x4 17 | 18 | // TypeIP is the union of TypeIPv4 and TypeIPv6 19 | TypeIP = 0x6 20 | ) 21 | 22 | type SockAddr interface { 23 | // CmpRFC returns 0 if SockAddr exactly matches one of the matched RFC 24 | // networks, -1 if the receiver is contained within the RFC network, or 25 | // 1 if the address is not contained within the RFC. 26 | CmpRFC(rfcNum uint, sa SockAddr) int 27 | 28 | // Contains returns true if the SockAddr arg is contained within the 29 | // receiver 30 | Contains(SockAddr) bool 31 | 32 | // Equal allows for the comparison of two SockAddrs 33 | Equal(SockAddr) bool 34 | 35 | DialPacketArgs() (string, string) 36 | DialStreamArgs() (string, string) 37 | ListenPacketArgs() (string, string) 38 | ListenStreamArgs() (string, string) 39 | 40 | // String returns the string representation of SockAddr 41 | String() string 42 | 43 | // Type returns the SockAddrType 44 | Type() SockAddrType 45 | } 46 | 47 | // sockAddrAttrMap is a map of the SockAddr type-specific attributes. 48 | var sockAddrAttrMap map[AttrName]func(SockAddr) string 49 | var sockAddrAttrs []AttrName 50 | 51 | func init() { 52 | sockAddrInit() 53 | } 54 | 55 | // New creates a new SockAddr from the string. The order in which New() 56 | // attempts to construct a SockAddr is: IPv4Addr, IPv6Addr, SockAddrUnix. 57 | // 58 | // NOTE: New() relies on the heuristic wherein if the path begins with either a 59 | // '.' or '/' character before creating a new UnixSock. For UNIX sockets that 60 | // are absolute paths or are nested within a sub-directory, this works as 61 | // expected, however if the UNIX socket is contained in the current working 62 | // directory, this will fail unless the path begins with "./" 63 | // (e.g. "./my-local-socket"). Calls directly to NewUnixSock() do not suffer 64 | // this limitation. Invalid IP addresses such as "256.0.0.0/-1" will run afoul 65 | // of this heuristic and be assumed to be a valid UNIX socket path (which they 66 | // are, but it is probably not what you want and you won't realize it until you 67 | // stat(2) the file system to discover it doesn't exist). 68 | func NewSockAddr(s string) (SockAddr, error) { 69 | ipv4Addr, err := NewIPv4Addr(s) 70 | if err == nil { 71 | return ipv4Addr, nil 72 | } 73 | 74 | ipv6Addr, err := NewIPv6Addr(s) 75 | if err == nil { 76 | return ipv6Addr, nil 77 | } 78 | 79 | // Check to make sure the string begins with either a '.' or '/', or 80 | // contains a '/'. 81 | if len(s) > 1 && (strings.IndexAny(s[0:1], "./") != -1 || strings.IndexByte(s, '/') != -1) { 82 | unixSock, err := NewUnixSock(s) 83 | if err == nil { 84 | return unixSock, nil 85 | } 86 | } 87 | 88 | return nil, fmt.Errorf("Unable to convert %q to an IPv4 or IPv6 address, or a UNIX Socket", s) 89 | } 90 | 91 | // ToIPAddr returns an IPAddr type or nil if the type conversion fails. 92 | func ToIPAddr(sa SockAddr) *IPAddr { 93 | ipa, ok := sa.(IPAddr) 94 | if !ok { 95 | return nil 96 | } 97 | return &ipa 98 | } 99 | 100 | // ToIPv4Addr returns an IPv4Addr type or nil if the type conversion fails. 101 | func ToIPv4Addr(sa SockAddr) *IPv4Addr { 102 | switch v := sa.(type) { 103 | case IPv4Addr: 104 | return &v 105 | default: 106 | return nil 107 | } 108 | } 109 | 110 | // ToIPv6Addr returns an IPv6Addr type or nil if the type conversion fails. 111 | func ToIPv6Addr(sa SockAddr) *IPv6Addr { 112 | switch v := sa.(type) { 113 | case IPv6Addr: 114 | return &v 115 | default: 116 | return nil 117 | } 118 | } 119 | 120 | // ToUnixSock returns a UnixSock type or nil if the type conversion fails. 121 | func ToUnixSock(sa SockAddr) *UnixSock { 122 | switch v := sa.(type) { 123 | case UnixSock: 124 | return &v 125 | default: 126 | return nil 127 | } 128 | } 129 | 130 | // SockAddrAttr returns a string representation of an attribute for the given 131 | // SockAddr. 132 | func SockAddrAttr(sa SockAddr, selector AttrName) string { 133 | fn, found := sockAddrAttrMap[selector] 134 | if !found { 135 | return "" 136 | } 137 | 138 | return fn(sa) 139 | } 140 | 141 | // String() for SockAddrType returns a string representation of the 142 | // SockAddrType (e.g. "IPv4", "IPv6", "UNIX", "IP", or "unknown"). 143 | func (sat SockAddrType) String() string { 144 | switch sat { 145 | case TypeIPv4: 146 | return "IPv4" 147 | case TypeIPv6: 148 | return "IPv6" 149 | // There is no concrete "IP" type. Leaving here as a reminder. 150 | // case TypeIP: 151 | // return "IP" 152 | case TypeUnix: 153 | return "UNIX" 154 | default: 155 | panic("unsupported type") 156 | } 157 | } 158 | 159 | // sockAddrInit is called once at init() 160 | func sockAddrInit() { 161 | sockAddrAttrs = []AttrName{ 162 | "type", // type should be first 163 | "string", 164 | } 165 | 166 | sockAddrAttrMap = map[AttrName]func(sa SockAddr) string{ 167 | "string": func(sa SockAddr) string { 168 | return sa.String() 169 | }, 170 | "type": func(sa SockAddr) string { 171 | return sa.Type().String() 172 | }, 173 | } 174 | } 175 | 176 | // UnixSockAttrs returns a list of attributes supported by the UnixSock type 177 | func SockAddrAttrs() []AttrName { 178 | return sockAddrAttrs 179 | } 180 | 181 | // Although this is pretty trivial to do in a program, having the logic here is 182 | // useful all around. Note that this marshals into a *string* -- the underlying 183 | // string representation of the sockaddr. If you then unmarshal into this type 184 | // in Go, all will work as expected, but externally you can take what comes out 185 | // and use the string value directly. 186 | type SockAddrMarshaler struct { 187 | SockAddr 188 | } 189 | 190 | func (s *SockAddrMarshaler) MarshalJSON() ([]byte, error) { 191 | return json.Marshal(s.SockAddr.String()) 192 | } 193 | 194 | func (s *SockAddrMarshaler) UnmarshalJSON(in []byte) error { 195 | var str string 196 | err := json.Unmarshal(in, &str) 197 | if err != nil { 198 | return err 199 | } 200 | sa, err := NewSockAddr(str) 201 | if err != nil { 202 | return err 203 | } 204 | s.SockAddr = sa 205 | return nil 206 | } 207 | -------------------------------------------------------------------------------- /sockaddr_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/hashicorp/go-sockaddr" 9 | ) 10 | 11 | // TODO(sean@): Either extend this test to include IPv6Addr and UnixSock, or 12 | // remove and find a good home to test this functionality elsewhere. 13 | 14 | func TestSockAddr_New(t *testing.T) { 15 | type SockAddrFixture struct { 16 | input string 17 | ResultType string 18 | NetworkAddress string 19 | BroadcastAddress string 20 | IPUint32 sockaddr.IPv4Address 21 | Maskbits int 22 | BinString string 23 | HexString string 24 | FirstUsableAddress string 25 | LastUsableAddress string 26 | } 27 | type SockAddrFixtures []SockAddrFixtures 28 | 29 | goodResults := []SockAddrFixture{ 30 | { 31 | input: "0.0.0.0", 32 | ResultType: "ipv4", 33 | NetworkAddress: "0.0.0.0", 34 | BroadcastAddress: "0.0.0.0", 35 | Maskbits: 32, 36 | IPUint32: 0, 37 | BinString: "00000000000000000000000000000000", 38 | HexString: "00000000", 39 | FirstUsableAddress: "0.0.0.0", 40 | LastUsableAddress: "0.0.0.0", 41 | }, 42 | { 43 | input: "0.0.0.0/0", 44 | ResultType: "ipv4", 45 | NetworkAddress: "0.0.0.0", 46 | BroadcastAddress: "255.255.255.255", 47 | Maskbits: 0, 48 | IPUint32: 0, 49 | BinString: "00000000000000000000000000000000", 50 | HexString: "00000000", 51 | FirstUsableAddress: "0.0.0.1", 52 | LastUsableAddress: "255.255.255.254", 53 | }, 54 | { 55 | input: "0.0.0.1", 56 | ResultType: "ipv4", 57 | NetworkAddress: "0.0.0.1", 58 | BroadcastAddress: "0.0.0.1", 59 | Maskbits: 32, 60 | IPUint32: 1, 61 | BinString: "00000000000000000000000000000001", 62 | HexString: "00000001", 63 | FirstUsableAddress: "0.0.0.1", 64 | LastUsableAddress: "0.0.0.1", 65 | }, 66 | { 67 | input: "0.0.0.1/1", 68 | ResultType: "ipv4", 69 | NetworkAddress: "0.0.0.0", 70 | BroadcastAddress: "127.255.255.255", 71 | Maskbits: 1, 72 | IPUint32: 1, 73 | BinString: "00000000000000000000000000000001", 74 | HexString: "00000001", 75 | FirstUsableAddress: "0.0.0.1", 76 | LastUsableAddress: "127.255.255.254", 77 | }, 78 | { 79 | input: "128.0.0.0", 80 | ResultType: "ipv4", 81 | NetworkAddress: "128.0.0.0", 82 | BroadcastAddress: "128.0.0.0", 83 | Maskbits: 32, 84 | IPUint32: 2147483648, 85 | BinString: "10000000000000000000000000000000", 86 | HexString: "80000000", 87 | FirstUsableAddress: "128.0.0.0", 88 | LastUsableAddress: "128.0.0.0", 89 | }, 90 | { 91 | input: "255.255.255.255", 92 | ResultType: "ipv4", 93 | NetworkAddress: "255.255.255.255", 94 | BroadcastAddress: "255.255.255.255", 95 | Maskbits: 32, 96 | IPUint32: 4294967295, 97 | BinString: "11111111111111111111111111111111", 98 | HexString: "ffffffff", 99 | FirstUsableAddress: "255.255.255.255", 100 | LastUsableAddress: "255.255.255.255", 101 | }, 102 | { 103 | input: "1.2.3.4", 104 | ResultType: "ipv4", 105 | NetworkAddress: "1.2.3.4", 106 | BroadcastAddress: "1.2.3.4", 107 | Maskbits: 32, 108 | IPUint32: 16909060, 109 | BinString: "00000001000000100000001100000100", 110 | HexString: "01020304", 111 | FirstUsableAddress: "1.2.3.4", 112 | LastUsableAddress: "1.2.3.4", 113 | }, 114 | { 115 | input: "192.168.10.10/16", 116 | ResultType: "ipv4", 117 | NetworkAddress: "192.168.0.0", 118 | BroadcastAddress: "192.168.255.255", 119 | Maskbits: 16, 120 | IPUint32: 3232238090, 121 | BinString: "11000000101010000000101000001010", 122 | HexString: "c0a80a0a", 123 | FirstUsableAddress: "192.168.0.1", 124 | LastUsableAddress: "192.168.255.254", 125 | }, 126 | { 127 | input: "192.168.1.10/24", 128 | ResultType: "ipv4", 129 | NetworkAddress: "192.168.1.0", 130 | BroadcastAddress: "192.168.1.255", 131 | Maskbits: 24, 132 | IPUint32: 3232235786, 133 | BinString: "11000000101010000000000100001010", 134 | HexString: "c0a8010a", 135 | FirstUsableAddress: "192.168.1.1", 136 | LastUsableAddress: "192.168.1.254", 137 | }, 138 | { 139 | input: "192.168.0.1", 140 | ResultType: "ipv4", 141 | NetworkAddress: "192.168.0.1", 142 | BroadcastAddress: "192.168.0.1", 143 | Maskbits: 32, 144 | IPUint32: 3232235521, 145 | BinString: "11000000101010000000000000000001", 146 | HexString: "c0a80001", 147 | FirstUsableAddress: "192.168.0.1", 148 | LastUsableAddress: "192.168.0.1", 149 | }, 150 | { 151 | input: "192.168.0.2/31", 152 | ResultType: "ipv4", 153 | NetworkAddress: "192.168.0.2", 154 | BroadcastAddress: "192.168.0.3", 155 | Maskbits: 31, 156 | IPUint32: 3232235522, 157 | BinString: "11000000101010000000000000000010", 158 | HexString: "c0a80002", 159 | FirstUsableAddress: "192.168.0.2", 160 | LastUsableAddress: "192.168.0.3", 161 | }, 162 | { 163 | input: "240.0.0.0/4", 164 | ResultType: "ipv4", 165 | NetworkAddress: "240.0.0.0", 166 | BroadcastAddress: "255.255.255.255", 167 | Maskbits: 4, 168 | IPUint32: 4026531840, 169 | BinString: "11110000000000000000000000000000", 170 | HexString: "f0000000", 171 | FirstUsableAddress: "240.0.0.1", 172 | LastUsableAddress: "255.255.255.254", 173 | }, 174 | } 175 | 176 | for idx, r := range goodResults { 177 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 178 | var ( 179 | addr sockaddr.IPAddr 180 | str string 181 | ) 182 | 183 | sa, err := sockaddr.NewSockAddr(r.input) 184 | if err != nil { 185 | t.Fatalf("Failed parse %s", r.input) 186 | } 187 | 188 | switch r.ResultType { 189 | case "ipv4": 190 | ipv4b, err := sockaddr.NewIPv4Addr(r.input) 191 | if err != nil { 192 | t.Fatalf("[%d] Unable to construct a new IPv4 from %s: %s", idx, r.input, err) 193 | } 194 | if !ipv4b.Equal(sa) { 195 | t.Fatalf("[%d] Equality comparison failed on fresh IPv4", idx) 196 | } 197 | 198 | type_ := sa.Type() 199 | if type_ != sockaddr.TypeIPv4 { 200 | t.Fatalf("[%d] Type mismatch for %s: %d", idx, r.input, type_) 201 | } 202 | 203 | ipv4 := sockaddr.ToIPv4Addr(sa) 204 | if ipv4 == nil { 205 | t.Fatalf("[%d] Failed ToIPv4Addr() %s", idx, r.input) 206 | } 207 | 208 | addr = ipv4.Broadcast() 209 | if addr == nil || addr.NetIP().To4().String() != r.BroadcastAddress { 210 | t.Fatalf("Failed IPv4Addr.BroadcastAddress() %s: expected %+q, received %+q", r.input, r.BroadcastAddress, addr.NetIP().To4().String()) 211 | } 212 | 213 | maskbits := ipv4.Maskbits() 214 | if maskbits != r.Maskbits { 215 | t.Fatalf("Failed Maskbits %s: %d != %d", r.input, maskbits, r.Maskbits) 216 | } 217 | 218 | if ipv4.Address != r.IPUint32 { 219 | t.Fatalf("Failed ToUint32() %s: %d != %d", r.input, ipv4.Address, r.IPUint32) 220 | } 221 | 222 | str = ipv4.AddressBinString() 223 | if str != r.BinString { 224 | t.Fatalf("Failed BinString %s: %s != %s", r.input, str, r.BinString) 225 | } 226 | 227 | str = ipv4.AddressHexString() 228 | if str != r.HexString { 229 | t.Fatalf("Failed HexString %s: %s != %s", r.input, str, r.HexString) 230 | } 231 | 232 | addr = ipv4.Network() 233 | if addr == nil || addr.NetIP().To4().String() != r.NetworkAddress { 234 | t.Fatalf("Failed NetworkAddress %s: %s != %s", r.input, addr.NetIP().To4().String(), r.NetworkAddress) 235 | } 236 | 237 | addr = ipv4.FirstUsable() 238 | if addr == nil || addr.NetIP().To4().String() != r.FirstUsableAddress { 239 | t.Fatalf("Failed FirstUsableAddress %s: %s != %s", r.input, addr.NetIP().To4().String(), r.FirstUsableAddress) 240 | } 241 | 242 | addr = ipv4.LastUsable() 243 | if addr == nil || addr.NetIP().To4().String() != r.LastUsableAddress { 244 | t.Fatalf("Failed LastUsableAddress %s: %s != %s", r.input, addr.NetIP().To4().String(), r.LastUsableAddress) 245 | } 246 | default: 247 | t.Fatalf("Unknown result type: %s", r.ResultType) 248 | } 249 | }) 250 | } 251 | 252 | badResults := []string{ 253 | "256.0.0.0", 254 | "0.0.0.0.0", 255 | } 256 | 257 | for idx, badIP := range badResults { 258 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 259 | sa, err := sockaddr.NewSockAddr(badIP) 260 | if err == nil { 261 | t.Fatalf("Failed should have failed to parse %s: %v", badIP, sa) 262 | } 263 | if sa != nil { 264 | t.Fatalf("SockAddr should be nil") 265 | } 266 | }) 267 | } 268 | 269 | } 270 | 271 | func TestSockAddrAttrs(t *testing.T) { 272 | const expectedNumAttrs = 2 273 | saa := sockaddr.SockAddrAttrs() 274 | if len(saa) != expectedNumAttrs { 275 | t.Fatalf("wrong number of SockAddrAttrs: %d vs %d", len(saa), expectedNumAttrs) 276 | } 277 | 278 | tests := []struct { 279 | name string 280 | sa sockaddr.SockAddr 281 | attr sockaddr.AttrName 282 | want string 283 | }{ 284 | { 285 | name: "type", 286 | sa: sockaddr.MustIPv4Addr("1.2.3.4"), 287 | attr: "type", 288 | want: "IPv4", 289 | }, 290 | { 291 | name: "string", 292 | sa: sockaddr.MustIPv4Addr("1.2.3.4"), 293 | attr: "string", 294 | want: "1.2.3.4", 295 | }, 296 | { 297 | name: "invalid", 298 | sa: sockaddr.MustIPv4Addr("1.2.3.4"), 299 | attr: "ENOENT", 300 | want: "", 301 | }, 302 | } 303 | 304 | for i, test := range tests { 305 | if test.name == "" { 306 | t.Fatalf("test %d needs a name", i) 307 | } 308 | 309 | result := sockaddr.SockAddrAttr(test.sa, test.attr) 310 | if result != test.want { 311 | t.Fatalf("%s: expected %s got %s", test.name, test.want, result) 312 | } 313 | } 314 | } 315 | 316 | func TestToFoo(t *testing.T) { 317 | tests := []struct { 318 | name string 319 | sa sockaddr.SockAddr 320 | passIP bool 321 | passIPv4 bool 322 | passIPv6 bool 323 | passUnix bool 324 | }{ 325 | { 326 | name: "ipv4", 327 | sa: sockaddr.MustIPv4Addr("1.2.3.4"), 328 | passIP: true, 329 | passIPv4: true, 330 | }, 331 | { 332 | name: "ipv6", 333 | sa: sockaddr.MustIPv6Addr("::1"), 334 | passIP: true, 335 | passIPv6: true, 336 | }, 337 | { 338 | name: "unix", 339 | sa: sockaddr.MustUnixSock("/tmp/foo"), 340 | passUnix: true, 341 | }, 342 | } 343 | 344 | for i, test := range tests { 345 | if test.name == "" { 346 | t.Fatalf("test %d must have a name", i) 347 | } 348 | 349 | switch us := sockaddr.ToUnixSock(test.sa); { 350 | case us == nil && test.passUnix, 351 | us != nil && !test.passUnix: 352 | t.Fatalf("bad") 353 | } 354 | 355 | switch ip := sockaddr.ToIPAddr(test.sa); { 356 | case ip == nil && test.passIP, 357 | ip != nil && !test.passIP: 358 | t.Fatalf("bad") 359 | } 360 | 361 | switch ipv4 := sockaddr.ToIPv4Addr(test.sa); { 362 | case ipv4 == nil && test.passIPv4, 363 | ipv4 != nil && !test.passIPv4: 364 | t.Fatalf("bad") 365 | } 366 | 367 | switch ipv6 := sockaddr.ToIPv6Addr(test.sa); { 368 | case ipv6 == nil && test.passIPv6, 369 | ipv6 != nil && !test.passIPv6: 370 | t.Fatalf("bad") 371 | } 372 | } 373 | 374 | } 375 | 376 | func TestSockAddrMarshaler(t *testing.T) { 377 | addr := "192.168.10.24/24" 378 | sa, err := sockaddr.NewSockAddr(addr) 379 | if err != nil { 380 | t.Fatal(err) 381 | } 382 | sam := &sockaddr.SockAddrMarshaler{ 383 | SockAddr: sa, 384 | } 385 | marshaled, err := json.Marshal(sam) 386 | if err != nil { 387 | t.Fatal(err) 388 | } 389 | sam2 := &sockaddr.SockAddrMarshaler{} 390 | err = json.Unmarshal(marshaled, sam2) 391 | if err != nil { 392 | t.Fatal(err) 393 | } 394 | if sam.SockAddr.String() != sam2.SockAddr.String() { 395 | t.Fatalf("mismatch after marshaling: %s vs %s", sam.SockAddr.String(), sam2.SockAddr.String()) 396 | } 397 | if sam2.SockAddr.String() != addr { 398 | t.Fatalf("mismatch after marshaling: %s vs %s", addr, sam2.SockAddr.String()) 399 | } 400 | } 401 | 402 | func TestSockAddrMultiMarshaler(t *testing.T) { 403 | addr := "192.168.10.24/24" 404 | type d struct { 405 | Addr *sockaddr.SockAddrMarshaler 406 | Addrs []*sockaddr.SockAddrMarshaler 407 | } 408 | sa, err := sockaddr.NewSockAddr(addr) 409 | if err != nil { 410 | t.Fatal(err) 411 | } 412 | myD := &d{ 413 | Addr: &sockaddr.SockAddrMarshaler{SockAddr: sa}, 414 | Addrs: []*sockaddr.SockAddrMarshaler{ 415 | &sockaddr.SockAddrMarshaler{SockAddr: sa}, 416 | &sockaddr.SockAddrMarshaler{SockAddr: sa}, 417 | &sockaddr.SockAddrMarshaler{SockAddr: sa}, 418 | }, 419 | } 420 | marshaled, err := json.Marshal(myD) 421 | if err != nil { 422 | t.Fatal(err) 423 | } 424 | var myD2 d 425 | err = json.Unmarshal(marshaled, &myD2) 426 | if err != nil { 427 | t.Fatal(err) 428 | } 429 | if myD.Addr.String() != myD2.Addr.String() { 430 | t.Fatalf("mismatch after marshaling: %s vs %s", myD.Addr.String(), myD2.Addr.String()) 431 | } 432 | if len(myD.Addrs) != len(myD2.Addrs) { 433 | t.Fatalf("mismatch after marshaling: %d vs %d", len(myD.Addrs), len(myD2.Addrs)) 434 | } 435 | for i, v := range myD.Addrs { 436 | if v.String() != myD2.Addrs[i].String() { 437 | t.Fatalf("mismatch after marshaling: %s vs %s", v.String(), myD2.Addrs[i].String()) 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /sockaddrs.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | ) 7 | 8 | // SockAddrs is a slice of SockAddrs 9 | type SockAddrs []SockAddr 10 | 11 | func (s SockAddrs) Len() int { return len(s) } 12 | func (s SockAddrs) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 13 | 14 | // CmpAddrFunc is the function signature that must be met to be used in the 15 | // OrderedAddrBy multiAddrSorter 16 | type CmpAddrFunc func(p1, p2 *SockAddr) int 17 | 18 | // multiAddrSorter implements the Sort interface, sorting the SockAddrs within. 19 | type multiAddrSorter struct { 20 | addrs SockAddrs 21 | cmp []CmpAddrFunc 22 | } 23 | 24 | // Sort sorts the argument slice according to the Cmp functions passed to 25 | // OrderedAddrBy. 26 | func (ms *multiAddrSorter) Sort(sockAddrs SockAddrs) { 27 | ms.addrs = sockAddrs 28 | sort.Sort(ms) 29 | } 30 | 31 | // OrderedAddrBy sorts SockAddr by the list of sort function pointers. 32 | func OrderedAddrBy(cmpFuncs ...CmpAddrFunc) *multiAddrSorter { 33 | return &multiAddrSorter{ 34 | cmp: cmpFuncs, 35 | } 36 | } 37 | 38 | // Len is part of sort.Interface. 39 | func (ms *multiAddrSorter) Len() int { 40 | return len(ms.addrs) 41 | } 42 | 43 | // Less is part of sort.Interface. It is implemented by looping along the 44 | // Cmp() functions until it finds a comparison that is either less than, 45 | // equal to, or greater than. 46 | func (ms *multiAddrSorter) Less(i, j int) bool { 47 | p, q := &ms.addrs[i], &ms.addrs[j] 48 | // Try all but the last comparison. 49 | var k int 50 | for k = 0; k < len(ms.cmp)-1; k++ { 51 | cmp := ms.cmp[k] 52 | x := cmp(p, q) 53 | switch x { 54 | case -1: 55 | // p < q, so we have a decision. 56 | return true 57 | case 1: 58 | // p > q, so we have a decision. 59 | return false 60 | } 61 | // p == q; try the next comparison. 62 | } 63 | // All comparisons to here said "equal", so just return whatever the 64 | // final comparison reports. 65 | switch ms.cmp[k](p, q) { 66 | case -1: 67 | return true 68 | case 1: 69 | return false 70 | default: 71 | // Still a tie! Now what? 72 | return false 73 | } 74 | } 75 | 76 | // Swap is part of sort.Interface. 77 | func (ms *multiAddrSorter) Swap(i, j int) { 78 | ms.addrs[i], ms.addrs[j] = ms.addrs[j], ms.addrs[i] 79 | } 80 | 81 | const ( 82 | // NOTE (sean@): These constants are here for code readability only and 83 | // are sprucing up the code for readability purposes. Some of the 84 | // Cmp*() variants have confusing logic (especially when dealing with 85 | // mixed-type comparisons) and this, I think, has made it easier to grok 86 | // the code faster. 87 | sortReceiverBeforeArg = -1 88 | sortDeferDecision = 0 89 | sortArgBeforeReceiver = 1 90 | ) 91 | 92 | // AscAddress is a sorting function to sort SockAddrs by their respective 93 | // address type. Non-equal types are deferred in the sort. 94 | func AscAddress(p1Ptr, p2Ptr *SockAddr) int { 95 | p1 := *p1Ptr 96 | p2 := *p2Ptr 97 | 98 | switch v := p1.(type) { 99 | case IPv4Addr: 100 | return v.CmpAddress(p2) 101 | case IPv6Addr: 102 | return v.CmpAddress(p2) 103 | case UnixSock: 104 | return v.CmpAddress(p2) 105 | default: 106 | return sortDeferDecision 107 | } 108 | } 109 | 110 | // AscPort is a sorting function to sort SockAddrs by their respective address 111 | // type. Non-equal types are deferred in the sort. 112 | func AscPort(p1Ptr, p2Ptr *SockAddr) int { 113 | p1 := *p1Ptr 114 | p2 := *p2Ptr 115 | 116 | switch v := p1.(type) { 117 | case IPv4Addr: 118 | return v.CmpPort(p2) 119 | case IPv6Addr: 120 | return v.CmpPort(p2) 121 | default: 122 | return sortDeferDecision 123 | } 124 | } 125 | 126 | // AscPrivate is a sorting function to sort "more secure" private values before 127 | // "more public" values. Both IPv4 and IPv6 are compared against RFC6890 128 | // (RFC6890 includes, and is not limited to, RFC1918 and RFC6598 for IPv4, and 129 | // IPv6 includes RFC4193). 130 | func AscPrivate(p1Ptr, p2Ptr *SockAddr) int { 131 | p1 := *p1Ptr 132 | p2 := *p2Ptr 133 | 134 | switch v := p1.(type) { 135 | case IPv4Addr, IPv6Addr: 136 | return v.CmpRFC(6890, p2) 137 | default: 138 | return sortDeferDecision 139 | } 140 | } 141 | 142 | // AscNetworkSize is a sorting function to sort SockAddrs based on their network 143 | // size. Non-equal types are deferred in the sort. 144 | func AscNetworkSize(p1Ptr, p2Ptr *SockAddr) int { 145 | p1 := *p1Ptr 146 | p2 := *p2Ptr 147 | p1Type := p1.Type() 148 | p2Type := p2.Type() 149 | 150 | // Network size operations on non-IP types make no sense 151 | if p1Type != p2Type && p1Type != TypeIP { 152 | return sortDeferDecision 153 | } 154 | 155 | ipA := p1.(IPAddr) 156 | ipB := p2.(IPAddr) 157 | 158 | return bytes.Compare([]byte(*ipA.NetIPMask()), []byte(*ipB.NetIPMask())) 159 | } 160 | 161 | // AscType is a sorting function to sort "more secure" types before 162 | // "less-secure" types. 163 | func AscType(p1Ptr, p2Ptr *SockAddr) int { 164 | p1 := *p1Ptr 165 | p2 := *p2Ptr 166 | p1Type := p1.Type() 167 | p2Type := p2.Type() 168 | switch { 169 | case p1Type < p2Type: 170 | return sortReceiverBeforeArg 171 | case p1Type == p2Type: 172 | return sortDeferDecision 173 | case p1Type > p2Type: 174 | return sortArgBeforeReceiver 175 | default: 176 | return sortDeferDecision 177 | } 178 | } 179 | 180 | // FilterByType returns two lists: a list of matched and unmatched SockAddrs 181 | func (sas SockAddrs) FilterByType(type_ SockAddrType) (matched, excluded SockAddrs) { 182 | matched = make(SockAddrs, 0, len(sas)) 183 | excluded = make(SockAddrs, 0, len(sas)) 184 | 185 | for _, sa := range sas { 186 | if sa.Type()&type_ != 0 { 187 | matched = append(matched, sa) 188 | } else { 189 | excluded = append(excluded, sa) 190 | } 191 | } 192 | return matched, excluded 193 | } 194 | -------------------------------------------------------------------------------- /sockaddrs_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr_test 2 | 3 | import ( 4 | crand "crypto/rand" 5 | "fmt" 6 | "math" 7 | "math/big" 8 | "math/rand" 9 | "testing" 10 | "time" 11 | 12 | sockaddr "github.com/hashicorp/go-sockaddr" 13 | ) 14 | 15 | func init() { 16 | seedMathRand() 17 | } 18 | 19 | // seedMathRand provides weak, but guaranteed seeding, which is better than 20 | // running with Go's default seed of 1. 21 | func seedMathRand() { 22 | n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) 23 | if err != nil { 24 | rand.Seed(time.Now().UTC().UnixNano()) 25 | return 26 | } 27 | rand.Seed(n.Int64()) 28 | } 29 | 30 | // NOTE: A number of these code paths are exercised in template/ and 31 | // cmd/sockaddr/ 32 | 33 | // sockAddrStringInputs allows for easy test creation by developers. 34 | // Parallel arrays of string inputs are converted to their SockAddr 35 | // equivalents for use by unit tests. 36 | type sockAddrStringInputs []struct { 37 | inputAddrs []string 38 | sortedAddrs []string 39 | sortedTypes []sockaddr.SockAddrType 40 | sortFuncs []sockaddr.CmpAddrFunc 41 | numIPv4Inputs int 42 | numIPv6Inputs int 43 | numUnixInputs int 44 | } 45 | 46 | func convertToSockAddrs(t *testing.T, inputs []string) sockaddr.SockAddrs { 47 | sockAddrs := make(sockaddr.SockAddrs, 0, len(inputs)) 48 | for i, input := range inputs { 49 | sa, err := sockaddr.NewSockAddr(input) 50 | if err != nil { 51 | t.Fatalf("[%d] Invalid SockAddr input for %+q: %v", i, input, err) 52 | } 53 | sockAddrs = append(sockAddrs, sa) 54 | } 55 | 56 | return sockAddrs 57 | } 58 | 59 | // shuffleStrings randomly shuffles the list of strings 60 | func shuffleStrings(list []string) { 61 | for i := range list { 62 | j := rand.Intn(i + 1) 63 | list[i], list[j] = list[j], list[i] 64 | } 65 | } 66 | 67 | func TestSockAddr_SockAddrs_AscAddress(t *testing.T) { 68 | testInputs := sockAddrStringInputs{ 69 | { // testNum: 0 70 | sortFuncs: []sockaddr.CmpAddrFunc{ 71 | sockaddr.AscAddress, 72 | }, 73 | numIPv4Inputs: 9, 74 | numIPv6Inputs: 1, 75 | numUnixInputs: 0, 76 | inputAddrs: []string{ 77 | "10.0.0.0/8", 78 | "172.16.1.3/12", 79 | "128.95.120.2:53", 80 | "128.95.120.2/32", 81 | "192.168.0.0/16", 82 | "128.95.120.1/32", 83 | "192.168.1.10/24", 84 | "128.95.120.2:8600", 85 | "240.0.0.1/4", 86 | "::", 87 | }, 88 | sortedAddrs: []string{ 89 | "10.0.0.0/8", 90 | "128.95.120.1/32", 91 | "128.95.120.2:53", 92 | "128.95.120.2/32", 93 | "128.95.120.2:8600", 94 | "172.16.1.3/12", 95 | "192.168.0.0/16", 96 | "192.168.1.10/24", 97 | "240.0.0.1/4", 98 | "::", 99 | }, 100 | }, 101 | } 102 | 103 | for idx, test := range testInputs { 104 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 105 | shuffleStrings(test.inputAddrs) 106 | inputSockAddrs := convertToSockAddrs(t, test.inputAddrs) 107 | sas := convertToSockAddrs(t, test.sortedAddrs) 108 | sortedIPv4Addrs, nonIPv4Addrs := sas.FilterByType(sockaddr.TypeIPv4) 109 | if l := len(sortedIPv4Addrs); l != test.numIPv4Inputs { 110 | t.Fatalf("[%d] Missing IPv4Addrs: expected %d, received %d", idx, test.numIPv4Inputs, l) 111 | } 112 | if len(nonIPv4Addrs) != test.numIPv6Inputs+test.numUnixInputs { 113 | t.Fatalf("[%d] Non-IPv4 Address in input", idx) 114 | } 115 | 116 | // Copy inputAddrs so we can manipulate it. wtb const. 117 | sockAddrs := append(sockaddr.SockAddrs(nil), inputSockAddrs...) 118 | filteredAddrs, _ := sockAddrs.FilterByType(sockaddr.TypeIPv4) 119 | sockaddr.OrderedAddrBy(test.sortFuncs...).Sort(filteredAddrs) 120 | ipv4SockAddrs, nonIPv4s := filteredAddrs.FilterByType(sockaddr.TypeIPv4) 121 | if len(nonIPv4s) != 0 { 122 | t.Fatalf("[%d] bad", idx) 123 | } 124 | 125 | for i, ipv4SockAddr := range ipv4SockAddrs { 126 | ipv4Addr := sockaddr.ToIPv4Addr(ipv4SockAddr) 127 | sortedIPv4Addr := sockaddr.ToIPv4Addr(sortedIPv4Addrs[i]) 128 | if ipv4Addr.Address != sortedIPv4Addr.Address { 129 | t.Errorf("[%d/%d] Sort equality failed: expected %s, received %s", idx, i, sortedIPv4Addrs[i], ipv4Addr) 130 | } 131 | } 132 | }) 133 | } 134 | } 135 | 136 | func TestSockAddr_SockAddrs_AscPrivate(t *testing.T) { 137 | testInputs := []struct { 138 | sortFuncs []sockaddr.CmpAddrFunc 139 | inputAddrs []string 140 | sortedAddrs []string 141 | }{ 142 | { // testNum: 0 143 | sortFuncs: []sockaddr.CmpAddrFunc{ 144 | sockaddr.AscType, 145 | sockaddr.AscPrivate, 146 | sockaddr.AscAddress, 147 | sockaddr.AscType, 148 | sockaddr.AscAddress, 149 | sockaddr.AscPort, 150 | }, 151 | inputAddrs: []string{ 152 | "10.0.0.0/8", 153 | "172.16.1.3/12", 154 | "192.168.0.0/16", 155 | "192.168.0.0/16", 156 | "192.168.1.10/24", 157 | "128.95.120.1/32", 158 | "128.95.120.2/32", 159 | "128.95.120.2:53", 160 | "128.95.120.2:8600", 161 | "240.0.0.1/4", 162 | "::", 163 | }, 164 | sortedAddrs: []string{ 165 | "10.0.0.0/8", 166 | "172.16.1.3/12", 167 | "192.168.0.0/16", 168 | "192.168.0.0/16", 169 | "192.168.1.10/24", 170 | "240.0.0.1/4", 171 | "128.95.120.1/32", 172 | "128.95.120.2/32", 173 | // "128.95.120.2:53", 174 | // "128.95.120.2:8600", 175 | // "::", 176 | }, 177 | }, 178 | { 179 | sortFuncs: []sockaddr.CmpAddrFunc{ 180 | sockaddr.AscType, 181 | sockaddr.AscPrivate, 182 | sockaddr.AscAddress, 183 | }, 184 | inputAddrs: []string{ 185 | "1.2.3.4:53", 186 | "192.168.1.2", 187 | "/tmp/foo", 188 | "[cc::1]:8600", 189 | "[::1]:53", 190 | }, 191 | sortedAddrs: []string{ 192 | "/tmp/foo", 193 | "192.168.1.2", 194 | "1.2.3.4:53", 195 | "[::1]:53", 196 | "[cc::1]:8600", 197 | }, 198 | }, 199 | { 200 | sortFuncs: []sockaddr.CmpAddrFunc{ 201 | sockaddr.AscType, 202 | sockaddr.AscPrivate, 203 | sockaddr.AscAddress, 204 | }, 205 | inputAddrs: []string{ 206 | "/tmp/foo", 207 | "/tmp/bar", 208 | "1.2.3.4", 209 | }, 210 | sortedAddrs: []string{ 211 | "/tmp/bar", 212 | "/tmp/foo", 213 | "1.2.3.4", 214 | }, 215 | }, 216 | } 217 | 218 | for idx, test := range testInputs { 219 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 220 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 221 | 222 | inputAddrs := append([]string(nil), test.inputAddrs...) 223 | shuffleStrings(inputAddrs) 224 | inputSockAddrs := convertToSockAddrs(t, inputAddrs) 225 | 226 | sockaddr.OrderedAddrBy(test.sortFuncs...).Sort(inputSockAddrs) 227 | 228 | for i, sockAddr := range sortedAddrs { 229 | if !sockAddr.Equal(inputSockAddrs[i]) { 230 | t.Logf("Input Addrs:\t%+v", inputAddrs) 231 | t.Logf("Sorted Addrs:\t%+v", inputSockAddrs) 232 | t.Logf("Expected Addrs:\t%+v", test.sortedAddrs) 233 | t.Fatalf("[%d/%d] Sort AscType/AscAddress failed: expected %+q, received %+q", idx, i, sockAddr, inputSockAddrs[i]) 234 | } 235 | } 236 | }) 237 | } 238 | } 239 | 240 | func TestSockAddr_SockAddrs_AscPort(t *testing.T) { 241 | testInputs := []struct { 242 | name string 243 | sortFuncs []sockaddr.CmpAddrFunc 244 | inputAddrs []string 245 | sortedAddrs []string 246 | }{ 247 | { 248 | name: "simple port test", 249 | sortFuncs: []sockaddr.CmpAddrFunc{ 250 | sockaddr.AscPort, 251 | sockaddr.AscType, 252 | }, 253 | inputAddrs: []string{ 254 | "1.2.3.4:53", 255 | "/tmp/foo", 256 | "[::1]:53", 257 | }, 258 | sortedAddrs: []string{ 259 | "/tmp/foo", 260 | "1.2.3.4:53", 261 | "[::1]:53", 262 | }, 263 | }, 264 | { 265 | name: "simple port test", 266 | sortFuncs: []sockaddr.CmpAddrFunc{ 267 | sockaddr.AscPort, 268 | sockaddr.AscType, 269 | }, 270 | inputAddrs: []string{ 271 | "1.2.3.4:53", 272 | "/tmp/foo", 273 | }, 274 | sortedAddrs: []string{ 275 | "/tmp/foo", 276 | "1.2.3.4:53", 277 | }, 278 | }, 279 | } 280 | 281 | for idx, test := range testInputs { 282 | t.Run(test.name, func(t *testing.T) { 283 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 284 | 285 | inputAddrs := append([]string(nil), test.inputAddrs...) 286 | shuffleStrings(inputAddrs) 287 | inputSockAddrs := convertToSockAddrs(t, inputAddrs) 288 | 289 | sockaddr.OrderedAddrBy(test.sortFuncs...).Sort(inputSockAddrs) 290 | 291 | for i, sockAddr := range sortedAddrs { 292 | if !sockAddr.Equal(inputSockAddrs[i]) { 293 | t.Logf("Input Addrs:\t%+v", inputAddrs) 294 | t.Logf("Sorted Addrs:\t%+v", inputSockAddrs) 295 | t.Logf("Expected Addrs:\t%+v", test.sortedAddrs) 296 | t.Fatalf("[%d/%d] Sort AscType/AscAddress failed: expected %+q, received %+q", idx, i, sockAddr, inputSockAddrs[i]) 297 | } 298 | } 299 | }) 300 | } 301 | } 302 | 303 | func TestSockAddr_SockAddrs_AscType(t *testing.T) { 304 | testInputs := sockAddrStringInputs{ 305 | { // testNum: 0 306 | sortFuncs: []sockaddr.CmpAddrFunc{ 307 | sockaddr.AscType, 308 | }, 309 | inputAddrs: []string{ 310 | "10.0.0.0/8", 311 | "172.16.1.3/12", 312 | "128.95.120.2:53", 313 | "::", 314 | "128.95.120.2/32", 315 | "192.168.0.0/16", 316 | "128.95.120.1/32", 317 | "192.168.1.10/24", 318 | "128.95.120.2:8600", 319 | "240.0.0.1/4", 320 | }, 321 | sortedTypes: []sockaddr.SockAddrType{ 322 | sockaddr.TypeIPv4, 323 | sockaddr.TypeIPv4, 324 | sockaddr.TypeIPv4, 325 | sockaddr.TypeIPv4, 326 | sockaddr.TypeIPv4, 327 | sockaddr.TypeIPv4, 328 | sockaddr.TypeIPv4, 329 | sockaddr.TypeIPv4, 330 | sockaddr.TypeIPv4, 331 | sockaddr.TypeIPv6, 332 | }, 333 | }, 334 | } 335 | 336 | for idx, test := range testInputs { 337 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 338 | shuffleStrings(test.inputAddrs) 339 | 340 | inputSockAddrs := convertToSockAddrs(t, test.inputAddrs) 341 | sortedAddrs := convertToSockAddrs(t, test.sortedAddrs) 342 | 343 | sockaddr.OrderedAddrBy(test.sortFuncs...).Sort(inputSockAddrs) 344 | 345 | for i, sockAddr := range sortedAddrs { 346 | if sockAddr.Type() != sortedAddrs[i].Type() { 347 | t.Errorf("[%d/%d] Sort AscType failed: expected %+q, received %+q", idx, i, sortedAddrs[i], sockAddr) 348 | } 349 | } 350 | }) 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /template/GNUmakefile: -------------------------------------------------------------------------------- 1 | test:: 2 | go test 3 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # sockaddr/template 2 | 3 | sockaddr's template library. See 4 | the 5 | [sockaddr/template](https://godoc.org/github.com/hashicorp/go-sockaddr/template) 6 | docs for details on how to use this template. 7 | -------------------------------------------------------------------------------- /template/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Package sockaddr/template provides a text/template interface the SockAddr helper 4 | functions. The primary entry point into the sockaddr/template package is 5 | through its Parse() call. For example: 6 | 7 | import ( 8 | "fmt" 9 | 10 | template "github.com/hashicorp/go-sockaddr/template" 11 | ) 12 | 13 | results, err := template.Parse(`{{ GetPrivateIP }}`) 14 | if err != nil { 15 | fmt.Errorf("Unable to find a private IP address: %v", err) 16 | } 17 | fmt.Printf("My Private IP address is: %s\n", results) 18 | 19 | Below is a list of builtin template functions and details re: their usage. It 20 | is possible to add additional functions by calling ParseIfAddrsTemplate 21 | directly. 22 | 23 | In general, the calling convention for this template library is to seed a list 24 | of initial interfaces via one of the Get*Interfaces() calls, then filter, sort, 25 | and extract the necessary attributes for use as string input. This template 26 | interface is primarily geared toward resolving specific values that are only 27 | available at runtime, but can be defined as a heuristic for execution when a 28 | config file is parsed. 29 | 30 | All functions, unless noted otherwise, return an array of IfAddr structs making 31 | it possible to `sort`, `filter`, `limit`, seek (via the `offset` function), or 32 | `unique` the list. To extract useful string information, the `attr` and `join` 33 | functions return a single string value. See below for details. 34 | 35 | Important note: see the 36 | https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr utility for 37 | more examples and for a CLI utility to experiment with the template syntax. 38 | 39 | `GetAllInterfaces` - Returns an exhaustive set of IfAddr structs available on 40 | the host. `GetAllInterfaces` is the initial input and accessible as the initial 41 | "dot" in the pipeline. 42 | 43 | Example: 44 | 45 | {{ GetAllInterfaces }} 46 | 47 | 48 | `GetDefaultInterfaces` - Returns one IfAddr for every IP that is on the 49 | interface containing the default route for the host. 50 | 51 | Example: 52 | 53 | {{ GetDefaultInterfaces }} 54 | 55 | `GetPrivateInterfaces` - Returns one IfAddr for every forwardable IP address 56 | that is included in RFC 6890 and whose interface is marked as up. NOTE: RFC 6890 is a more exhaustive 57 | version of RFC1918 because it spans IPv4 and IPv6, however, RFC6890 does permit the 58 | inclusion of likely undesired addresses such as multicast, therefore our version 59 | of "private" also filters out non-forwardable addresses. 60 | 61 | Example: 62 | 63 | {{ GetPrivateInterfaces | sort "default" | join "address" " " }} 64 | 65 | 66 | `GetPublicInterfaces` - Returns a list of IfAddr structs whos IPs are 67 | forwardable, do not match RFC 6890, and whose interface is marked up. 68 | 69 | Example: 70 | 71 | {{ GetPublicInterfaces | sort "default" | join "name" " " }} 72 | 73 | 74 | `GetPrivateIP` - Helper function that returns a string of the first IP address 75 | from GetPrivateInterfaces. 76 | 77 | Example: 78 | 79 | {{ GetPrivateIP }} 80 | 81 | 82 | `GetPrivateIPs` - Helper function that returns a string of the all private IP 83 | addresses on the host. 84 | 85 | Example: 86 | 87 | {{ GetPrivateIPs }} 88 | 89 | 90 | `GetPublicIP` - Helper function that returns a string of the first IP from 91 | GetPublicInterfaces. 92 | 93 | Example: 94 | 95 | {{ GetPublicIP }} 96 | 97 | `GetPublicIPs` - Helper function that returns a space-delimited string of the 98 | all public IP addresses on the host. 99 | 100 | Example: 101 | 102 | {{ GetPrivateIPs }} 103 | 104 | 105 | `GetInterfaceIP` - Helper function that returns a string of the first IP from 106 | the named interface. 107 | 108 | Example: 109 | 110 | {{ GetInterfaceIP "en0" }} 111 | 112 | 113 | 114 | `GetInterfaceIPs` - Helper function that returns a space-delimited list of all 115 | IPs on a given interface. 116 | 117 | Example: 118 | 119 | {{ GetInterfaceIPs "en0" }} 120 | 121 | 122 | `sort` - Sorts the IfAddrs result based on its arguments. `sort` takes one 123 | argument, a list of ways to sort its IfAddrs argument. The list of sort 124 | criteria is comma separated (`,`): 125 | - `address`, `+address`: Ascending sort of IfAddrs by Address 126 | - `-address`: Descending sort of IfAddrs by Address 127 | - `default`, `+default`: Ascending sort of IfAddrs, IfAddr with a default route first 128 | - `-default`: Descending sort of IfAddrs, IfAttr with default route last 129 | - `name`, `+name`: Ascending sort of IfAddrs by lexical ordering of interface name 130 | - `-name`: Descending sort of IfAddrs by lexical ordering of interface name 131 | - `port`, `+port`: Ascending sort of IfAddrs by port number 132 | - `-port`: Descending sort of IfAddrs by port number 133 | - `private`, `+private`: Ascending sort of IfAddrs with private addresses first 134 | - `-private`: Descending sort IfAddrs with private addresses last 135 | - `size`, `+size`: Ascending sort of IfAddrs by their network size as determined 136 | by their netmask (larger networks first) 137 | - `-size`: Descending sort of IfAddrs by their network size as determined by their 138 | netmask (smaller networks first) 139 | - `type`, `+type`: Ascending sort of IfAddrs by the type of the IfAddr (Unix, 140 | IPv4, then IPv6) 141 | - `-type`: Descending sort of IfAddrs by the type of the IfAddr (IPv6, IPv4, Unix) 142 | 143 | Example: 144 | 145 | {{ GetPrivateInterfaces | sort "default,-type,size,+address" }} 146 | 147 | 148 | `exclude` and `include`: Filters IfAddrs based on the selector criteria and its 149 | arguments. Both `exclude` and `include` take two arguments. The list of 150 | available filtering criteria is: 151 | - "address": Filter IfAddrs based on a regexp matching the string representation 152 | of the address 153 | - "flag","flags": Filter IfAddrs based on the list of flags specified. Multiple 154 | flags can be passed together using the pipe character (`|`) to create an inclusive 155 | bitmask of flags. The list of flags is included below. 156 | - "name": Filter IfAddrs based on a regexp matching the interface name. 157 | - "network": Filter IfAddrs based on whether a netowkr is included in a given 158 | CIDR. More than one CIDR can be passed in if each network is separated by 159 | the pipe character (`|`). 160 | - "port": Filter IfAddrs based on an exact match of the port number (number must 161 | be expressed as a string) 162 | - "rfc", "rfcs": Filter IfAddrs based on the matching RFC. If more than one RFC 163 | is specified, the list of RFCs can be joined together using the pipe character (`|`). 164 | - "size": Filter IfAddrs based on the exact match of the mask size. 165 | - "type": Filter IfAddrs based on their SockAddr type. Multiple types can be 166 | specified together by using the pipe character (`|`). Valid types include: 167 | `ip`, `ipv4`, `ipv6`, and `unix`. 168 | 169 | Example: 170 | 171 | {{ GetPrivateInterfaces | exclude "type" "IPv6" }} 172 | 173 | 174 | `unique`: Removes duplicate entries from the IfAddrs list, assuming the list has 175 | already been sorted. `unique` only takes one argument: 176 | - "address": Removes duplicates with the same address 177 | - "name": Removes duplicates with the same interface names 178 | 179 | Example: 180 | 181 | {{ GetAllInterfaces | sort "default,-type,address" | unique "name" }} 182 | 183 | 184 | `limit`: Reduces the size of the list to the specified value. 185 | 186 | Example: 187 | 188 | {{ GetPrivateInterfaces | limit 1 }} 189 | 190 | 191 | `offset`: Seeks into the list by the specified value. A negative value can be 192 | used to seek from the end of the list. 193 | 194 | Example: 195 | 196 | {{ GetPrivateInterfaces | offset "-2" | limit 1 }} 197 | 198 | 199 | `math`: Perform a "math" operation on each member of the list and return new 200 | values. `math` takes two arguments, the attribute to operate on and the 201 | operation's value. 202 | 203 | Supported operations include: 204 | 205 | - `address`: Adds the value, a positive or negative value expressed as a 206 | decimal string, to the address. The sign is required. This value is 207 | allowed to over or underflow networks (e.g. 127.255.255.255 `"address" "+1"` 208 | will return "128.0.0.0"). Addresses will wrap at IPv4 or IPv6 boundaries. 209 | - `network`: Add the value, a positive or negative value expressed as a 210 | decimal string, to the network address. The sign is required. Positive 211 | values are added to the network address. Negative values are subtracted 212 | from the network's broadcast address (e.g. 127.0.0.1 `"network" "-1"` will 213 | return "127.255.255.255"). Values that overflow the network size will 214 | safely wrap. 215 | - `mask`: Applies the given network mask to the address. The network mask is 216 | expressed as a decimal value (e.g. network mask "24" corresponds to 217 | `255.255.255.0`). After applying the network mask, the network mask of the 218 | resulting address will be either the applied network mask or the network mask 219 | of the input address depending on which network is larger 220 | (e.g. 192.168.10.20/24 `"mask" "16"` will return "192.168.0.0/16" but 221 | 192.168.10.20/24 `"mask" "28"` will return "192.168.10.16/24"). 222 | 223 | Example: 224 | 225 | {{ GetPrivateInterfaces | include "type" "IP" | math "address" "+256" | attr "address" }} 226 | {{ GetPrivateInterfaces | include "type" "IP" | math "address" "-256" | attr "address" }} 227 | {{ GetPrivateInterfaces | include "type" "IP" | math "network" "+2" | attr "address" }} 228 | {{ GetPrivateInterfaces | include "type" "IP" | math "network" "-2" | attr "address" }} 229 | {{ GetPrivateInterfaces | include "type" "IP" | math "mask" "24" | attr "address" }} 230 | {{ GetPrivateInterfaces | include "flags" "forwardable|up" | include "type" "IPv4" | math "network" "+2" | attr "address" }} 231 | 232 | 233 | `attr`: Extracts a single attribute of the first member of the list and returns 234 | it as a string. `attr` takes a single attribute name. The list of available 235 | attributes is type-specific and shared between `join`. See below for a list of 236 | supported attributes. 237 | 238 | Example: 239 | 240 | {{ GetAllInterfaces | exclude "flags" "up" | attr "address" }} 241 | 242 | 243 | `Attr`: Extracts a single attribute from an `IfAttr` and in every other way 244 | performs the same as the `attr`. 245 | 246 | Example: 247 | 248 | {{ with $ifAddrs := GetAllInterfaces | include "type" "IP" | sort "+type,+address" -}} 249 | {{- range $ifAddrs -}} 250 | {{- Attr "address" . }} -- {{ Attr "network" . }}/{{ Attr "size" . -}} 251 | {{- end -}} 252 | {{- end }} 253 | 254 | 255 | `join`: Similar to `attr`, `join` extracts all matching attributes of the list 256 | and returns them as a string joined by the separator, the second argument to 257 | `join`. The list of available attributes is type-specific and shared between 258 | `join`. 259 | 260 | Example: 261 | 262 | {{ GetAllInterfaces | include "flags" "forwardable" | join "address" " " }} 263 | 264 | 265 | `exclude` and `include` flags: 266 | - `broadcast` 267 | - `down`: Is the interface down? 268 | - `forwardable`: Is the IP forwardable? 269 | - `global unicast` 270 | - `interface-local multicast` 271 | - `link-local multicast` 272 | - `link-local unicast` 273 | - `loopback` 274 | - `multicast` 275 | - `point-to-point` 276 | - `unspecified`: Is the IfAddr the IPv6 unspecified address? 277 | - `up`: Is the interface up? 278 | 279 | 280 | Attributes for `attr`, `Attr`, and `join`: 281 | 282 | SockAddr Type: 283 | - `string` 284 | - `type` 285 | 286 | IPAddr Type: 287 | - `address` 288 | - `binary` 289 | - `first_usable` 290 | - `hex` 291 | - `host` 292 | - `last_usable` 293 | - `mask_bits` 294 | - `netmask` 295 | - `network` 296 | - `octets`: Decimal values per byte 297 | - `port` 298 | - `size`: Number of hosts in the network 299 | 300 | IPv4Addr Type: 301 | - `broadcast` 302 | - `uint32`: unsigned integer representation of the value 303 | 304 | IPv6Addr Type: 305 | - `uint128`: unsigned integer representation of the value 306 | 307 | UnixSock Type: 308 | - `path` 309 | 310 | */ 311 | package template 312 | -------------------------------------------------------------------------------- /template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | "github.com/hashicorp/errwrap" 9 | sockaddr "github.com/hashicorp/go-sockaddr" 10 | ) 11 | 12 | var ( 13 | // SourceFuncs is a map of all top-level functions that generate 14 | // sockaddr data types. 15 | SourceFuncs template.FuncMap 16 | 17 | // SortFuncs is a map of all functions used in sorting 18 | SortFuncs template.FuncMap 19 | 20 | // FilterFuncs is a map of all functions used in sorting 21 | FilterFuncs template.FuncMap 22 | 23 | // HelperFuncs is a map of all functions used in sorting 24 | HelperFuncs template.FuncMap 25 | ) 26 | 27 | func init() { 28 | SourceFuncs = template.FuncMap{ 29 | // GetAllInterfaces - Returns an exhaustive set of IfAddr 30 | // structs available on the host. `GetAllInterfaces` is the 31 | // initial input and accessible as the initial "dot" in the 32 | // pipeline. 33 | "GetAllInterfaces": sockaddr.GetAllInterfaces, 34 | 35 | // GetDefaultInterfaces - Returns one IfAddr for every IP that 36 | // is on the interface containing the default route for the 37 | // host. 38 | "GetDefaultInterfaces": sockaddr.GetDefaultInterfaces, 39 | 40 | // GetPrivateInterfaces - Returns one IfAddr for every IP that 41 | // matches RFC 6890, are attached to the interface with the 42 | // default route, and are forwardable IP addresses. NOTE: RFC 43 | // 6890 is a more exhaustive version of RFC1918 because it spans 44 | // IPv4 and IPv6, however it doespermit the inclusion of likely 45 | // undesired addresses such as multicast, therefore our 46 | // definition of a "private" address also excludes 47 | // non-forwardable IP addresses (as defined by the IETF). 48 | "GetPrivateInterfaces": sockaddr.GetPrivateInterfaces, 49 | 50 | // GetPublicInterfaces - Returns a list of IfAddr that do not 51 | // match RFC 6890, are attached to the default route, and are 52 | // forwardable. 53 | "GetPublicInterfaces": sockaddr.GetPublicInterfaces, 54 | } 55 | 56 | SortFuncs = template.FuncMap{ 57 | "sort": sockaddr.SortIfBy, 58 | } 59 | 60 | FilterFuncs = template.FuncMap{ 61 | "exclude": sockaddr.ExcludeIfs, 62 | "include": sockaddr.IncludeIfs, 63 | } 64 | 65 | HelperFuncs = template.FuncMap{ 66 | // Misc functions that operate on IfAddrs inputs 67 | "attr": Attr, 68 | "join": sockaddr.JoinIfAddrs, 69 | "limit": sockaddr.LimitIfAddrs, 70 | "offset": sockaddr.OffsetIfAddrs, 71 | "unique": sockaddr.UniqueIfAddrsBy, 72 | 73 | // Misc math functions that operate on a single IfAddr input 74 | "math": sockaddr.IfAddrsMath, 75 | 76 | // Return a Private RFC 6890 IP address string that is attached 77 | // to the default route and a forwardable address. 78 | "GetPrivateIP": sockaddr.GetPrivateIP, 79 | 80 | // Return all Private RFC 6890 IP addresses as a space-delimited string of 81 | // IP addresses. Addresses returned do not have to be on the interface with 82 | // a default route. 83 | "GetPrivateIPs": sockaddr.GetPrivateIPs, 84 | 85 | // Return a Public RFC 6890 IP address string that is attached 86 | // to the default route and a forwardable address. 87 | "GetPublicIP": sockaddr.GetPublicIP, 88 | 89 | // Return allPublic RFC 6890 IP addresses as a space-delimited string of IP 90 | // addresses. Addresses returned do not have to be on the interface with a 91 | // default route. 92 | "GetPublicIPs": sockaddr.GetPublicIPs, 93 | 94 | // Return the first IP address of the named interface, sorted by 95 | // the largest network size. 96 | "GetInterfaceIP": sockaddr.GetInterfaceIP, 97 | 98 | // Return all IP addresses on the named interface, sorted by the largest 99 | // network size. 100 | "GetInterfaceIPs": sockaddr.GetInterfaceIPs, 101 | } 102 | } 103 | 104 | // Attr returns the attribute from the ifAddrRaw argument. If the argument is 105 | // an IfAddrs, only the first element will be evaluated for resolution. 106 | func Attr(selectorName string, ifAddrsRaw interface{}) (string, error) { 107 | switch v := ifAddrsRaw.(type) { 108 | case sockaddr.IfAddr: 109 | return sockaddr.IfAttr(selectorName, v) 110 | case sockaddr.IfAddrs: 111 | return sockaddr.IfAttrs(selectorName, v) 112 | default: 113 | return "", fmt.Errorf("unable to obtain attribute %s from type %T (%v)", selectorName, ifAddrsRaw, ifAddrsRaw) 114 | } 115 | } 116 | 117 | // Parse parses input as template input using the addresses available on the 118 | // host, then returns the string output if there are no errors. 119 | func Parse(input string) (string, error) { 120 | addrs, err := sockaddr.GetAllInterfaces() 121 | if err != nil { 122 | return "", errwrap.Wrapf("unable to query interface addresses: {{err}}", err) 123 | } 124 | 125 | return ParseIfAddrs(input, addrs) 126 | } 127 | 128 | // ParseIfAddrs parses input as template input using the IfAddrs inputs, then 129 | // returns the string output if there are no errors. 130 | func ParseIfAddrs(input string, ifAddrs sockaddr.IfAddrs) (string, error) { 131 | return ParseIfAddrsTemplate(input, ifAddrs, template.New("sockaddr.Parse")) 132 | } 133 | 134 | // ParseIfAddrsTemplate parses input as template input using the IfAddrs inputs, 135 | // then returns the string output if there are no errors. 136 | func ParseIfAddrsTemplate(input string, ifAddrs sockaddr.IfAddrs, tmplIn *template.Template) (string, error) { 137 | // Create a template, add the function map, and parse the text. 138 | tmpl, err := tmplIn.Option("missingkey=error"). 139 | Funcs(SourceFuncs). 140 | Funcs(SortFuncs). 141 | Funcs(FilterFuncs). 142 | Funcs(HelperFuncs). 143 | Parse(input) 144 | if err != nil { 145 | return "", errwrap.Wrapf(fmt.Sprintf("unable to parse template %+q: {{err}}", input), err) 146 | } 147 | 148 | var outWriter bytes.Buffer 149 | err = tmpl.Execute(&outWriter, ifAddrs) 150 | if err != nil { 151 | return "", errwrap.Wrapf(fmt.Sprintf("unable to execute sockaddr input %+q: {{err}}", input), err) 152 | } 153 | 154 | return outWriter.String(), nil 155 | } 156 | -------------------------------------------------------------------------------- /unixsock.go: -------------------------------------------------------------------------------- 1 | package sockaddr 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type UnixSock struct { 9 | SockAddr 10 | path string 11 | } 12 | type UnixSocks []*UnixSock 13 | 14 | // unixAttrMap is a map of the UnixSockAddr type-specific attributes. 15 | var unixAttrMap map[AttrName]func(UnixSock) string 16 | var unixAttrs []AttrName 17 | 18 | func init() { 19 | unixAttrInit() 20 | } 21 | 22 | // NewUnixSock creates an UnixSock from a string path. String can be in the 23 | // form of either URI-based string (e.g. `file:///etc/passwd`), an absolute 24 | // path (e.g. `/etc/passwd`), or a relative path (e.g. `./foo`). 25 | func NewUnixSock(s string) (ret UnixSock, err error) { 26 | ret.path = s 27 | return ret, nil 28 | } 29 | 30 | // Contains returns true if sa and us have the same path 31 | func (us UnixSock) Contains(sa SockAddr) bool { 32 | usb, ok := sa.(UnixSock) 33 | if !ok { 34 | return false 35 | } 36 | 37 | return usb.path == us.path 38 | } 39 | 40 | // CmpAddress follows the Cmp() standard protocol and returns: 41 | // 42 | // - -1 If the receiver should sort first because its name lexically sorts before arg 43 | // - 0 if the SockAddr arg is not a UnixSock, or is a UnixSock with the same path. 44 | // - 1 If the argument should sort first. 45 | func (us UnixSock) CmpAddress(sa SockAddr) int { 46 | usb, ok := sa.(UnixSock) 47 | if !ok { 48 | return sortDeferDecision 49 | } 50 | 51 | return strings.Compare(us.Path(), usb.Path()) 52 | } 53 | 54 | // CmpRFC doesn't make sense for a Unix socket, so just return defer decision 55 | func (us UnixSock) CmpRFC(rfcNum uint, sa SockAddr) int { return sortDeferDecision } 56 | 57 | // DialPacketArgs returns the arguments required to be passed to net.DialUnix() 58 | // with the `unixgram` network type. 59 | func (us UnixSock) DialPacketArgs() (network, dialArgs string) { 60 | return "unixgram", us.path 61 | } 62 | 63 | // DialStreamArgs returns the arguments required to be passed to net.DialUnix() 64 | // with the `unix` network type. 65 | func (us UnixSock) DialStreamArgs() (network, dialArgs string) { 66 | return "unix", us.path 67 | } 68 | 69 | // Equal returns true if a SockAddr is equal to the receiving UnixSock. 70 | func (us UnixSock) Equal(sa SockAddr) bool { 71 | usb, ok := sa.(UnixSock) 72 | if !ok { 73 | return false 74 | } 75 | 76 | if us.Path() != usb.Path() { 77 | return false 78 | } 79 | 80 | return true 81 | } 82 | 83 | // ListenPacketArgs returns the arguments required to be passed to 84 | // net.ListenUnixgram() with the `unixgram` network type. 85 | func (us UnixSock) ListenPacketArgs() (network, dialArgs string) { 86 | return "unixgram", us.path 87 | } 88 | 89 | // ListenStreamArgs returns the arguments required to be passed to 90 | // net.ListenUnix() with the `unix` network type. 91 | func (us UnixSock) ListenStreamArgs() (network, dialArgs string) { 92 | return "unix", us.path 93 | } 94 | 95 | // MustUnixSock is a helper method that must return an UnixSock or panic on 96 | // invalid input. 97 | func MustUnixSock(addr string) UnixSock { 98 | us, err := NewUnixSock(addr) 99 | if err != nil { 100 | panic(fmt.Sprintf("Unable to create a UnixSock from %+q: %v", addr, err)) 101 | } 102 | return us 103 | } 104 | 105 | // Path returns the given path of the UnixSock 106 | func (us UnixSock) Path() string { 107 | return us.path 108 | } 109 | 110 | // String returns the path of the UnixSock 111 | func (us UnixSock) String() string { 112 | return fmt.Sprintf("%+q", us.path) 113 | } 114 | 115 | // Type is used as a type switch and returns TypeUnix 116 | func (UnixSock) Type() SockAddrType { 117 | return TypeUnix 118 | } 119 | 120 | // UnixSockAttrs returns a list of attributes supported by the UnixSockAddr type 121 | func UnixSockAttrs() []AttrName { 122 | return unixAttrs 123 | } 124 | 125 | // UnixSockAttr returns a string representation of an attribute for the given 126 | // UnixSock. 127 | func UnixSockAttr(us UnixSock, attrName AttrName) string { 128 | fn, found := unixAttrMap[attrName] 129 | if !found { 130 | return "" 131 | } 132 | 133 | return fn(us) 134 | } 135 | 136 | // unixAttrInit is called once at init() 137 | func unixAttrInit() { 138 | // Sorted for human readability 139 | unixAttrs = []AttrName{ 140 | "path", 141 | } 142 | 143 | unixAttrMap = map[AttrName]func(us UnixSock) string{ 144 | "path": func(us UnixSock) string { 145 | return us.Path() 146 | }, 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /unixsock_test.go: -------------------------------------------------------------------------------- 1 | package sockaddr_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sockaddr "github.com/hashicorp/go-sockaddr" 7 | ) 8 | 9 | func TestUnixSock_impl_SockAddr(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | input sockaddr.UnixSock 13 | dialPacketArgs []string 14 | dialStreamArgs []string 15 | listenPacketArgs []string 16 | listenStreamArgs []string 17 | }{ 18 | { 19 | name: "simple", 20 | input: sockaddr.MustUnixSock("/tmp/foo"), 21 | dialPacketArgs: []string{"unixgram", "/tmp/foo"}, 22 | dialStreamArgs: []string{"unixgram", "/tmp/foo"}, 23 | listenPacketArgs: []string{"unixgram", "/tmp/foo"}, 24 | listenStreamArgs: []string{"unixgram", "/tmp/foo"}, 25 | }, 26 | } 27 | 28 | for i, test := range tests { 29 | if test.name == "" { 30 | t.Fatalf("test %d needs a name", i) 31 | } 32 | 33 | arg1, arg2 := test.input.DialPacketArgs() 34 | if arg1 != test.dialPacketArgs[0] && arg2 != test.dialPacketArgs[1] { 35 | t.Fatalf("%s: %q %q", test.name, arg1, arg2) 36 | } 37 | 38 | arg1, arg2 = test.input.DialStreamArgs() 39 | if arg1 != test.dialStreamArgs[0] && arg2 != test.dialStreamArgs[1] { 40 | t.Fatalf("%s: %q %q", test.name, arg1, arg2) 41 | } 42 | 43 | arg1, arg2 = test.input.ListenPacketArgs() 44 | if arg1 != test.listenPacketArgs[0] && arg2 != test.listenPacketArgs[1] { 45 | t.Fatalf("%s: %q %q", test.name, arg1, arg2) 46 | } 47 | 48 | arg1, arg2 = test.input.ListenStreamArgs() 49 | if arg1 != test.listenStreamArgs[0] && arg2 != test.listenStreamArgs[1] { 50 | t.Fatalf("%s: %q %q", test.name, arg1, arg2) 51 | } 52 | } 53 | } 54 | 55 | func TestUnixSock_Equal(t *testing.T) { 56 | tests := []struct { 57 | name string 58 | input sockaddr.UnixSock 59 | sa sockaddr.SockAddr 60 | equal bool 61 | }{ 62 | { 63 | name: "equal", 64 | input: sockaddr.MustUnixSock("/tmp/foo"), 65 | sa: sockaddr.MustUnixSock("/tmp/foo"), 66 | equal: true, 67 | }, 68 | { 69 | name: "not equal", 70 | input: sockaddr.MustUnixSock("/tmp/foo"), 71 | sa: sockaddr.MustUnixSock("/tmp/bar"), 72 | equal: false, 73 | }, 74 | { 75 | name: "ipv4", 76 | input: sockaddr.MustUnixSock("/tmp/foo"), 77 | sa: sockaddr.MustIPv4Addr("1.2.3.4"), 78 | equal: false, 79 | }, 80 | { 81 | name: "ipv6", 82 | input: sockaddr.MustUnixSock("/tmp/foo"), 83 | sa: sockaddr.MustIPv6Addr("::1"), 84 | equal: false, 85 | }, 86 | } 87 | 88 | for i, test := range tests { 89 | if test.name == "" { 90 | t.Fatalf("test %d needs a name", i) 91 | } 92 | 93 | t.Run(test.name, func(t *testing.T) { 94 | us := test.input 95 | if ret := us.Equal(test.sa); ret != test.equal { 96 | t.Fatalf("%s: equal: %v %q %q", test.name, ret, us, test.sa) 97 | } 98 | }) 99 | } 100 | } 101 | 102 | func TestUnixSockAttrs(t *testing.T) { 103 | const expectedNumAttrs = 1 104 | usa := sockaddr.UnixSockAttrs() 105 | if len(usa) != expectedNumAttrs { 106 | t.Fatalf("wrong number of UnixSockAttrs: %d vs %d", len(usa), expectedNumAttrs) 107 | } 108 | } 109 | --------------------------------------------------------------------------------