diff --git a/hack/dockerfile/install/proxy.installer b/hack/dockerfile/install/proxy.installer index 00c2f1dd05e06..b5748b51a886f 100755 --- a/hack/dockerfile/install/proxy.installer +++ b/hack/dockerfile/install/proxy.installer @@ -3,7 +3,7 @@ # LIBNETWORK_COMMIT is used to build the docker-userland-proxy binary. When # updating the binary version, consider updating github.com/docker/libnetwork # in vendor.conf accordingly -LIBNETWORK_COMMIT=19279f0492417475b6bfbd0aa529f73e8f178fb5 +LIBNETWORK_COMMIT=430c00a6a6b3dfdd774f21e1abd4ad6b0216c629 install_proxy() { case "$1" in diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index 95f7ccfff07b8..a0abb6798a21c 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -610,17 +610,17 @@ func (s *DockerNetworkSuite) TestDockerNetworkIPAMMultipleNetworks(c *check.C) { // test network with multiple subnets // bridge network doesn't support multiple subnets. hence, use a dummy driver that supports - dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, "--subnet=192.168.0.0/16", "--subnet=192.170.0.0/16", "test6") + dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, "--subnet=192.170.0.0/16", "--subnet=192.171.0.0/16", "test6") assertNwIsAvailable(c, "test6") // test network with multiple subnets with valid ipam combinations // also check same subnet across networks when the driver supports it. dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, - "--subnet=192.168.0.0/16", "--subnet=192.170.0.0/16", - "--gateway=192.168.0.100", "--gateway=192.170.0.100", - "--ip-range=192.168.1.0/24", - "--aux-address", "a=192.168.1.5", "--aux-address", "b=192.168.1.6", - "--aux-address", "c=192.170.1.5", "--aux-address", "d=192.170.1.6", + "--subnet=192.172.0.0/16", "--subnet=192.173.0.0/16", + "--gateway=192.172.0.100", "--gateway=192.173.0.100", + "--ip-range=192.172.1.0/24", + "--aux-address", "a=192.172.1.5", "--aux-address", "b=192.172.1.6", + "--aux-address", "c=192.173.1.5", "--aux-address", "d=192.173.1.6", "test7") assertNwIsAvailable(c, "test7") diff --git a/vendor.conf b/vendor.conf index cd059b719e248..5327c9d194129 100644 --- a/vendor.conf +++ b/vendor.conf @@ -37,14 +37,14 @@ github.com/mitchellh/hashstructure 2bca23e0e452137f789efbc8610126fd8b94f73b #get libnetwork packages # When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy accordingly -github.com/docker/libnetwork 19279f0492417475b6bfbd0aa529f73e8f178fb5 +github.com/docker/libnetwork 430c00a6a6b3dfdd774f21e1abd4ad6b0216c629 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b github.com/hashicorp/memberlist 3d8438da9589e7b608a83ffac1ef8211486bcb7c github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372 -github.com/hashicorp/go-sockaddr acd314c5781ea706c710d9ea70069fd2e110d61d +github.com/hashicorp/go-sockaddr 6d291a969b86c4b633730bfc6b8b9d64c3aafed9 github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef diff --git a/vendor/github.com/docker/libnetwork/controller.go b/vendor/github.com/docker/libnetwork/controller.go index fe9c4ddf97ecf..5e967b7c32771 100644 --- a/vendor/github.com/docker/libnetwork/controller.go +++ b/vendor/github.com/docker/libnetwork/controller.go @@ -69,6 +69,7 @@ import ( "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/osl" "github.com/docker/libnetwork/types" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -1252,7 +1253,7 @@ func (c *controller) loadDriver(networkType string) error { } if err != nil { - if err == plugins.ErrNotFound { + if errors.Cause(err) == plugins.ErrNotFound { return types.NotFoundErrorf(err.Error()) } return err diff --git a/vendor/github.com/docker/libnetwork/ipam/allocator.go b/vendor/github.com/docker/libnetwork/ipam/allocator.go index 29473c5cc3224..7213148bcca88 100644 --- a/vendor/github.com/docker/libnetwork/ipam/allocator.go +++ b/vendor/github.com/docker/libnetwork/ipam/allocator.go @@ -29,7 +29,10 @@ const ( // Allocator provides per address space ipv4/ipv6 book keeping type Allocator struct { // Predefined pools for default address spaces - predefined map[string][]*net.IPNet + // Separate from the addrSpace because they should not be serialized + predefined map[string][]*net.IPNet + predefinedStartIndices map[string]int + // The (potentially serialized) address spaces addrSpaces map[string]*addrSpace // stores []datastore.Datastore // Allocated addresses in each address space's subnet @@ -47,6 +50,9 @@ func NewAllocator(lcDs, glDs datastore.DataStore) (*Allocator, error) { globalAddressSpace: ipamutils.PredefinedGranularNetworks, } + // Initialize asIndices map + a.predefinedStartIndices = make(map[string]int) + // Initialize bitseq map a.addresses = make(map[SubnetKey]*bitseq.Handle) @@ -374,11 +380,24 @@ func (a *Allocator) retrieveBitmask(k SubnetKey, n *net.IPNet) (*bitseq.Handle, func (a *Allocator) getPredefineds(as string) []*net.IPNet { a.Lock() defer a.Unlock() - l := make([]*net.IPNet, 0, len(a.predefined[as])) - for _, pool := range a.predefined[as] { - l = append(l, pool) + + p := a.predefined[as] + i := a.predefinedStartIndices[as] + // defensive in case the list changed since last update + if i >= len(p) { + i = 0 } - return l + return append(p[i:], p[:i]...) +} + +func (a *Allocator) updateStartIndex(as string, amt int) { + a.Lock() + i := a.predefinedStartIndices[as] + amt + if i < 0 || i >= len(a.predefined[as]) { + i = 0 + } + a.predefinedStartIndices[as] = i + a.Unlock() } func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error) { @@ -397,21 +416,26 @@ func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error) return nil, err } - for _, nw := range a.getPredefineds(as) { + predefined := a.getPredefineds(as) + + aSpace.Lock() + for i, nw := range predefined { if v != getAddressVersion(nw.IP) { continue } - aSpace.Lock() + // Checks whether pool has already been allocated if _, ok := aSpace.subnets[SubnetKey{AddressSpace: as, Subnet: nw.String()}]; ok { - aSpace.Unlock() continue } + // Shouldn't be necessary, but check prevents IP collisions should + // predefined pools overlap for any reason. if !aSpace.contains(as, nw) { aSpace.Unlock() + a.updateStartIndex(as, i+1) return nw, nil } - aSpace.Unlock() } + aSpace.Unlock() return nil, types.NotFoundErrorf("could not find an available, non-overlapping IPv%d address pool among the defaults to assign to the network", v) } diff --git a/vendor/github.com/docker/libnetwork/ipam/structures.go b/vendor/github.com/docker/libnetwork/ipam/structures.go index 09a77695dd4f3..455a16ca650a0 100644 --- a/vendor/github.com/docker/libnetwork/ipam/structures.go +++ b/vendor/github.com/docker/libnetwork/ipam/structures.go @@ -262,12 +262,13 @@ func (aSpace *addrSpace) updatePoolDBOnAdd(k SubnetKey, nw *net.IPNet, ipr *Addr defer aSpace.Unlock() // Check if already allocated - if p, ok := aSpace.subnets[k]; ok { + if _, ok := aSpace.subnets[k]; ok { if pdf { return nil, types.InternalMaskableErrorf("predefined pool %s is already reserved", nw) } - aSpace.incRefCount(p, 1) - return func() error { return nil }, nil + // This means the same pool is already allocated. updatePoolDBOnAdd is called when there + // is request for a pool/subpool. It should ensure there is no overlap with existing pools + return nil, ipamapi.ErrPoolOverlap } // If master pool, check for overlap diff --git a/vendor/github.com/docker/libnetwork/network.go b/vendor/github.com/docker/libnetwork/network.go index e283d658153ea..70d6b1cc696ff 100644 --- a/vendor/github.com/docker/libnetwork/network.go +++ b/vendor/github.com/docker/libnetwork/network.go @@ -1156,26 +1156,27 @@ func (n *network) createEndpoint(name string, options ...EndpointOption) (Endpoi ep.releaseAddress() } }() - // Moving updateToSTore before calling addEndpoint so that we shall clean up VETH interfaces in case - // DockerD get killed between addEndpoint and updateSTore call - if err = n.getController().updateToStore(ep); err != nil { + + if err = n.addEndpoint(ep); err != nil { return nil, err } defer func() { if err != nil { - if e := n.getController().deleteFromStore(ep); e != nil { - logrus.Warnf("error rolling back endpoint %s from store: %v", name, e) + if e := ep.deleteEndpoint(false); e != nil { + logrus.Warnf("cleaning up endpoint failed %s : %v", name, e) } } }() - if err = n.addEndpoint(ep); err != nil { + // We should perform updateToStore call right after addEndpoint + // in order to have iface properly configured + if err = n.getController().updateToStore(ep); err != nil { return nil, err } defer func() { if err != nil { - if e := ep.deleteEndpoint(false); e != nil { - logrus.Warnf("cleaning up endpoint failed %s : %v", name, e) + if e := n.getController().deleteFromStore(ep); e != nil { + logrus.Warnf("error rolling back endpoint %s from store: %v", name, e) } } }() diff --git a/vendor/github.com/docker/libnetwork/networkdb/delegate.go b/vendor/github.com/docker/libnetwork/networkdb/delegate.go index 9a379fe7cdb20..6cd827ee26240 100644 --- a/vendor/github.com/docker/libnetwork/networkdb/delegate.go +++ b/vendor/github.com/docker/libnetwork/networkdb/delegate.go @@ -41,7 +41,7 @@ func (nDB *NetworkDB) handleNodeEvent(nEvent *NodeEvent) bool { // If the node is not known from memberlist we cannot process save any state of it else if it actually // dies we won't receive any notification and we will remain stuck with it if _, ok := nDB.nodes[nEvent.NodeName]; !ok { - logrus.Error("node: %s is unknown to memberlist", nEvent.NodeName) + logrus.Errorf("node: %s is unknown to memberlist", nEvent.NodeName) return false } diff --git a/vendor/github.com/docker/libnetwork/types/types.go b/vendor/github.com/docker/libnetwork/types/types.go index f851d6fbb7c45..5968545ba5143 100644 --- a/vendor/github.com/docker/libnetwork/types/types.go +++ b/vendor/github.com/docker/libnetwork/types/types.go @@ -145,7 +145,12 @@ func (p *PortBinding) String() string { return ret } -// FromString reads the PortBinding structure from string +// FromString reads the PortBinding structure from string s. +// String s is a triple of "protocol/containerIP:port/hostIP:port" +// containerIP and hostIP can be in dotted decimal ("192.0.2.1") or IPv6 ("2001:db8::68") form. +// Zoned addresses ("169.254.0.23%eth0" or "fe80::1ff:fe23:4567:890a%eth0") are not supported. +// If string s is incorrectly formatted or the IP addresses or ports cannot be parsed, FromString +// returns an error. func (p *PortBinding) FromString(s string) error { ps := strings.Split(s, "/") if len(ps) != 3 { @@ -167,21 +172,19 @@ func (p *PortBinding) FromString(s string) error { } func parseIPPort(s string) (net.IP, uint16, error) { - pp := strings.Split(s, ":") - if len(pp) != 2 { - return nil, 0, BadRequestErrorf("invalid format: %s", s) + hoststr, portstr, err := net.SplitHostPort(s) + if err != nil { + return nil, 0, err } - var ip net.IP - if pp[0] != "" { - if ip = net.ParseIP(pp[0]); ip == nil { - return nil, 0, BadRequestErrorf("invalid ip: %s", pp[0]) - } + ip := net.ParseIP(hoststr) + if ip == nil { + return nil, 0, BadRequestErrorf("invalid ip: %s", hoststr) } - port, err := strconv.ParseUint(pp[1], 10, 16) + port, err := strconv.ParseUint(portstr, 10, 16) if err != nil { - return nil, 0, BadRequestErrorf("invalid port: %s", pp[1]) + return nil, 0, BadRequestErrorf("invalid port: %s", portstr) } return ip, uint16(port), nil diff --git a/vendor/github.com/docker/libnetwork/vendor.conf b/vendor/github.com/docker/libnetwork/vendor.conf index 9480699b87cd0..5237c5588a522 100644 --- a/vendor/github.com/docker/libnetwork/vendor.conf +++ b/vendor/github.com/docker/libnetwork/vendor.conf @@ -1,27 +1,28 @@ github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 -github.com/Microsoft/go-winio v0.4.5 -github.com/Microsoft/hcsshim v0.6.5 +github.com/Microsoft/go-winio v0.4.7 +github.com/Microsoft/hcsshim v0.6.11 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 github.com/codegangsta/cli a65b733b303f0055f8d324d805f393cd3e7a7904 -github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e -github.com/containerd/continuity 22694c680ee48fb8f50015b44618517e2bde77e8 +github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925 +github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b github.com/coreos/etcd v3.2.1 github.com/coreos/go-semver v0.2.0 -github.com/coreos/go-systemd v4 +github.com/coreos/go-systemd v17 +github.com/coreos/pkg fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8 github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d -github.com/docker/docker a3efe9722f34af5cf4443fe3a5c4e4e3e0457b54 -github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d +github.com/docker/docker 162ba6016def672690ee4a1f3978368853a1e149 +github.com/docker/go-connections 7beb39f0b969b075d1325fecb092faf27fd357b6 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef github.com/godbus/dbus v4.0.0 -github.com/gogo/protobuf v0.4 -github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 +github.com/gogo/protobuf v1.0.0 +github.com/golang/protobuf v1.1.0 github.com/gorilla/context v1.1 github.com/gorilla/mux v1.1 github.com/hashicorp/consul v0.5.2 @@ -29,27 +30,37 @@ github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e github.com/hashicorp/memberlist 3d8438da9589e7b608a83ffac1ef8211486bcb7c github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372 -github.com/hashicorp/go-sockaddr acd314c5781ea706c710d9ea70069fd2e110d61d +github.com/hashicorp/go-sockaddr 6d291a969b86c4b633730bfc6b8b9d64c3aafed9 github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 github.com/mattn/go-shellwords v1.0.3 -github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 +github.com/miekg/dns v1.0.7 github.com/mrunalp/fileutils ed869b029674c0e9ce4c0dfa781405c2d9946d08 -github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb -github.com/opencontainers/image-spec 372ad780f63454fbbbbcc7cf80e5b90245c13e13 -github.com/opencontainers/runc 0351df1c5a66838d0c392b4ac4cf9450de844e2d -github.com/opencontainers/runtime-spec v1.0.0 -github.com/opencontainers/selinux v1.0.0-rc1 +github.com/opencontainers/go-digest v1.0.0-rc1 +github.com/opencontainers/image-spec v1.0.1 +github.com/opencontainers/runc 69663f0bd4b60df09991c08812a60108003fa340 +github.com/opencontainers/runtime-spec v1.0.1 +github.com/opencontainers/selinux b29023b86e4a69d1b46b7e7b4e2b6fda03f0b9cd github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 github.com/sirupsen/logrus v1.0.3 github.com/stretchr/testify dab07ac62d4905d3e48d17dc549c684ac3b7c15a -github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16 +github.com/syndtr/gocapability 33e07d32887e1e06b7c025f27ce52f62c7990bc0 github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 -golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca -golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac -golang.org/x/sys 07c182904dbd53199946ba614a412c61d3c548f5 +golang.org/x/crypto 1a580b3eff7814fc9b40602fd35256c63b50f491 +golang.org/x/net 0ed95abb35c445290478a5348a7b38bb154135fd +golang.org/x/sys 37707fdb30a5b38865cfb95e5aab41707daec7fd golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5 github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 github.com/ishidawataru/sctp 07191f837fedd2f13d1ec7b5f885f0f3ec54b1cb + +github.com/davecgh/go-spew 8991bc29aa16c548c550c7ff78260e27b9ab7c73 +github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 +github.com/cyphar/filepath-securejoin v0.2.1 +github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55 +github.com/hashicorp/go-immutable-radix 7f3cd4390caab3250a57f30efdb2a65dd7649ecf +github.com/hashicorp/golang-lru 0fb14efe8c47ae851c0034ed7a448854d3d34cf3 +github.com/hashicorp/go-cleanhttp d5fe4b57a186c716b0e00b8c301cbd9b4182694d +github.com/hashicorp/go-rootcerts 6bb64b370b90e7ef1fa532be9e591a81c3493e00 +github.com/mitchellh/go-homedir 3864e76763d94a6df2f9960b16a20a33da9f9a66 diff --git a/vendor/github.com/hashicorp/go-sockaddr/ifaddr.go b/vendor/github.com/hashicorp/go-sockaddr/ifaddr.go index 3e4ff9fca433b..0811b27599056 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/ifaddr.go +++ b/vendor/github.com/hashicorp/go-sockaddr/ifaddr.go @@ -1,5 +1,7 @@ package sockaddr +import "strings" + // ifAddrAttrMap is a map of the IfAddr type-specific attributes. var ifAddrAttrMap map[AttrName]func(IfAddr) string var ifAddrAttrs []AttrName @@ -30,6 +32,53 @@ func GetPrivateIP() (string, error) { return ip.NetIP().String(), nil } +// GetPrivateIPs returns a string with all IP addresses that are part of RFC +// 6890 (regardless of whether or not there is a default route, unlike +// GetPublicIP). If the system can't find any RFC 6890 IP addresses, an empty +// string will be returned instead. This function is the `eval` equivalent of: +// +// ``` +// $ sockaddr eval -r '{{GetAllInterfaces | include "RFC" "6890" | join "address" " "}}' +/// ``` +func GetPrivateIPs() (string, error) { + ifAddrs, err := GetAllInterfaces() + if err != nil { + return "", err + } else if len(ifAddrs) < 1 { + return "", nil + } + + ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP) + if len(ifAddrs) == 0 { + return "", nil + } + + OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs) + + ifAddrs, _, err = IfByRFC("6890", ifAddrs) + if err != nil { + return "", err + } else if len(ifAddrs) == 0 { + return "", nil + } + + _, ifAddrs, err = IfByRFC(ForwardingBlacklistRFC, ifAddrs) + if err != nil { + return "", err + } else if len(ifAddrs) == 0 { + return "", nil + } + + ips := make([]string, 0, len(ifAddrs)) + for _, ifAddr := range ifAddrs { + ip := *ToIPAddr(ifAddr.SockAddr) + s := ip.NetIP().String() + ips = append(ips, s) + } + + return strings.Join(ips, " "), nil +} + // GetPublicIP returns a string with a single IP address that is NOT part of RFC // 6890 and has a default route. If the system can't determine its IP address // or find a non RFC 6890 IP address, an empty string will be returned instead. @@ -51,6 +100,47 @@ func GetPublicIP() (string, error) { return ip.NetIP().String(), nil } +// GetPublicIPs returns a string with all IP addresses that are NOT part of RFC +// 6890 (regardless of whether or not there is a default route, unlike +// GetPublicIP). If the system can't find any non RFC 6890 IP addresses, an +// empty string will be returned instead. This function is the `eval` +// equivalent of: +// +// ``` +// $ sockaddr eval -r '{{GetAllInterfaces | exclude "RFC" "6890" | join "address" " "}}' +/// ``` +func GetPublicIPs() (string, error) { + ifAddrs, err := GetAllInterfaces() + if err != nil { + return "", err + } else if len(ifAddrs) < 1 { + return "", nil + } + + ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP) + if len(ifAddrs) == 0 { + return "", nil + } + + OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs) + + _, ifAddrs, err = IfByRFC("6890", ifAddrs) + if err != nil { + return "", err + } else if len(ifAddrs) == 0 { + return "", nil + } + + ips := make([]string, 0, len(ifAddrs)) + for _, ifAddr := range ifAddrs { + ip := *ToIPAddr(ifAddr.SockAddr) + s := ip.NetIP().String() + ips = append(ips, s) + } + + return strings.Join(ips, " "), nil +} + // GetInterfaceIP returns a string with a single IP address sorted by the size // of the network (i.e. IP addresses with a smaller netmask, larger network // size, are sorted first). This function is the `eval` equivalent of: @@ -91,6 +181,44 @@ func GetInterfaceIP(namedIfRE string) (string, error) { return IPAddrAttr(*ip, "address"), nil } +// GetInterfaceIPs returns a string with all IPs, sorted by the size of the +// network (i.e. IP addresses with a smaller netmask, larger network size, are +// sorted first), on a named interface. This function is the `eval` equivalent +// of: +// +// ``` +// $ sockaddr eval -r '{{GetAllInterfaces | include "name" <> | sort "type,size" | join "address" " "}}' +/// ``` +func GetInterfaceIPs(namedIfRE string) (string, error) { + ifAddrs, err := GetAllInterfaces() + if err != nil { + return "", err + } + + ifAddrs, _, err = IfByName(namedIfRE, ifAddrs) + if err != nil { + return "", err + } + + ifAddrs, err = SortIfBy("+type,+size", ifAddrs) + if err != nil { + return "", err + } + + if len(ifAddrs) == 0 { + return "", err + } + + ips := make([]string, 0, len(ifAddrs)) + for _, ifAddr := range ifAddrs { + ip := *ToIPAddr(ifAddr.SockAddr) + s := ip.NetIP().String() + ips = append(ips, s) + } + + return strings.Join(ips, " "), nil +} + // IfAddrAttrs returns a list of attributes supported by the IfAddr type func IfAddrAttrs() []AttrName { return ifAddrAttrs diff --git a/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go b/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go index 8233be2022bf5..2a706c34e92b8 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go +++ b/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go @@ -1,8 +1,10 @@ package sockaddr import ( + "encoding/binary" "errors" "fmt" + "math/big" "net" "regexp" "sort" @@ -10,6 +12,14 @@ import ( "strings" ) +var ( + // Centralize all regexps and regexp.Copy() where necessary. + signRE *regexp.Regexp = regexp.MustCompile(`^[\s]*[+-]`) + whitespaceRE *regexp.Regexp = regexp.MustCompile(`[\s]+`) + ifNameRE *regexp.Regexp = regexp.MustCompile(`^(?:Ethernet|Wireless LAN) adapter ([^:]+):`) + ipAddrRE *regexp.Regexp = regexp.MustCompile(`^ IPv[46] Address\. \. \. \. \. \. \. \. \. \. \. : ([^\s]+)`) +) + // IfAddrs is a slice of IfAddr type IfAddrs []IfAddr @@ -91,6 +101,40 @@ func AscIfAddress(p1Ptr, p2Ptr *IfAddr) int { return AscAddress(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } +// AscIfDefault is a sorting function to sort IfAddrs by whether or not they +// have a default route or not. Non-equal types are deferred in the sort. +// +// FIXME: This is a particularly expensive sorting operation because of the +// non-memoized calls to NewRouteInfo(). In an ideal world the routeInfo data +// once at the start of the sort and pass it along as a context or by wrapping +// the IfAddr type with this information (this would also solve the inability to +// return errors and the possibility of failing silently). Fortunately, +// N*log(N) where N = 3 is only ~6.2 invocations. Not ideal, but not worth +// optimizing today. The common case is this gets called once or twice. +// Patches welcome. +func AscIfDefault(p1Ptr, p2Ptr *IfAddr) int { + ri, err := NewRouteInfo() + if err != nil { + return sortDeferDecision + } + + defaultIfName, err := ri.GetDefaultInterfaceName() + if err != nil { + return sortDeferDecision + } + + switch { + case p1Ptr.Interface.Name == defaultIfName && p2Ptr.Interface.Name == defaultIfName: + return sortDeferDecision + case p1Ptr.Interface.Name == defaultIfName: + return sortReceiverBeforeArg + case p2Ptr.Interface.Name == defaultIfName: + return sortArgBeforeReceiver + default: + return sortDeferDecision + } +} + // AscIfName is a sorting function to sort IfAddrs by their interface names. func AscIfName(p1Ptr, p2Ptr *IfAddr) int { return strings.Compare(p1Ptr.Name, p2Ptr.Name) @@ -127,6 +171,11 @@ func DescIfAddress(p1Ptr, p2Ptr *IfAddr) int { return -1 * AscAddress(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } +// DescIfDefault is identical to AscIfDefault but reverse ordered. +func DescIfDefault(p1Ptr, p2Ptr *IfAddr) int { + return -1 * AscIfDefault(p1Ptr, p2Ptr) +} + // DescIfName is identical to AscIfName but reverse ordered. func DescIfName(p1Ptr, p2Ptr *IfAddr) int { return -1 * strings.Compare(p1Ptr.Name, p2Ptr.Name) @@ -169,7 +218,15 @@ func FilterIfByType(ifAddrs IfAddrs, type_ SockAddrType) (matchedIfs, excludedIf // IfAttr forwards the selector to IfAttr.Attr() for resolution. If there is // more than one IfAddr, only the first IfAddr is used. -func IfAttr(selectorName string, ifAddrs IfAddrs) (string, error) { +func IfAttr(selectorName string, ifAddr IfAddr) (string, error) { + attrName := AttrName(strings.ToLower(selectorName)) + attrVal, err := ifAddr.Attr(attrName) + return attrVal, err +} + +// IfAttrs forwards the selector to IfAttrs.Attr() for resolution. If there is +// more than one IfAddr, only the first IfAddr is used. +func IfAttrs(selectorName string, ifAddrs IfAddrs) (string, error) { if len(ifAddrs) == 0 { return "", nil } @@ -243,10 +300,10 @@ func GetDefaultInterfaces() (IfAddrs, error) { // the `eval` equivalent of: // // ``` -// $ sockaddr eval -r '{{GetDefaultInterfaces | include "type" "ip" | include "flags" "forwardable|up" | sort "type,size" | include "RFC" "6890" }}' +// $ sockaddr eval -r '{{GetAllInterfaces | include "type" "ip" | include "flags" "forwardable" | include "flags" "up" | sort "default,type,size" | include "RFC" "6890" }}' /// ``` func GetPrivateInterfaces() (IfAddrs, error) { - privateIfs, err := GetDefaultInterfaces() + privateIfs, err := GetAllInterfaces() if err != nil { return IfAddrs{}, err } @@ -259,15 +316,21 @@ func GetPrivateInterfaces() (IfAddrs, error) { return IfAddrs{}, nil } - privateIfs, _, err = IfByFlag("forwardable|up", privateIfs) + privateIfs, _, err = IfByFlag("forwardable", privateIfs) + if err != nil { + return IfAddrs{}, err + } + + privateIfs, _, err = IfByFlag("up", privateIfs) if err != nil { return IfAddrs{}, err } + if len(privateIfs) == 0 { return IfAddrs{}, nil } - OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(privateIfs) + OrderedIfAddrBy(AscIfDefault, AscIfType, AscIfNetworkSize).Sort(privateIfs) privateIfs, _, err = IfByRFC("6890", privateIfs) if err != nil { @@ -285,10 +348,10 @@ func GetPrivateInterfaces() (IfAddrs, error) { // function is the `eval` equivalent of: // // ``` -// $ sockaddr eval -r '{{GetDefaultInterfaces | include "type" "ip" | include "flags" "forwardable|up" | sort "type,size" | exclude "RFC" "6890" }}' +// $ sockaddr eval -r '{{GetAllInterfaces | include "type" "ip" | include "flags" "forwardable" | include "flags" "up" | sort "default,type,size" | exclude "RFC" "6890" }}' /// ``` func GetPublicInterfaces() (IfAddrs, error) { - publicIfs, err := GetDefaultInterfaces() + publicIfs, err := GetAllInterfaces() if err != nil { return IfAddrs{}, err } @@ -301,15 +364,21 @@ func GetPublicInterfaces() (IfAddrs, error) { return IfAddrs{}, nil } - publicIfs, _, err = IfByFlag("forwardable|up", publicIfs) + publicIfs, _, err = IfByFlag("forwardable", publicIfs) if err != nil { return IfAddrs{}, err } + + publicIfs, _, err = IfByFlag("up", publicIfs) + if err != nil { + return IfAddrs{}, err + } + if len(publicIfs) == 0 { return IfAddrs{}, nil } - OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(publicIfs) + OrderedIfAddrBy(AscIfDefault, AscIfType, AscIfNetworkSize).Sort(publicIfs) _, publicIfs, err = IfByRFC("6890", publicIfs) if err != nil { @@ -652,6 +721,245 @@ func IfByNetwork(selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, IfAddrs, return includedIfs, excludedIfs, nil } +// IfAddrMath will return a new IfAddr struct with a mutated value. +func IfAddrMath(operation, value string, inputIfAddr IfAddr) (IfAddr, error) { + // Regexp used to enforce the sign being a required part of the grammar for + // some values. + signRe := signRE.Copy() + + switch strings.ToLower(operation) { + case "address": + // "address" operates on the IP address and is allowed to overflow or + // underflow networks, however it will wrap along the underlying address's + // underlying type. + + if !signRe.MatchString(value) { + return IfAddr{}, fmt.Errorf("sign (+/-) is required for operation %q", operation) + } + + switch sockType := inputIfAddr.SockAddr.Type(); sockType { + case TypeIPv4: + // 33 == Accept any uint32 value + // TODO(seanc@): Add the ability to parse hex + i, err := strconv.ParseInt(value, 10, 33) + if err != nil { + return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) + } + + ipv4 := *ToIPv4Addr(inputIfAddr.SockAddr) + ipv4Uint32 := uint32(ipv4.Address) + ipv4Uint32 += uint32(i) + return IfAddr{ + SockAddr: IPv4Addr{ + Address: IPv4Address(ipv4Uint32), + Mask: ipv4.Mask, + }, + Interface: inputIfAddr.Interface, + }, nil + case TypeIPv6: + // 64 == Accept any int32 value + // TODO(seanc@): Add the ability to parse hex. Also parse a bignum int. + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) + } + + ipv6 := *ToIPv6Addr(inputIfAddr.SockAddr) + ipv6BigIntA := new(big.Int) + ipv6BigIntA.Set(ipv6.Address) + ipv6BigIntB := big.NewInt(i) + + ipv6Addr := ipv6BigIntA.Add(ipv6BigIntA, ipv6BigIntB) + ipv6Addr.And(ipv6Addr, ipv6HostMask) + + return IfAddr{ + SockAddr: IPv6Addr{ + Address: IPv6Address(ipv6Addr), + Mask: ipv6.Mask, + }, + Interface: inputIfAddr.Interface, + }, nil + default: + return IfAddr{}, fmt.Errorf("unsupported type for operation %q: %T", operation, sockType) + } + case "network": + // "network" operates on the network address. Positive values start at the + // network address and negative values wrap at the network address, which + // means a "-1" value on a network will be the broadcast address after + // wrapping is applied. + + if !signRe.MatchString(value) { + return IfAddr{}, fmt.Errorf("sign (+/-) is required for operation %q", operation) + } + + switch sockType := inputIfAddr.SockAddr.Type(); sockType { + case TypeIPv4: + // 33 == Accept any uint32 value + // TODO(seanc@): Add the ability to parse hex + i, err := strconv.ParseInt(value, 10, 33) + if err != nil { + return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) + } + + ipv4 := *ToIPv4Addr(inputIfAddr.SockAddr) + ipv4Uint32 := uint32(ipv4.NetworkAddress()) + + // Wrap along network mask boundaries. EZ-mode wrapping made possible by + // use of int64 vs a uint. + var wrappedMask int64 + if i >= 0 { + wrappedMask = i + } else { + wrappedMask = 1 + i + int64(^uint32(ipv4.Mask)) + } + + ipv4Uint32 = ipv4Uint32 + (uint32(wrappedMask) &^ uint32(ipv4.Mask)) + + return IfAddr{ + SockAddr: IPv4Addr{ + Address: IPv4Address(ipv4Uint32), + Mask: ipv4.Mask, + }, + Interface: inputIfAddr.Interface, + }, nil + case TypeIPv6: + // 64 == Accept any int32 value + // TODO(seanc@): Add the ability to parse hex. Also parse a bignum int. + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) + } + + ipv6 := *ToIPv6Addr(inputIfAddr.SockAddr) + ipv6BigInt := new(big.Int) + ipv6BigInt.Set(ipv6.NetworkAddress()) + + mask := new(big.Int) + mask.Set(ipv6.Mask) + if i > 0 { + wrappedMask := new(big.Int) + wrappedMask.SetInt64(i) + + wrappedMask.AndNot(wrappedMask, mask) + ipv6BigInt.Add(ipv6BigInt, wrappedMask) + } else { + // Mask off any bits that exceed the network size. Subtract the + // wrappedMask from the last usable - 1 + wrappedMask := new(big.Int) + wrappedMask.SetInt64(-1 * i) + wrappedMask.Sub(wrappedMask, big.NewInt(1)) + + wrappedMask.AndNot(wrappedMask, mask) + + lastUsable := new(big.Int) + lastUsable.Set(ipv6.LastUsable().(IPv6Addr).Address) + + ipv6BigInt = lastUsable.Sub(lastUsable, wrappedMask) + } + + return IfAddr{ + SockAddr: IPv6Addr{ + Address: IPv6Address(ipv6BigInt), + Mask: ipv6.Mask, + }, + Interface: inputIfAddr.Interface, + }, nil + default: + return IfAddr{}, fmt.Errorf("unsupported type for operation %q: %T", operation, sockType) + } + case "mask": + // "mask" operates on the IP address and returns the IP address on + // which the given integer mask has been applied. If the applied mask + // corresponds to a larger network than the mask of the IP address, + // the latter will be replaced by the former. + switch sockType := inputIfAddr.SockAddr.Type(); sockType { + case TypeIPv4: + i, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) + } + + if i > 32 { + return IfAddr{}, fmt.Errorf("parameter for operation %q on ipv4 addresses must be between 0 and 32", operation) + } + + ipv4 := *ToIPv4Addr(inputIfAddr.SockAddr) + + ipv4Mask := net.CIDRMask(int(i), 32) + ipv4MaskUint32 := binary.BigEndian.Uint32(ipv4Mask) + + maskedIpv4 := ipv4.NetIP().Mask(ipv4Mask) + maskedIpv4Uint32 := binary.BigEndian.Uint32(maskedIpv4) + + maskedIpv4MaskUint32 := uint32(ipv4.Mask) + + if ipv4MaskUint32 < maskedIpv4MaskUint32 { + maskedIpv4MaskUint32 = ipv4MaskUint32 + } + + return IfAddr{ + SockAddr: IPv4Addr{ + Address: IPv4Address(maskedIpv4Uint32), + Mask: IPv4Mask(maskedIpv4MaskUint32), + }, + Interface: inputIfAddr.Interface, + }, nil + case TypeIPv6: + i, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) + } + + if i > 128 { + return IfAddr{}, fmt.Errorf("parameter for operation %q on ipv6 addresses must be between 0 and 64", operation) + } + + ipv6 := *ToIPv6Addr(inputIfAddr.SockAddr) + + ipv6Mask := net.CIDRMask(int(i), 128) + ipv6MaskBigInt := new(big.Int) + ipv6MaskBigInt.SetBytes(ipv6Mask) + + maskedIpv6 := ipv6.NetIP().Mask(ipv6Mask) + maskedIpv6BigInt := new(big.Int) + maskedIpv6BigInt.SetBytes(maskedIpv6) + + maskedIpv6MaskBigInt := new(big.Int) + maskedIpv6MaskBigInt.Set(ipv6.Mask) + + if ipv6MaskBigInt.Cmp(maskedIpv6MaskBigInt) == -1 { + maskedIpv6MaskBigInt = ipv6MaskBigInt + } + + return IfAddr{ + SockAddr: IPv6Addr{ + Address: IPv6Address(maskedIpv6BigInt), + Mask: IPv6Mask(maskedIpv6MaskBigInt), + }, + Interface: inputIfAddr.Interface, + }, nil + default: + return IfAddr{}, fmt.Errorf("unsupported type for operation %q: %T", operation, sockType) + } + default: + return IfAddr{}, fmt.Errorf("unsupported math operation: %q", operation) + } +} + +// IfAddrsMath will apply an IfAddrMath operation each IfAddr struct. Any +// failure will result in zero results. +func IfAddrsMath(operation, value string, inputIfAddrs IfAddrs) (IfAddrs, error) { + outputAddrs := make(IfAddrs, 0, len(inputIfAddrs)) + for _, ifAddr := range inputIfAddrs { + result, err := IfAddrMath(operation, value, ifAddr) + if err != nil { + return IfAddrs{}, fmt.Errorf("unable to perform an IPMath operation on %s: %v", ifAddr, err) + } + outputAddrs = append(outputAddrs, result) + } + return outputAddrs, nil +} + // IncludeIfs returns an IfAddrs based on the passed in selector. func IncludeIfs(selectorName, selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) { var includedIfs IfAddrs @@ -736,6 +1044,10 @@ func SortIfBy(selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) { sortFuncs[i] = AscIfAddress case "-address": sortFuncs[i] = DescIfAddress + case "+default", "default": + sortFuncs[i] = AscIfDefault + case "-default": + sortFuncs[i] = DescIfDefault case "+name", "name": // The "name" selector returns an array of IfAddrs // ordered by the interface name. @@ -886,7 +1198,7 @@ func parseDefaultIfNameFromRoute(routeOut string) (string, error) { // Linux. func parseDefaultIfNameFromIPCmd(routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") - re := regexp.MustCompile(`[\s]+`) + re := whitespaceRE.Copy() for _, line := range lines { kvs := re.Split(line, -1) if len(kvs) < 5 { @@ -929,7 +1241,7 @@ func parseDefaultIfNameWindows(routeOut, ipconfigOut string) (string, error) { // support added. func parseDefaultIPAddrWindowsRoute(routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") - re := regexp.MustCompile(`[\s]+`) + re := whitespaceRE.Copy() for _, line := range lines { kvs := re.Split(strings.TrimSpace(line), -1) if len(kvs) < 3 { @@ -949,17 +1261,17 @@ func parseDefaultIPAddrWindowsRoute(routeOut string) (string, error) { // interface name forwarding traffic to the default gateway. func parseDefaultIfNameWindowsIPConfig(defaultIPAddr, routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") - ifNameRE := regexp.MustCompile(`^Ethernet adapter ([^\s:]+):`) - ipAddrRE := regexp.MustCompile(`^ IPv[46] Address\. \. \. \. \. \. \. \. \. \. \. : ([^\s]+)`) + ifNameRe := ifNameRE.Copy() + ipAddrRe := ipAddrRE.Copy() var ifName string for _, line := range lines { - switch ifNameMatches := ifNameRE.FindStringSubmatch(line); { + switch ifNameMatches := ifNameRe.FindStringSubmatch(line); { case len(ifNameMatches) > 1: ifName = ifNameMatches[1] continue } - switch ipAddrMatches := ipAddrRE.FindStringSubmatch(line); { + switch ipAddrMatches := ipAddrRe.FindStringSubmatch(line); { case len(ipAddrMatches) > 1 && ipAddrMatches[1] == defaultIPAddr: return ifName, nil } diff --git a/vendor/github.com/hashicorp/go-sockaddr/ipv4addr.go b/vendor/github.com/hashicorp/go-sockaddr/ipv4addr.go index 9f2616a69f627..4d395dc954b65 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/ipv4addr.go +++ b/vendor/github.com/hashicorp/go-sockaddr/ipv4addr.go @@ -58,7 +58,8 @@ func NewIPv4Addr(ipv4Str string) (IPv4Addr, error) { // Strip off any bogus hex-encoded netmasks that will be mis-parsed by Go. In // particular, clients with the Barracuda VPN client will see something like: // `192.168.3.51/00ffffff` as their IP address. - if match := trailingHexNetmaskRE.FindStringIndex(ipv4Str); match != nil { + trailingHexNetmaskRe := trailingHexNetmaskRE.Copy() + if match := trailingHexNetmaskRe.FindStringIndex(ipv4Str); match != nil { ipv4Str = ipv4Str[:match[0]] } diff --git a/vendor/github.com/hashicorp/go-sockaddr/rfc.go b/vendor/github.com/hashicorp/go-sockaddr/rfc.go index fd9be940b1130..02e188f6fe647 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/rfc.go +++ b/vendor/github.com/hashicorp/go-sockaddr/rfc.go @@ -3,6 +3,7 @@ package sockaddr // ForwardingBlacklist is a faux RFC that includes a list of non-forwardable IP // blocks. const ForwardingBlacklist = 4294967295 +const ForwardingBlacklistRFC = "4294967295" // IsRFC tests to see if an SockAddr matches the specified RFC func IsRFC(rfcNum uint, sa SockAddr) bool { diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go b/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go index b33e4c0d0848e..c2ec91eaf456e 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go @@ -5,10 +5,6 @@ import ( "os/exec" ) -var cmds map[string][]string = map[string][]string{ - "ip": {"/sbin/ip", "route"}, -} - type routeInfo struct { cmds map[string][]string } @@ -16,15 +12,22 @@ type routeInfo struct { // NewRouteInfo returns a Linux-specific implementation of the RouteInfo // interface. func NewRouteInfo() (routeInfo, error) { + // CoreOS Container Linux moved ip to /usr/bin/ip, so look it up on + // $PATH and fallback to /sbin/ip on error. + path, _ := exec.LookPath("ip") + if path == "" { + path = "/sbin/ip" + } + return routeInfo{ - cmds: cmds, + cmds: map[string][]string{"ip": {path, "route"}}, }, nil } // GetDefaultInterfaceName returns the interface name attached to the default // route on the default interface. func (ri routeInfo) GetDefaultInterfaceName() (string, error) { - out, err := exec.Command(cmds["ip"][0], cmds["ip"][1:]...).Output() + out, err := exec.Command(ri.cmds["ip"][0], ri.cmds["ip"][1:]...).Output() if err != nil { return "", err } diff --git a/vendor/github.com/hashicorp/go-sockaddr/sockaddr.go b/vendor/github.com/hashicorp/go-sockaddr/sockaddr.go index 51389ebe9a690..826c91c2e3d06 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/sockaddr.go +++ b/vendor/github.com/hashicorp/go-sockaddr/sockaddr.go @@ -1,6 +1,7 @@ package sockaddr import ( + "encoding/json" "fmt" "strings" ) @@ -176,3 +177,30 @@ func sockAddrInit() { func SockAddrAttrs() []AttrName { return sockAddrAttrs } + +// Although this is pretty trivial to do in a program, having the logic here is +// useful all around. Note that this marshals into a *string* -- the underlying +// string representation of the sockaddr. If you then unmarshal into this type +// in Go, all will work as expected, but externally you can take what comes out +// and use the string value directly. +type SockAddrMarshaler struct { + SockAddr +} + +func (s *SockAddrMarshaler) MarshalJSON() ([]byte, error) { + return json.Marshal(s.SockAddr.String()) +} + +func (s *SockAddrMarshaler) UnmarshalJSON(in []byte) error { + var str string + err := json.Unmarshal(in, &str) + if err != nil { + return err + } + sa, err := NewSockAddr(str) + if err != nil { + return err + } + s.SockAddr = sa + return nil +}