Vendoring memberlist tag 0.1.0

Signed-off-by: Flavio Crisciani <flavio.crisciani@docker.com>
This commit is contained in:
Flavio Crisciani 2017-04-27 13:32:34 -07:00
parent 1624c61ef2
commit 541cafdb30
37 changed files with 6187 additions and 551 deletions

View file

@ -25,7 +25,9 @@ github.com/gorilla/mux 8096f47503459bcc74d1f4c487b7e6e42e5746b5
github.com/hashicorp/consul/api 954aec66231b79c161a4122b023fbcad13047f79
github.com/hashicorp/go-msgpack/codec 71c2886f5a673a35f909803f38ece5810165097b
github.com/hashicorp/go-multierror 2167c8ec40776024589f483a6b836489e47e1049
github.com/hashicorp/memberlist 88ac4de0d1a0ca6def284b571342db3b777a4c37
github.com/hashicorp/memberlist v0.1.0
github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372
github.com/hashicorp/go-sockaddr acd314c5781ea706c710d9ea70069fd2e110d61d
github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870
github.com/mattn/go-shellwords 525bedee691b5a8df547cb5cf9f86b7fb1883e24
github.com/miekg/dns d27455715200c7d3e321a1e5cadb27c9ee0b0f02

View file

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View file

@ -0,0 +1,118 @@
# go-sockaddr
## `sockaddr` Library
Socket address convenience functions for Go. `go-sockaddr` is a convenience
library that makes doing the right thing with IP addresses easy. `go-sockaddr`
is loosely modeled after the UNIX `sockaddr_t` and creates a union of the family
of `sockaddr_t` types (see below for an ascii diagram). Library documentation
is available
at
[https://godoc.org/github.com/hashicorp/go-sockaddr](https://godoc.org/github.com/hashicorp/go-sockaddr).
The primary intent of the library was to make it possible to define heuristics
for selecting the correct IP addresses when a configuration is evaluated at
runtime. See
the
[docs](https://godoc.org/github.com/hashicorp/go-sockaddr),
[`template` package](https://godoc.org/github.com/hashicorp/go-sockaddr/template),
tests,
and
[CLI utility](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr)
for details and hints as to how to use this library.
For example, with this library it is possible to find an IP address that:
* is attached to a default route
([`GetDefaultInterfaces()`](https://godoc.org/github.com/hashicorp/go-sockaddr#GetDefaultInterfaces))
* is contained within a CIDR block ([`IfByNetwork()`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByNetwork))
* is an RFC1918 address
([`IfByRFC("1918")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC))
* is ordered
([`OrderedIfAddrBy(args)`](https://godoc.org/github.com/hashicorp/go-sockaddr#OrderedIfAddrBy) where
`args` includes, but is not limited
to,
[`AscIfType`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscIfType),
[`AscNetworkSize`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscNetworkSize))
* excludes all IPv6 addresses
([`IfByType("^(IPv4)$")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByType))
* is larger than a `/32`
([`IfByMaskSize(32)`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByMaskSize))
* is not on a `down` interface
([`ExcludeIfs("flags", "down")`](https://godoc.org/github.com/hashicorp/go-sockaddr#ExcludeIfs))
* preferences an IPv6 address over an IPv4 address
([`SortIfByType()`](https://godoc.org/github.com/hashicorp/go-sockaddr#SortIfByType) +
[`ReverseIfAddrs()`](https://godoc.org/github.com/hashicorp/go-sockaddr#ReverseIfAddrs)); and
* excludes any IP in RFC6890 address
([`IfByRFC("6890")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC))
Or any combination or variation therein.
There are also a few simple helper functions such as `GetPublicIP` and
`GetPrivateIP` which both return strings and select the first public or private
IP address on the default interface, respectively. Similarly, there is also a
helper function called `GetInterfaceIP` which returns the first usable IP
address on the named interface.
## `sockaddr` CLI
Given the possible complexity of the `sockaddr` library, there is a CLI utility
that accompanies the library, also
called
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr).
The
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr)
utility exposes nearly all of the functionality of the library and can be used
either as an administrative tool or testing tool. To install
the
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr),
run:
```text
$ go get -u github.com/hashicorp/go-sockaddr/cmd/sockaddr
```
If you're familiar with UNIX's `sockaddr` struct's, the following diagram
mapping the C `sockaddr` (top) to `go-sockaddr` structs (bottom) and
interfaces will be helpful:
```
+-------------------------------------------------------+
| |
| sockaddr |
| SockAddr |
| |
| +--------------+ +----------------------------------+ |
| | sockaddr_un | | | |
| | SockAddrUnix | | sockaddr_in{,6} | |
| +--------------+ | IPAddr | |
| | | |
| | +-------------+ +--------------+ | |
| | | sockaddr_in | | sockaddr_in6 | | |
| | | IPv4Addr | | IPv6Addr | | |
| | +-------------+ +--------------+ | |
| | | |
| +----------------------------------+ |
| |
+-------------------------------------------------------+
```
## Inspiration and Design
There were many subtle inspirations that led to this design, but the most direct
inspiration for the filtering syntax was
OpenBSD's
[`pf.conf(5)`](https://www.freebsd.org/cgi/man.cgi?query=pf.conf&apropos=0&sektion=0&arch=default&format=html#PARAMETERS) firewall
syntax that lets you select the first IP address on a given named interface.
The original problem stemmed from:
* needing to create immutable images using [Packer](https://www.packer.io) that
ran the [Consul](https://www.consul.io) process (Consul can only use one IP
address at a time);
* images that may or may not have multiple interfaces or IP addresses at
runtime; and
* we didn't want to rely on configuration management to render out the correct
IP address if the VM image was being used in an auto-scaling group.
Instead we needed some way to codify a heuristic that would correctly select the
right IP address but the input parameters were not known when the image was
created.

View file

@ -0,0 +1,5 @@
/*
Package sockaddr is a Go implementation of the UNIX socket family data types and
related helper functions.
*/
package sockaddr

View file

@ -0,0 +1,126 @@
package sockaddr
// ifAddrAttrMap is a map of the IfAddr type-specific attributes.
var ifAddrAttrMap map[AttrName]func(IfAddr) string
var ifAddrAttrs []AttrName
func init() {
ifAddrAttrInit()
}
// GetPrivateIP returns a string with a single IP address that is part of RFC
// 6890 and has a default route. If the system can't determine its IP address
// or find an RFC 6890 IP address, an empty string will be returned instead.
// This function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetPrivateInterfaces | attr "address"}}'
/// ```
func GetPrivateIP() (string, error) {
privateIfs, err := GetPrivateInterfaces()
if err != nil {
return "", err
}
if len(privateIfs) < 1 {
return "", nil
}
ifAddr := privateIfs[0]
ip := *ToIPAddr(ifAddr.SockAddr)
return ip.NetIP().String(), 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.
// This function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetPublicInterfaces | attr "address"}}'
/// ```
func GetPublicIP() (string, error) {
publicIfs, err := GetPublicInterfaces()
if err != nil {
return "", err
} else if len(publicIfs) < 1 {
return "", nil
}
ifAddr := publicIfs[0]
ip := *ToIPAddr(ifAddr.SockAddr)
return ip.NetIP().String(), 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:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | include "name" <<ARG>> | sort "type,size" | include "flag" "forwardable" | attr "address" }}'
/// ```
func GetInterfaceIP(namedIfRE string) (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
}
ifAddrs, _, err = IfByName(namedIfRE, ifAddrs)
if err != nil {
return "", err
}
ifAddrs, _, err = IfByFlag("forwardable", ifAddrs)
if err != nil {
return "", err
}
ifAddrs, err = SortIfBy("+type,+size", ifAddrs)
if err != nil {
return "", err
}
if len(ifAddrs) == 0 {
return "", err
}
ip := ToIPAddr(ifAddrs[0].SockAddr)
if ip == nil {
return "", err
}
return IPAddrAttr(*ip, "address"), nil
}
// IfAddrAttrs returns a list of attributes supported by the IfAddr type
func IfAddrAttrs() []AttrName {
return ifAddrAttrs
}
// IfAddrAttr returns a string representation of an attribute for the given
// IfAddr.
func IfAddrAttr(ifAddr IfAddr, attrName AttrName) string {
fn, found := ifAddrAttrMap[attrName]
if !found {
return ""
}
return fn(ifAddr)
}
// ifAddrAttrInit is called once at init()
func ifAddrAttrInit() {
// Sorted for human readability
ifAddrAttrs = []AttrName{
"flags",
"name",
}
ifAddrAttrMap = map[AttrName]func(ifAddr IfAddr) string{
"flags": func(ifAddr IfAddr) string {
return ifAddr.Interface.Flags.String()
},
"name": func(ifAddr IfAddr) string {
return ifAddr.Interface.Name
},
}
}

View file

@ -0,0 +1,969 @@
package sockaddr
import (
"errors"
"fmt"
"net"
"regexp"
"sort"
"strconv"
"strings"
)
// IfAddrs is a slice of IfAddr
type IfAddrs []IfAddr
func (ifs IfAddrs) Len() int { return len(ifs) }
// CmpIfFunc is the function signature that must be met to be used in the
// OrderedIfAddrBy multiIfAddrSorter
type CmpIfAddrFunc func(p1, p2 *IfAddr) int
// multiIfAddrSorter implements the Sort interface, sorting the IfAddrs within.
type multiIfAddrSorter struct {
ifAddrs IfAddrs
cmp []CmpIfAddrFunc
}
// Sort sorts the argument slice according to the Cmp functions passed to
// OrderedIfAddrBy.
func (ms *multiIfAddrSorter) Sort(ifAddrs IfAddrs) {
ms.ifAddrs = ifAddrs
sort.Sort(ms)
}
// OrderedIfAddrBy sorts SockAddr by the list of sort function pointers.
func OrderedIfAddrBy(cmpFuncs ...CmpIfAddrFunc) *multiIfAddrSorter {
return &multiIfAddrSorter{
cmp: cmpFuncs,
}
}
// Len is part of sort.Interface.
func (ms *multiIfAddrSorter) Len() int {
return len(ms.ifAddrs)
}
// Less is part of sort.Interface. It is implemented by looping along the Cmp()
// functions until it finds a comparison that is either less than or greater
// than. A return value of 0 defers sorting to the next function in the
// multisorter (which means the results of sorting may leave the resutls in a
// non-deterministic order).
func (ms *multiIfAddrSorter) Less(i, j int) bool {
p, q := &ms.ifAddrs[i], &ms.ifAddrs[j]
// Try all but the last comparison.
var k int
for k = 0; k < len(ms.cmp)-1; k++ {
cmp := ms.cmp[k]
x := cmp(p, q)
switch x {
case -1:
// p < q, so we have a decision.
return true
case 1:
// p > q, so we have a decision.
return false
}
// p == q; try the next comparison.
}
// All comparisons to here said "equal", so just return whatever the
// final comparison reports.
switch ms.cmp[k](p, q) {
case -1:
return true
case 1:
return false
default:
// Still a tie! Now what?
return false
panic("undefined sort order for remaining items in the list")
}
}
// Swap is part of sort.Interface.
func (ms *multiIfAddrSorter) Swap(i, j int) {
ms.ifAddrs[i], ms.ifAddrs[j] = ms.ifAddrs[j], ms.ifAddrs[i]
}
// AscIfAddress is a sorting function to sort IfAddrs by their respective
// address type. Non-equal types are deferred in the sort.
func AscIfAddress(p1Ptr, p2Ptr *IfAddr) int {
return AscAddress(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// 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)
}
// AscIfNetworkSize is a sorting function to sort IfAddrs by their respective
// network mask size.
func AscIfNetworkSize(p1Ptr, p2Ptr *IfAddr) int {
return AscNetworkSize(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// AscIfPort is a sorting function to sort IfAddrs by their respective
// port type. Non-equal types are deferred in the sort.
func AscIfPort(p1Ptr, p2Ptr *IfAddr) int {
return AscPort(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// AscIfPrivate is a sorting function to sort IfAddrs by "private" values before
// "public" values. Both IPv4 and IPv6 are compared against RFC6890 (RFC6890
// includes, and is not limited to, RFC1918 and RFC6598 for IPv4, and IPv6
// includes RFC4193).
func AscIfPrivate(p1Ptr, p2Ptr *IfAddr) int {
return AscPrivate(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// AscIfType is a sorting function to sort IfAddrs by their respective address
// type. Non-equal types are deferred in the sort.
func AscIfType(p1Ptr, p2Ptr *IfAddr) int {
return AscType(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// DescIfAddress is identical to AscIfAddress but reverse ordered.
func DescIfAddress(p1Ptr, p2Ptr *IfAddr) int {
return -1 * AscAddress(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// DescIfName is identical to AscIfName but reverse ordered.
func DescIfName(p1Ptr, p2Ptr *IfAddr) int {
return -1 * strings.Compare(p1Ptr.Name, p2Ptr.Name)
}
// DescIfNetworkSize is identical to AscIfNetworkSize but reverse ordered.
func DescIfNetworkSize(p1Ptr, p2Ptr *IfAddr) int {
return -1 * AscNetworkSize(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// DescIfPort is identical to AscIfPort but reverse ordered.
func DescIfPort(p1Ptr, p2Ptr *IfAddr) int {
return -1 * AscPort(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// DescIfPrivate is identical to AscIfPrivate but reverse ordered.
func DescIfPrivate(p1Ptr, p2Ptr *IfAddr) int {
return -1 * AscPrivate(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// DescIfType is identical to AscIfType but reverse ordered.
func DescIfType(p1Ptr, p2Ptr *IfAddr) int {
return -1 * AscType(&p1Ptr.SockAddr, &p2Ptr.SockAddr)
}
// FilterIfByType filters IfAddrs and returns a list of the matching type
func FilterIfByType(ifAddrs IfAddrs, type_ SockAddrType) (matchedIfs, excludedIfs IfAddrs) {
excludedIfs = make(IfAddrs, 0, len(ifAddrs))
matchedIfs = make(IfAddrs, 0, len(ifAddrs))
for _, ifAddr := range ifAddrs {
if ifAddr.SockAddr.Type()&type_ != 0 {
matchedIfs = append(matchedIfs, ifAddr)
} else {
excludedIfs = append(excludedIfs, ifAddr)
}
}
return matchedIfs, excludedIfs
}
// 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) {
if len(ifAddrs) == 0 {
return "", nil
}
attrName := AttrName(strings.ToLower(selectorName))
attrVal, err := ifAddrs[0].Attr(attrName)
return attrVal, err
}
// GetAllInterfaces iterates over all available network interfaces and finds all
// available IP addresses on each interface and converts them to
// sockaddr.IPAddrs, and returning the result as an array of IfAddr.
func GetAllInterfaces() (IfAddrs, error) {
ifs, err := net.Interfaces()
if err != nil {
return nil, err
}
ifAddrs := make(IfAddrs, 0, len(ifs))
for _, intf := range ifs {
addrs, err := intf.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
var ipAddr IPAddr
ipAddr, err = NewIPAddr(addr.String())
if err != nil {
return IfAddrs{}, fmt.Errorf("unable to create an IP address from %q", addr.String())
}
ifAddr := IfAddr{
SockAddr: ipAddr,
Interface: intf,
}
ifAddrs = append(ifAddrs, ifAddr)
}
}
return ifAddrs, nil
}
// GetDefaultInterfaces returns IfAddrs of the addresses attached to the default
// route.
func GetDefaultInterfaces() (IfAddrs, error) {
ri, err := NewRouteInfo()
if err != nil {
return nil, err
}
defaultIfName, err := ri.GetDefaultInterfaceName()
if err != nil {
return nil, err
}
var defaultIfs, ifAddrs IfAddrs
ifAddrs, err = GetAllInterfaces()
for _, ifAddr := range ifAddrs {
if ifAddr.Name == defaultIfName {
defaultIfs = append(defaultIfs, ifAddr)
}
}
return defaultIfs, nil
}
// GetPrivateInterfaces returns an IfAddrs that are part of RFC 6890 and have a
// default route. If the system can't determine its IP address or find an RFC
// 6890 IP address, an empty IfAddrs will be returned instead. This function is
// the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetDefaultInterfaces | include "type" "ip" | include "flags" "forwardable|up" | sort "type,size" | include "RFC" "6890" }}'
/// ```
func GetPrivateInterfaces() (IfAddrs, error) {
privateIfs, err := GetDefaultInterfaces()
if err != nil {
return IfAddrs{}, err
}
if len(privateIfs) == 0 {
return IfAddrs{}, nil
}
privateIfs, _ = FilterIfByType(privateIfs, TypeIP)
if len(privateIfs) == 0 {
return IfAddrs{}, nil
}
privateIfs, _, err = IfByFlag("forwardable|up", privateIfs)
if err != nil {
return IfAddrs{}, err
}
if len(privateIfs) == 0 {
return IfAddrs{}, nil
}
OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(privateIfs)
privateIfs, _, err = IfByRFC("6890", privateIfs)
if err != nil {
return IfAddrs{}, err
} else if len(privateIfs) == 0 {
return IfAddrs{}, nil
}
return privateIfs, nil
}
// GetPublicInterfaces returns an IfAddrs that are 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 IfAddrs will be returned instead. This
// function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetDefaultInterfaces | include "type" "ip" | include "flags" "forwardable|up" | sort "type,size" | exclude "RFC" "6890" }}'
/// ```
func GetPublicInterfaces() (IfAddrs, error) {
publicIfs, err := GetDefaultInterfaces()
if err != nil {
return IfAddrs{}, err
}
if len(publicIfs) == 0 {
return IfAddrs{}, nil
}
publicIfs, _ = FilterIfByType(publicIfs, TypeIP)
if len(publicIfs) == 0 {
return IfAddrs{}, nil
}
publicIfs, _, err = IfByFlag("forwardable|up", publicIfs)
if err != nil {
return IfAddrs{}, err
}
if len(publicIfs) == 0 {
return IfAddrs{}, nil
}
OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(publicIfs)
_, publicIfs, err = IfByRFC("6890", publicIfs)
if err != nil {
return IfAddrs{}, err
} else if len(publicIfs) == 0 {
return IfAddrs{}, nil
}
return publicIfs, nil
}
// IfByAddress returns a list of matched and non-matched IfAddrs, or an error if
// the regexp fails to compile.
func IfByAddress(inputRe string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) {
re, err := regexp.Compile(inputRe)
if err != nil {
return nil, nil, fmt.Errorf("Unable to compile address regexp %+q: %v", inputRe, err)
}
matchedAddrs := make(IfAddrs, 0, len(ifAddrs))
excludedAddrs := make(IfAddrs, 0, len(ifAddrs))
for _, addr := range ifAddrs {
if re.MatchString(addr.SockAddr.String()) {
matchedAddrs = append(matchedAddrs, addr)
} else {
excludedAddrs = append(excludedAddrs, addr)
}
}
return matchedAddrs, excludedAddrs, nil
}
// IfByName returns a list of matched and non-matched IfAddrs, or an error if
// the regexp fails to compile.
func IfByName(inputRe string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) {
re, err := regexp.Compile(inputRe)
if err != nil {
return nil, nil, fmt.Errorf("Unable to compile name regexp %+q: %v", inputRe, err)
}
matchedAddrs := make(IfAddrs, 0, len(ifAddrs))
excludedAddrs := make(IfAddrs, 0, len(ifAddrs))
for _, addr := range ifAddrs {
if re.MatchString(addr.Name) {
matchedAddrs = append(matchedAddrs, addr)
} else {
excludedAddrs = append(excludedAddrs, addr)
}
}
return matchedAddrs, excludedAddrs, nil
}
// IfByPort returns a list of matched and non-matched IfAddrs, or an error if
// the regexp fails to compile.
func IfByPort(inputRe string, ifAddrs IfAddrs) (matchedIfs, excludedIfs IfAddrs, err error) {
re, err := regexp.Compile(inputRe)
if err != nil {
return nil, nil, fmt.Errorf("Unable to compile port regexp %+q: %v", inputRe, err)
}
ipIfs, nonIfs := FilterIfByType(ifAddrs, TypeIP)
matchedIfs = make(IfAddrs, 0, len(ipIfs))
excludedIfs = append(IfAddrs(nil), nonIfs...)
for _, addr := range ipIfs {
ipAddr := ToIPAddr(addr.SockAddr)
if ipAddr == nil {
continue
}
port := strconv.FormatInt(int64((*ipAddr).IPPort()), 10)
if re.MatchString(port) {
matchedIfs = append(matchedIfs, addr)
} else {
excludedIfs = append(excludedIfs, addr)
}
}
return matchedIfs, excludedIfs, nil
}
// IfByRFC returns a list of matched and non-matched IfAddrs that contain the
// relevant RFC-specified traits.
func IfByRFC(selectorParam string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) {
inputRFC, err := strconv.ParseUint(selectorParam, 10, 64)
if err != nil {
return IfAddrs{}, IfAddrs{}, fmt.Errorf("unable to parse RFC number %q: %v", selectorParam, err)
}
matchedIfAddrs := make(IfAddrs, 0, len(ifAddrs))
remainingIfAddrs := make(IfAddrs, 0, len(ifAddrs))
rfcNetMap := KnownRFCs()
rfcNets, ok := rfcNetMap[uint(inputRFC)]
if !ok {
return nil, nil, fmt.Errorf("unsupported RFC %d", inputRFC)
}
for _, ifAddr := range ifAddrs {
var contained bool
for _, rfcNet := range rfcNets {
if rfcNet.Contains(ifAddr.SockAddr) {
matchedIfAddrs = append(matchedIfAddrs, ifAddr)
contained = true
break
}
}
if !contained {
remainingIfAddrs = append(remainingIfAddrs, ifAddr)
}
}
return matchedIfAddrs, remainingIfAddrs, nil
}
// IfByRFCs returns a list of matched and non-matched IfAddrs that contain the
// relevant RFC-specified traits. Multiple RFCs can be specified and separated
// by the `|` symbol. No protection is taken to ensure an IfAddr does not end
// up in both the included and excluded list.
func IfByRFCs(selectorParam string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) {
var includedIfs, excludedIfs IfAddrs
for _, rfcStr := range strings.Split(selectorParam, "|") {
includedRFCIfs, excludedRFCIfs, err := IfByRFC(rfcStr, ifAddrs)
if err != nil {
return IfAddrs{}, IfAddrs{}, fmt.Errorf("unable to lookup RFC number %q: %v", rfcStr, err)
}
includedIfs = append(includedIfs, includedRFCIfs...)
excludedIfs = append(excludedIfs, excludedRFCIfs...)
}
return includedIfs, excludedIfs, nil
}
// IfByMaskSize returns a list of matched and non-matched IfAddrs that have the
// matching mask size.
func IfByMaskSize(selectorParam string, ifAddrs IfAddrs) (matchedIfs, excludedIfs IfAddrs, err error) {
maskSize, err := strconv.ParseUint(selectorParam, 10, 64)
if err != nil {
return IfAddrs{}, IfAddrs{}, fmt.Errorf("invalid exclude size argument (%q): %v", selectorParam, err)
}
ipIfs, nonIfs := FilterIfByType(ifAddrs, TypeIP)
matchedIfs = make(IfAddrs, 0, len(ipIfs))
excludedIfs = append(IfAddrs(nil), nonIfs...)
for _, addr := range ipIfs {
ipAddr := ToIPAddr(addr.SockAddr)
if ipAddr == nil {
return IfAddrs{}, IfAddrs{}, fmt.Errorf("unable to filter mask sizes on non-IP type %s: %v", addr.SockAddr.Type().String(), addr.SockAddr.String())
}
switch {
case (*ipAddr).Type()&TypeIPv4 != 0 && maskSize > 32:
return IfAddrs{}, IfAddrs{}, fmt.Errorf("mask size out of bounds for IPv4 address: %d", maskSize)
case (*ipAddr).Type()&TypeIPv6 != 0 && maskSize > 128:
return IfAddrs{}, IfAddrs{}, fmt.Errorf("mask size out of bounds for IPv6 address: %d", maskSize)
}
if (*ipAddr).Maskbits() == int(maskSize) {
matchedIfs = append(matchedIfs, addr)
} else {
excludedIfs = append(excludedIfs, addr)
}
}
return matchedIfs, excludedIfs, nil
}
// IfByType returns a list of matching and non-matching IfAddr that match the
// specified type. For instance:
//
// include "type" "IPv4,IPv6"
//
// will include any IfAddrs that is either an IPv4 or IPv6 address. Any
// addresses on those interfaces that don't match will be included in the
// remainder results.
func IfByType(inputTypes string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) {
matchingIfAddrs := make(IfAddrs, 0, len(ifAddrs))
remainingIfAddrs := make(IfAddrs, 0, len(ifAddrs))
ifTypes := strings.Split(strings.ToLower(inputTypes), "|")
for _, ifType := range ifTypes {
switch ifType {
case "ip", "ipv4", "ipv6", "unix":
// Valid types
default:
return nil, nil, fmt.Errorf("unsupported type %q %q", ifType, inputTypes)
}
}
for _, ifAddr := range ifAddrs {
for _, ifType := range ifTypes {
var matched bool
switch {
case ifType == "ip" && ifAddr.SockAddr.Type()&TypeIP != 0:
matched = true
case ifType == "ipv4" && ifAddr.SockAddr.Type()&TypeIPv4 != 0:
matched = true
case ifType == "ipv6" && ifAddr.SockAddr.Type()&TypeIPv6 != 0:
matched = true
case ifType == "unix" && ifAddr.SockAddr.Type()&TypeUnix != 0:
matched = true
}
if matched {
matchingIfAddrs = append(matchingIfAddrs, ifAddr)
} else {
remainingIfAddrs = append(remainingIfAddrs, ifAddr)
}
}
}
return matchingIfAddrs, remainingIfAddrs, nil
}
// IfByFlag returns a list of matching and non-matching IfAddrs that match the
// specified type. For instance:
//
// include "flag" "up,broadcast"
//
// will include any IfAddrs that have both the "up" and "broadcast" flags set.
// Any addresses on those interfaces that don't match will be omitted from the
// results.
func IfByFlag(inputFlags string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) {
matchedAddrs := make(IfAddrs, 0, len(ifAddrs))
excludedAddrs := make(IfAddrs, 0, len(ifAddrs))
var wantForwardable,
wantGlobalUnicast,
wantInterfaceLocalMulticast,
wantLinkLocalMulticast,
wantLinkLocalUnicast,
wantLoopback,
wantMulticast,
wantUnspecified bool
var ifFlags net.Flags
var checkFlags, checkAttrs bool
for _, flagName := range strings.Split(strings.ToLower(inputFlags), "|") {
switch flagName {
case "broadcast":
checkFlags = true
ifFlags = ifFlags | net.FlagBroadcast
case "down":
checkFlags = true
ifFlags = (ifFlags &^ net.FlagUp)
case "forwardable":
checkAttrs = true
wantForwardable = true
case "global unicast":
checkAttrs = true
wantGlobalUnicast = true
case "interface-local multicast":
checkAttrs = true
wantInterfaceLocalMulticast = true
case "link-local multicast":
checkAttrs = true
wantLinkLocalMulticast = true
case "link-local unicast":
checkAttrs = true
wantLinkLocalUnicast = true
case "loopback":
checkAttrs = true
checkFlags = true
ifFlags = ifFlags | net.FlagLoopback
wantLoopback = true
case "multicast":
checkAttrs = true
checkFlags = true
ifFlags = ifFlags | net.FlagMulticast
wantMulticast = true
case "point-to-point":
checkFlags = true
ifFlags = ifFlags | net.FlagPointToPoint
case "unspecified":
checkAttrs = true
wantUnspecified = true
case "up":
checkFlags = true
ifFlags = ifFlags | net.FlagUp
default:
return nil, nil, fmt.Errorf("Unknown interface flag: %+q", flagName)
}
}
for _, ifAddr := range ifAddrs {
var matched bool
if checkFlags && ifAddr.Interface.Flags&ifFlags == ifFlags {
matched = true
}
if checkAttrs {
if ip := ToIPAddr(ifAddr.SockAddr); ip != nil {
netIP := (*ip).NetIP()
switch {
case wantGlobalUnicast && netIP.IsGlobalUnicast():
matched = true
case wantInterfaceLocalMulticast && netIP.IsInterfaceLocalMulticast():
matched = true
case wantLinkLocalMulticast && netIP.IsLinkLocalMulticast():
matched = true
case wantLinkLocalUnicast && netIP.IsLinkLocalUnicast():
matched = true
case wantLoopback && netIP.IsLoopback():
matched = true
case wantMulticast && netIP.IsMulticast():
matched = true
case wantUnspecified && netIP.IsUnspecified():
matched = true
case wantForwardable && !IsRFC(ForwardingBlacklist, ifAddr.SockAddr):
matched = true
}
}
}
if matched {
matchedAddrs = append(matchedAddrs, ifAddr)
} else {
excludedAddrs = append(excludedAddrs, ifAddr)
}
}
return matchedAddrs, excludedAddrs, nil
}
// IfByNetwork returns an IfAddrs that are equal to or included within the
// network passed in by selector.
func IfByNetwork(selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, IfAddrs, error) {
var includedIfs, excludedIfs IfAddrs
for _, netStr := range strings.Split(selectorParam, "|") {
netAddr, err := NewIPAddr(netStr)
if err != nil {
return nil, nil, fmt.Errorf("unable to create an IP address from %+q: %v", netStr, err)
}
for _, ifAddr := range inputIfAddrs {
if netAddr.Contains(ifAddr.SockAddr) {
includedIfs = append(includedIfs, ifAddr)
} else {
excludedIfs = append(excludedIfs, ifAddr)
}
}
}
return includedIfs, excludedIfs, nil
}
// IncludeIfs returns an IfAddrs based on the passed in selector.
func IncludeIfs(selectorName, selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) {
var includedIfs IfAddrs
var err error
switch strings.ToLower(selectorName) {
case "address":
includedIfs, _, err = IfByAddress(selectorParam, inputIfAddrs)
case "flag", "flags":
includedIfs, _, err = IfByFlag(selectorParam, inputIfAddrs)
case "name":
includedIfs, _, err = IfByName(selectorParam, inputIfAddrs)
case "network":
includedIfs, _, err = IfByNetwork(selectorParam, inputIfAddrs)
case "port":
includedIfs, _, err = IfByPort(selectorParam, inputIfAddrs)
case "rfc", "rfcs":
includedIfs, _, err = IfByRFCs(selectorParam, inputIfAddrs)
case "size":
includedIfs, _, err = IfByMaskSize(selectorParam, inputIfAddrs)
case "type":
includedIfs, _, err = IfByType(selectorParam, inputIfAddrs)
default:
return IfAddrs{}, fmt.Errorf("invalid include selector %q", selectorName)
}
if err != nil {
return IfAddrs{}, err
}
return includedIfs, nil
}
// ExcludeIfs returns an IfAddrs based on the passed in selector.
func ExcludeIfs(selectorName, selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) {
var excludedIfs IfAddrs
var err error
switch strings.ToLower(selectorName) {
case "address":
_, excludedIfs, err = IfByAddress(selectorParam, inputIfAddrs)
case "flag", "flags":
_, excludedIfs, err = IfByFlag(selectorParam, inputIfAddrs)
case "name":
_, excludedIfs, err = IfByName(selectorParam, inputIfAddrs)
case "network":
_, excludedIfs, err = IfByNetwork(selectorParam, inputIfAddrs)
case "port":
_, excludedIfs, err = IfByPort(selectorParam, inputIfAddrs)
case "rfc", "rfcs":
_, excludedIfs, err = IfByRFCs(selectorParam, inputIfAddrs)
case "size":
_, excludedIfs, err = IfByMaskSize(selectorParam, inputIfAddrs)
case "type":
_, excludedIfs, err = IfByType(selectorParam, inputIfAddrs)
default:
return IfAddrs{}, fmt.Errorf("invalid exclude selector %q", selectorName)
}
if err != nil {
return IfAddrs{}, err
}
return excludedIfs, nil
}
// SortIfBy returns an IfAddrs sorted based on the passed in selector. Multiple
// sort clauses can be passed in as a comma delimited list without whitespace.
func SortIfBy(selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) {
sortedIfs := append(IfAddrs(nil), inputIfAddrs...)
clauses := strings.Split(selectorParam, ",")
sortFuncs := make([]CmpIfAddrFunc, len(clauses))
for i, clause := range clauses {
switch strings.TrimSpace(strings.ToLower(clause)) {
case "+address", "address":
// The "address" selector returns an array of IfAddrs
// ordered by the network address. IfAddrs that are not
// comparable will be at the end of the list and in a
// non-deterministic order.
sortFuncs[i] = AscIfAddress
case "-address":
sortFuncs[i] = DescIfAddress
case "+name", "name":
// The "name" selector returns an array of IfAddrs
// ordered by the interface name.
sortFuncs[i] = AscIfName
case "-name":
sortFuncs[i] = DescIfName
case "+port", "port":
// The "port" selector returns an array of IfAddrs
// ordered by the port, if included in the IfAddr.
// IfAddrs that are not comparable will be at the end of
// the list and in a non-deterministic order.
sortFuncs[i] = AscIfPort
case "-port":
sortFuncs[i] = DescIfPort
case "+private", "private":
// The "private" selector returns an array of IfAddrs
// ordered by private addresses first. IfAddrs that are
// not comparable will be at the end of the list and in
// a non-deterministic order.
sortFuncs[i] = AscIfPrivate
case "-private":
sortFuncs[i] = DescIfPrivate
case "+size", "size":
// The "size" selector returns an array of IfAddrs
// ordered by the size of the network mask, smaller mask
// (larger number of hosts per network) to largest
// (e.g. a /24 sorts before a /32).
sortFuncs[i] = AscIfNetworkSize
case "-size":
sortFuncs[i] = DescIfNetworkSize
case "+type", "type":
// The "type" selector returns an array of IfAddrs
// ordered by the type of the IfAddr. The sort order is
// Unix, IPv4, then IPv6.
sortFuncs[i] = AscIfType
case "-type":
sortFuncs[i] = DescIfType
default:
// Return an empty list for invalid sort types.
return IfAddrs{}, fmt.Errorf("unknown sort type: %q", clause)
}
}
OrderedIfAddrBy(sortFuncs...).Sort(sortedIfs)
return sortedIfs, nil
}
// UniqueIfAddrsBy creates a unique set of IfAddrs based on the matching
// selector. UniqueIfAddrsBy assumes the input has already been sorted.
func UniqueIfAddrsBy(selectorName string, inputIfAddrs IfAddrs) (IfAddrs, error) {
attrName := strings.ToLower(selectorName)
ifs := make(IfAddrs, 0, len(inputIfAddrs))
var lastMatch string
for _, ifAddr := range inputIfAddrs {
var out string
switch attrName {
case "address":
out = ifAddr.SockAddr.String()
case "name":
out = ifAddr.Name
default:
return nil, fmt.Errorf("unsupported unique constraint %+q", selectorName)
}
switch {
case lastMatch == "", lastMatch != out:
lastMatch = out
ifs = append(ifs, ifAddr)
case lastMatch == out:
continue
}
}
return ifs, nil
}
// JoinIfAddrs joins an IfAddrs and returns a string
func JoinIfAddrs(selectorName string, joinStr string, inputIfAddrs IfAddrs) (string, error) {
outputs := make([]string, 0, len(inputIfAddrs))
attrName := AttrName(strings.ToLower(selectorName))
for _, ifAddr := range inputIfAddrs {
var attrVal string
var err error
attrVal, err = ifAddr.Attr(attrName)
if err != nil {
return "", err
}
outputs = append(outputs, attrVal)
}
return strings.Join(outputs, joinStr), nil
}
// LimitIfAddrs returns a slice of IfAddrs based on the specified limit.
func LimitIfAddrs(lim uint, in IfAddrs) (IfAddrs, error) {
// Clamp the limit to the length of the array
if int(lim) > len(in) {
lim = uint(len(in))
}
return in[0:lim], nil
}
// OffsetIfAddrs returns a slice of IfAddrs based on the specified offset.
func OffsetIfAddrs(off int, in IfAddrs) (IfAddrs, error) {
var end bool
if off < 0 {
end = true
off = off * -1
}
if off > len(in) {
return IfAddrs{}, fmt.Errorf("unable to seek past the end of the interface array: offset (%d) exceeds the number of interfaces (%d)", off, len(in))
}
if end {
return in[len(in)-off:], nil
}
return in[off:], nil
}
func (ifAddr IfAddr) String() string {
return fmt.Sprintf("%s %v", ifAddr.SockAddr, ifAddr.Interface)
}
// parseDefaultIfNameFromRoute parses standard route(8)'s output for the *BSDs
// and Solaris.
func parseDefaultIfNameFromRoute(routeOut string) (string, error) {
lines := strings.Split(routeOut, "\n")
for _, line := range lines {
kvs := strings.SplitN(line, ":", 2)
if len(kvs) != 2 {
continue
}
if strings.TrimSpace(kvs[0]) == "interface" {
ifName := strings.TrimSpace(kvs[1])
return ifName, nil
}
}
return "", errors.New("No default interface found")
}
// parseDefaultIfNameFromIPCmd parses the default interface from ip(8) for
// Linux.
func parseDefaultIfNameFromIPCmd(routeOut string) (string, error) {
lines := strings.Split(routeOut, "\n")
re := regexp.MustCompile(`[\s]+`)
for _, line := range lines {
kvs := re.Split(line, -1)
if len(kvs) < 5 {
continue
}
if kvs[0] == "default" &&
kvs[1] == "via" &&
kvs[3] == "dev" {
ifName := strings.TrimSpace(kvs[4])
return ifName, nil
}
}
return "", errors.New("No default interface found")
}
// parseDefaultIfNameWindows parses the default interface from `netstat -rn` and
// `ipconfig` on Windows.
func parseDefaultIfNameWindows(routeOut, ipconfigOut string) (string, error) {
defaultIPAddr, err := parseDefaultIPAddrWindowsRoute(routeOut)
if err != nil {
return "", err
}
ifName, err := parseDefaultIfNameWindowsIPConfig(defaultIPAddr, ipconfigOut)
if err != nil {
return "", err
}
return ifName, nil
}
// parseDefaultIPAddrWindowsRoute parses the IP address on the default interface
// `netstat -rn`.
//
// NOTES(sean): Only IPv4 addresses are parsed at this time. If you have an
// IPv6 connected host, submit an issue on github.com/hashicorp/go-sockaddr with
// the output from `netstat -rn`, `ipconfig`, and version of Windows to see IPv6
// support added.
func parseDefaultIPAddrWindowsRoute(routeOut string) (string, error) {
lines := strings.Split(routeOut, "\n")
re := regexp.MustCompile(`[\s]+`)
for _, line := range lines {
kvs := re.Split(strings.TrimSpace(line), -1)
if len(kvs) < 3 {
continue
}
if kvs[0] == "0.0.0.0" && kvs[1] == "0.0.0.0" {
defaultIPAddr := strings.TrimSpace(kvs[3])
return defaultIPAddr, nil
}
}
return "", errors.New("No IP on default interface found")
}
// parseDefaultIfNameWindowsIPConfig parses the output of `ipconfig` to find the
// 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]+)`)
var ifName string
for _, line := range lines {
switch ifNameMatches := ifNameRE.FindStringSubmatch(line); {
case len(ifNameMatches) > 1:
ifName = ifNameMatches[1]
continue
}
switch ipAddrMatches := ipAddrRE.FindStringSubmatch(line); {
case len(ipAddrMatches) > 1 && ipAddrMatches[1] == defaultIPAddr:
return ifName, nil
}
}
return "", errors.New("No default interface found with matching IP")
}

View file

@ -0,0 +1,65 @@
package sockaddr
import (
"fmt"
"net"
)
// IfAddr is a union of a SockAddr and a net.Interface.
type IfAddr struct {
SockAddr
net.Interface
}
// Attr returns the named attribute as a string
func (ifAddr IfAddr) Attr(attrName AttrName) (string, error) {
val := IfAddrAttr(ifAddr, attrName)
if val != "" {
return val, nil
}
return Attr(ifAddr.SockAddr, attrName)
}
// Attr returns the named attribute as a string
func Attr(sa SockAddr, attrName AttrName) (string, error) {
switch sockType := sa.Type(); {
case sockType&TypeIP != 0:
ip := *ToIPAddr(sa)
attrVal := IPAddrAttr(ip, attrName)
if attrVal != "" {
return attrVal, nil
}
if sockType == TypeIPv4 {
ipv4 := *ToIPv4Addr(sa)
attrVal := IPv4AddrAttr(ipv4, attrName)
if attrVal != "" {
return attrVal, nil
}
} else if sockType == TypeIPv6 {
ipv6 := *ToIPv6Addr(sa)
attrVal := IPv6AddrAttr(ipv6, attrName)
if attrVal != "" {
return attrVal, nil
}
}
case sockType == TypeUnix:
us := *ToUnixSock(sa)
attrVal := UnixSockAttr(us, attrName)
if attrVal != "" {
return attrVal, nil
}
}
// Non type-specific attributes
switch attrName {
case "string":
return sa.String(), nil
case "type":
return sa.Type().String(), nil
}
return "", fmt.Errorf("unsupported attribute name %q", attrName)
}

View file

@ -0,0 +1,169 @@
package sockaddr
import (
"fmt"
"math/big"
"net"
"strings"
)
// Constants for the sizes of IPv3, IPv4, and IPv6 address types.
const (
IPv3len = 6
IPv4len = 4
IPv6len = 16
)
// IPAddr is a generic IP address interface for IPv4 and IPv6 addresses,
// networks, and socket endpoints.
type IPAddr interface {
SockAddr
AddressBinString() string
AddressHexString() string
Cmp(SockAddr) int
CmpAddress(SockAddr) int
CmpPort(SockAddr) int
FirstUsable() IPAddr
Host() IPAddr
IPPort() IPPort
LastUsable() IPAddr
Maskbits() int
NetIP() *net.IP
NetIPMask() *net.IPMask
NetIPNet() *net.IPNet
Network() IPAddr
Octets() []int
}
// IPPort is the type for an IP port number for the TCP and UDP IP transports.
type IPPort uint16
// IPPrefixLen is a typed integer representing the prefix length for a given
// IPAddr.
type IPPrefixLen byte
// ipAddrAttrMap is a map of the IPAddr type-specific attributes.
var ipAddrAttrMap map[AttrName]func(IPAddr) string
var ipAddrAttrs []AttrName
func init() {
ipAddrInit()
}
// NewIPAddr creates a new IPAddr from a string. Returns nil if the string is
// not an IPv4 or an IPv6 address.
func NewIPAddr(addr string) (IPAddr, error) {
ipv4Addr, err := NewIPv4Addr(addr)
if err == nil {
return ipv4Addr, nil
}
ipv6Addr, err := NewIPv6Addr(addr)
if err == nil {
return ipv6Addr, nil
}
return nil, fmt.Errorf("invalid IPAddr %v", addr)
}
// IPAddrAttr returns a string representation of an attribute for the given
// IPAddr.
func IPAddrAttr(ip IPAddr, selector AttrName) string {
fn, found := ipAddrAttrMap[selector]
if !found {
return ""
}
return fn(ip)
}
// IPAttrs returns a list of attributes supported by the IPAddr type
func IPAttrs() []AttrName {
return ipAddrAttrs
}
// MustIPAddr is a helper method that must return an IPAddr or panic on invalid
// input.
func MustIPAddr(addr string) IPAddr {
ip, err := NewIPAddr(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create an IPAddr from %+q: %v", addr, err))
}
return ip
}
// ipAddrInit is called once at init()
func ipAddrInit() {
// Sorted for human readability
ipAddrAttrs = []AttrName{
"host",
"address",
"port",
"netmask",
"network",
"mask_bits",
"binary",
"hex",
"first_usable",
"last_usable",
"octets",
}
ipAddrAttrMap = map[AttrName]func(ip IPAddr) string{
"address": func(ip IPAddr) string {
return ip.NetIP().String()
},
"binary": func(ip IPAddr) string {
return ip.AddressBinString()
},
"first_usable": func(ip IPAddr) string {
return ip.FirstUsable().String()
},
"hex": func(ip IPAddr) string {
return ip.AddressHexString()
},
"host": func(ip IPAddr) string {
return ip.Host().String()
},
"last_usable": func(ip IPAddr) string {
return ip.LastUsable().String()
},
"mask_bits": func(ip IPAddr) string {
return fmt.Sprintf("%d", ip.Maskbits())
},
"netmask": func(ip IPAddr) string {
switch v := ip.(type) {
case IPv4Addr:
ipv4Mask := IPv4Addr{
Address: IPv4Address(v.Mask),
Mask: IPv4HostMask,
}
return ipv4Mask.String()
case IPv6Addr:
ipv6Mask := new(big.Int)
ipv6Mask.Set(v.Mask)
ipv6MaskAddr := IPv6Addr{
Address: IPv6Address(ipv6Mask),
Mask: ipv6HostMask,
}
return ipv6MaskAddr.String()
default:
return fmt.Sprintf("<unsupported type: %T>", ip)
}
},
"network": func(ip IPAddr) string {
return ip.Network().NetIP().String()
},
"octets": func(ip IPAddr) string {
octets := ip.Octets()
octetStrs := make([]string, 0, len(octets))
for _, octet := range octets {
octetStrs = append(octetStrs, fmt.Sprintf("%d", octet))
}
return strings.Join(octetStrs, " ")
},
"port": func(ip IPAddr) string {
return fmt.Sprintf("%d", ip.IPPort())
},
}
}

View file

@ -0,0 +1,98 @@
package sockaddr
import "bytes"
type IPAddrs []IPAddr
func (s IPAddrs) Len() int { return len(s) }
func (s IPAddrs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// // SortIPAddrsByCmp is a type that satisfies sort.Interface and can be used
// // by the routines in this package. The SortIPAddrsByCmp type is used to
// // sort IPAddrs by Cmp()
// type SortIPAddrsByCmp struct{ IPAddrs }
// // Less reports whether the element with index i should sort before the
// // element with index j.
// func (s SortIPAddrsByCmp) Less(i, j int) bool {
// // Sort by Type, then address, then port number.
// return Less(s.IPAddrs[i], s.IPAddrs[j])
// }
// SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and
// can be used by the routines in this package. The
// SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest
// network (most specific to largest network).
type SortIPAddrsByNetworkSize struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsByNetworkSize) Less(i, j int) bool {
// Sort masks with a larger binary value (i.e. fewer hosts per network
// prefix) after masks with a smaller value (larger number of hosts per
// prefix).
switch bytes.Compare([]byte(*s.IPAddrs[i].NetIPMask()), []byte(*s.IPAddrs[j].NetIPMask())) {
case 0:
// Fall through to the second test if the net.IPMasks are the
// same.
break
case 1:
return true
case -1:
return false
default:
panic("bad, m'kay?")
}
// Sort IPs based on the length (i.e. prefer IPv4 over IPv6).
iLen := len(*s.IPAddrs[i].NetIP())
jLen := len(*s.IPAddrs[j].NetIP())
if iLen != jLen {
return iLen > jLen
}
// Sort IPs based on their network address from lowest to highest.
switch bytes.Compare(s.IPAddrs[i].NetIPNet().IP, s.IPAddrs[j].NetIPNet().IP) {
case 0:
break
case 1:
return false
case -1:
return true
default:
panic("lol wut?")
}
// If a host does not have a port set, it always sorts after hosts
// that have a port (e.g. a host with a /32 and port number is more
// specific and should sort first over a host with a /32 but no port
// set).
if s.IPAddrs[i].IPPort() == 0 || s.IPAddrs[j].IPPort() == 0 {
return false
}
return s.IPAddrs[i].IPPort() < s.IPAddrs[j].IPPort()
}
// SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and
// can be used by the routines in this package. The
// SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest
// network (most specific to largest network).
type SortIPAddrsBySpecificMaskLen struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsBySpecificMaskLen) Less(i, j int) bool {
return s.IPAddrs[i].Maskbits() > s.IPAddrs[j].Maskbits()
}
// SortIPAddrsByBroadMaskLen is a type that satisfies sort.Interface and can
// be used by the routines in this package. The SortIPAddrsByBroadMaskLen
// type is used to sort IPAddrs by largest network (i.e. largest subnets
// first).
type SortIPAddrsByBroadMaskLen struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsByBroadMaskLen) Less(i, j int) bool {
return s.IPAddrs[i].Maskbits() < s.IPAddrs[j].Maskbits()
}

View file

@ -0,0 +1,515 @@
package sockaddr
import (
"encoding/binary"
"fmt"
"net"
"regexp"
"strconv"
"strings"
)
type (
// IPv4Address is a named type representing an IPv4 address.
IPv4Address uint32
// IPv4Network is a named type representing an IPv4 network.
IPv4Network uint32
// IPv4Mask is a named type representing an IPv4 network mask.
IPv4Mask uint32
)
// IPv4HostMask is a constant represents a /32 IPv4 Address
// (i.e. 255.255.255.255).
const IPv4HostMask = IPv4Mask(0xffffffff)
// ipv4AddrAttrMap is a map of the IPv4Addr type-specific attributes.
var ipv4AddrAttrMap map[AttrName]func(IPv4Addr) string
var ipv4AddrAttrs []AttrName
var trailingHexNetmaskRE *regexp.Regexp
// IPv4Addr implements a convenience wrapper around the union of Go's
// built-in net.IP and net.IPNet types. In UNIX-speak, IPv4Addr implements
// `sockaddr` when the the address family is set to AF_INET
// (i.e. `sockaddr_in`).
type IPv4Addr struct {
IPAddr
Address IPv4Address
Mask IPv4Mask
Port IPPort
}
func init() {
ipv4AddrInit()
trailingHexNetmaskRE = regexp.MustCompile(`/([0f]{8})$`)
}
// NewIPv4Addr creates an IPv4Addr from a string. String can be in the form
// of either an IPv4:port (e.g. `1.2.3.4:80`, in which case the mask is
// assumed to be a `/32`), an IPv4 address (e.g. `1.2.3.4`, also with a `/32`
// mask), or an IPv4 CIDR (e.g. `1.2.3.4/24`, which has its IP port
// initialized to zero). ipv4Str can not be a hostname.
//
// NOTE: Many net.*() routines will initialize and return an IPv6 address.
// To create uint32 values from net.IP, always test to make sure the address
// returned can be converted to a 4 byte array using To4().
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 {
ipv4Str = ipv4Str[:match[0]]
}
// Parse as an IPv4 CIDR
ipAddr, network, err := net.ParseCIDR(ipv4Str)
if err == nil {
ipv4 := ipAddr.To4()
if ipv4 == nil {
return IPv4Addr{}, fmt.Errorf("Unable to convert %s to an IPv4 address", ipv4Str)
}
// If we see an IPv6 netmask, convert it to an IPv4 mask.
netmaskSepPos := strings.LastIndexByte(ipv4Str, '/')
if netmaskSepPos != -1 && netmaskSepPos+1 < len(ipv4Str) {
netMask, err := strconv.ParseUint(ipv4Str[netmaskSepPos+1:], 10, 8)
if err != nil {
return IPv4Addr{}, fmt.Errorf("Unable to convert %s to an IPv4 address: unable to parse CIDR netmask: %v", ipv4Str, err)
} else if netMask > 128 {
return IPv4Addr{}, fmt.Errorf("Unable to convert %s to an IPv4 address: invalid CIDR netmask", ipv4Str)
}
if netMask >= 96 {
// Convert the IPv6 netmask to an IPv4 netmask
network.Mask = net.CIDRMask(int(netMask-96), IPv4len*8)
}
}
ipv4Addr := IPv4Addr{
Address: IPv4Address(binary.BigEndian.Uint32(ipv4)),
Mask: IPv4Mask(binary.BigEndian.Uint32(network.Mask)),
}
return ipv4Addr, nil
}
// Attempt to parse ipv4Str as a /32 host with a port number.
tcpAddr, err := net.ResolveTCPAddr("tcp4", ipv4Str)
if err == nil {
ipv4 := tcpAddr.IP.To4()
if ipv4 == nil {
return IPv4Addr{}, fmt.Errorf("Unable to resolve %+q as an IPv4 address", ipv4Str)
}
ipv4Uint32 := binary.BigEndian.Uint32(ipv4)
ipv4Addr := IPv4Addr{
Address: IPv4Address(ipv4Uint32),
Mask: IPv4HostMask,
Port: IPPort(tcpAddr.Port),
}
return ipv4Addr, nil
}
// Parse as a naked IPv4 address
ip := net.ParseIP(ipv4Str)
if ip != nil {
ipv4 := ip.To4()
if ipv4 == nil {
return IPv4Addr{}, fmt.Errorf("Unable to string convert %+q to an IPv4 address", ipv4Str)
}
ipv4Uint32 := binary.BigEndian.Uint32(ipv4)
ipv4Addr := IPv4Addr{
Address: IPv4Address(ipv4Uint32),
Mask: IPv4HostMask,
}
return ipv4Addr, nil
}
return IPv4Addr{}, fmt.Errorf("Unable to parse %+q to an IPv4 address: %v", ipv4Str, err)
}
// AddressBinString returns a string with the IPv4Addr's Address represented
// as a sequence of '0' and '1' characters. This method is useful for
// debugging or by operators who want to inspect an address.
func (ipv4 IPv4Addr) AddressBinString() string {
return fmt.Sprintf("%032s", strconv.FormatUint(uint64(ipv4.Address), 2))
}
// AddressHexString returns a string with the IPv4Addr address represented as
// a sequence of hex characters. This method is useful for debugging or by
// operators who want to inspect an address.
func (ipv4 IPv4Addr) AddressHexString() string {
return fmt.Sprintf("%08s", strconv.FormatUint(uint64(ipv4.Address), 16))
}
// Broadcast is an IPv4Addr-only method that returns the broadcast address of
// the network.
//
// NOTE: IPv6 only supports multicast, so this method only exists for
// IPv4Addr.
func (ipv4 IPv4Addr) Broadcast() IPAddr {
// Nothing should listen on a broadcast address.
return IPv4Addr{
Address: IPv4Address(ipv4.BroadcastAddress()),
Mask: IPv4HostMask,
}
}
// BroadcastAddress returns a IPv4Network of the IPv4Addr's broadcast
// address.
func (ipv4 IPv4Addr) BroadcastAddress() IPv4Network {
return IPv4Network(uint32(ipv4.Address)&uint32(ipv4.Mask) | ^uint32(ipv4.Mask))
}
// CmpAddress follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its address is lower than arg
// - 0 if the SockAddr arg is equal to the receiving IPv4Addr or the argument is
// of a different type.
// - 1 If the argument should sort first.
func (ipv4 IPv4Addr) CmpAddress(sa SockAddr) int {
ipv4b, ok := sa.(IPv4Addr)
if !ok {
return sortDeferDecision
}
switch {
case ipv4.Address == ipv4b.Address:
return sortDeferDecision
case ipv4.Address < ipv4b.Address:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// CmpPort follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its port is lower than arg
// - 0 if the SockAddr arg's port number is equal to the receiving IPv4Addr,
// regardless of type.
// - 1 If the argument should sort first.
func (ipv4 IPv4Addr) CmpPort(sa SockAddr) int {
var saPort IPPort
switch v := sa.(type) {
case IPv4Addr:
saPort = v.Port
case IPv6Addr:
saPort = v.Port
default:
return sortDeferDecision
}
switch {
case ipv4.Port == saPort:
return sortDeferDecision
case ipv4.Port < saPort:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// CmpRFC follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because it belongs to the RFC and its
// arg does not
// - 0 if the receiver and arg both belong to the same RFC or neither do.
// - 1 If the arg belongs to the RFC but receiver does not.
func (ipv4 IPv4Addr) CmpRFC(rfcNum uint, sa SockAddr) int {
recvInRFC := IsRFC(rfcNum, ipv4)
ipv4b, ok := sa.(IPv4Addr)
if !ok {
// If the receiver is part of the desired RFC and the SockAddr
// argument is not, return -1 so that the receiver sorts before
// the non-IPv4 SockAddr. Conversely, if the receiver is not
// part of the RFC, punt on sorting and leave it for the next
// sorter.
if recvInRFC {
return sortReceiverBeforeArg
} else {
return sortDeferDecision
}
}
argInRFC := IsRFC(rfcNum, ipv4b)
switch {
case (recvInRFC && argInRFC), (!recvInRFC && !argInRFC):
// If a and b both belong to the RFC, or neither belong to
// rfcNum, defer sorting to the next sorter.
return sortDeferDecision
case recvInRFC && !argInRFC:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// Contains returns true if the SockAddr is contained within the receiver.
func (ipv4 IPv4Addr) Contains(sa SockAddr) bool {
ipv4b, ok := sa.(IPv4Addr)
if !ok {
return false
}
return ipv4.ContainsNetwork(ipv4b)
}
// ContainsAddress returns true if the IPv4Address is contained within the
// receiver.
func (ipv4 IPv4Addr) ContainsAddress(x IPv4Address) bool {
return IPv4Address(ipv4.NetworkAddress()) <= x &&
IPv4Address(ipv4.BroadcastAddress()) >= x
}
// ContainsNetwork returns true if the network from IPv4Addr is contained
// within the receiver.
func (ipv4 IPv4Addr) ContainsNetwork(x IPv4Addr) bool {
return ipv4.NetworkAddress() <= x.NetworkAddress() &&
ipv4.BroadcastAddress() >= x.BroadcastAddress()
}
// DialPacketArgs returns the arguments required to be passed to
// net.DialUDP(). If the Mask of ipv4 is not a /32 or the Port is 0,
// DialPacketArgs() will fail. See Host() to create an IPv4Addr with its
// mask set to /32.
func (ipv4 IPv4Addr) DialPacketArgs() (network, dialArgs string) {
if ipv4.Mask != IPv4HostMask || ipv4.Port == 0 {
return "udp4", ""
}
return "udp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// DialStreamArgs returns the arguments required to be passed to
// net.DialTCP(). If the Mask of ipv4 is not a /32 or the Port is 0,
// DialStreamArgs() will fail. See Host() to create an IPv4Addr with its
// mask set to /32.
func (ipv4 IPv4Addr) DialStreamArgs() (network, dialArgs string) {
if ipv4.Mask != IPv4HostMask || ipv4.Port == 0 {
return "tcp4", ""
}
return "tcp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// Equal returns true if a SockAddr is equal to the receiving IPv4Addr.
func (ipv4 IPv4Addr) Equal(sa SockAddr) bool {
ipv4b, ok := sa.(IPv4Addr)
if !ok {
return false
}
if ipv4.Port != ipv4b.Port {
return false
}
if ipv4.Address != ipv4b.Address {
return false
}
if ipv4.NetIPNet().String() != ipv4b.NetIPNet().String() {
return false
}
return true
}
// FirstUsable returns an IPv4Addr set to the first address following the
// network prefix. The first usable address in a network is normally the
// gateway and should not be used except by devices forwarding packets
// between two administratively distinct networks (i.e. a router). This
// function does not discriminate against first usable vs "first address that
// should be used." For example, FirstUsable() on "192.168.1.10/24" would
// return the address "192.168.1.1/24".
func (ipv4 IPv4Addr) FirstUsable() IPAddr {
addr := ipv4.NetworkAddress()
// If /32, return the address itself. If /31 assume a point-to-point
// link and return the lower address.
if ipv4.Maskbits() < 31 {
addr++
}
return IPv4Addr{
Address: IPv4Address(addr),
Mask: IPv4HostMask,
}
}
// Host returns a copy of ipv4 with its mask set to /32 so that it can be
// used by DialPacketArgs(), DialStreamArgs(), ListenPacketArgs(), or
// ListenStreamArgs().
func (ipv4 IPv4Addr) Host() IPAddr {
// Nothing should listen on a broadcast address.
return IPv4Addr{
Address: ipv4.Address,
Mask: IPv4HostMask,
Port: ipv4.Port,
}
}
// IPPort returns the Port number attached to the IPv4Addr
func (ipv4 IPv4Addr) IPPort() IPPort {
return ipv4.Port
}
// LastUsable returns the last address before the broadcast address in a
// given network.
func (ipv4 IPv4Addr) LastUsable() IPAddr {
addr := ipv4.BroadcastAddress()
// If /32, return the address itself. If /31 assume a point-to-point
// link and return the upper address.
if ipv4.Maskbits() < 31 {
addr--
}
return IPv4Addr{
Address: IPv4Address(addr),
Mask: IPv4HostMask,
}
}
// ListenPacketArgs returns the arguments required to be passed to
// net.ListenUDP(). If the Mask of ipv4 is not a /32, ListenPacketArgs()
// will fail. See Host() to create an IPv4Addr with its mask set to /32.
func (ipv4 IPv4Addr) ListenPacketArgs() (network, listenArgs string) {
if ipv4.Mask != IPv4HostMask {
return "udp4", ""
}
return "udp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// ListenStreamArgs returns the arguments required to be passed to
// net.ListenTCP(). If the Mask of ipv4 is not a /32, ListenStreamArgs()
// will fail. See Host() to create an IPv4Addr with its mask set to /32.
func (ipv4 IPv4Addr) ListenStreamArgs() (network, listenArgs string) {
if ipv4.Mask != IPv4HostMask {
return "tcp4", ""
}
return "tcp4", fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
// Maskbits returns the number of network mask bits in a given IPv4Addr. For
// example, the Maskbits() of "192.168.1.1/24" would return 24.
func (ipv4 IPv4Addr) Maskbits() int {
mask := make(net.IPMask, IPv4len)
binary.BigEndian.PutUint32(mask, uint32(ipv4.Mask))
maskOnes, _ := mask.Size()
return maskOnes
}
// MustIPv4Addr is a helper method that must return an IPv4Addr or panic on
// invalid input.
func MustIPv4Addr(addr string) IPv4Addr {
ipv4, err := NewIPv4Addr(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create an IPv4Addr from %+q: %v", addr, err))
}
return ipv4
}
// NetIP returns the address as a net.IP (address is always presized to
// IPv4).
func (ipv4 IPv4Addr) NetIP() *net.IP {
x := make(net.IP, IPv4len)
binary.BigEndian.PutUint32(x, uint32(ipv4.Address))
return &x
}
// NetIPMask create a new net.IPMask from the IPv4Addr.
func (ipv4 IPv4Addr) NetIPMask() *net.IPMask {
ipv4Mask := net.IPMask{}
ipv4Mask = make(net.IPMask, IPv4len)
binary.BigEndian.PutUint32(ipv4Mask, uint32(ipv4.Mask))
return &ipv4Mask
}
// NetIPNet create a new net.IPNet from the IPv4Addr.
func (ipv4 IPv4Addr) NetIPNet() *net.IPNet {
ipv4net := &net.IPNet{}
ipv4net.IP = make(net.IP, IPv4len)
binary.BigEndian.PutUint32(ipv4net.IP, uint32(ipv4.NetworkAddress()))
ipv4net.Mask = *ipv4.NetIPMask()
return ipv4net
}
// Network returns the network prefix or network address for a given network.
func (ipv4 IPv4Addr) Network() IPAddr {
return IPv4Addr{
Address: IPv4Address(ipv4.NetworkAddress()),
Mask: ipv4.Mask,
}
}
// NetworkAddress returns an IPv4Network of the IPv4Addr's network address.
func (ipv4 IPv4Addr) NetworkAddress() IPv4Network {
return IPv4Network(uint32(ipv4.Address) & uint32(ipv4.Mask))
}
// Octets returns a slice of the four octets in an IPv4Addr's Address. The
// order of the bytes is big endian.
func (ipv4 IPv4Addr) Octets() []int {
return []int{
int(ipv4.Address >> 24),
int((ipv4.Address >> 16) & 0xff),
int((ipv4.Address >> 8) & 0xff),
int(ipv4.Address & 0xff),
}
}
// String returns a string representation of the IPv4Addr
func (ipv4 IPv4Addr) String() string {
if ipv4.Port != 0 {
return fmt.Sprintf("%s:%d", ipv4.NetIP().String(), ipv4.Port)
}
if ipv4.Maskbits() == 32 {
return ipv4.NetIP().String()
}
return fmt.Sprintf("%s/%d", ipv4.NetIP().String(), ipv4.Maskbits())
}
// Type is used as a type switch and returns TypeIPv4
func (IPv4Addr) Type() SockAddrType {
return TypeIPv4
}
// IPv4AddrAttr returns a string representation of an attribute for the given
// IPv4Addr.
func IPv4AddrAttr(ipv4 IPv4Addr, selector AttrName) string {
fn, found := ipv4AddrAttrMap[selector]
if !found {
return ""
}
return fn(ipv4)
}
// IPv4Attrs returns a list of attributes supported by the IPv4Addr type
func IPv4Attrs() []AttrName {
return ipv4AddrAttrs
}
// ipv4AddrInit is called once at init()
func ipv4AddrInit() {
// Sorted for human readability
ipv4AddrAttrs = []AttrName{
"size", // Same position as in IPv6 for output consistency
"broadcast",
"uint32",
}
ipv4AddrAttrMap = map[AttrName]func(ipv4 IPv4Addr) string{
"broadcast": func(ipv4 IPv4Addr) string {
return ipv4.Broadcast().String()
},
"size": func(ipv4 IPv4Addr) string {
return fmt.Sprintf("%d", 1<<uint(IPv4len*8-ipv4.Maskbits()))
},
"uint32": func(ipv4 IPv4Addr) string {
return fmt.Sprintf("%d", uint32(ipv4.Address))
},
}
}

View file

@ -0,0 +1,591 @@
package sockaddr
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"net"
)
type (
// IPv6Address is a named type representing an IPv6 address.
IPv6Address *big.Int
// IPv6Network is a named type representing an IPv6 network.
IPv6Network *big.Int
// IPv6Mask is a named type representing an IPv6 network mask.
IPv6Mask *big.Int
)
// IPv6HostPrefix is a constant represents a /128 IPv6 Prefix.
const IPv6HostPrefix = IPPrefixLen(128)
// ipv6HostMask is an unexported big.Int representing a /128 IPv6 address.
// This value must be a constant and always set to all ones.
var ipv6HostMask IPv6Mask
// ipv6AddrAttrMap is a map of the IPv6Addr type-specific attributes.
var ipv6AddrAttrMap map[AttrName]func(IPv6Addr) string
var ipv6AddrAttrs []AttrName
func init() {
biMask := new(big.Int)
biMask.SetBytes([]byte{
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
},
)
ipv6HostMask = IPv6Mask(biMask)
ipv6AddrInit()
}
// IPv6Addr implements a convenience wrapper around the union of Go's
// built-in net.IP and net.IPNet types. In UNIX-speak, IPv6Addr implements
// `sockaddr` when the the address family is set to AF_INET6
// (i.e. `sockaddr_in6`).
type IPv6Addr struct {
IPAddr
Address IPv6Address
Mask IPv6Mask
Port IPPort
}
// NewIPv6Addr creates an IPv6Addr from a string. String can be in the form of
// an an IPv6:port (e.g. `[2001:4860:0:2001::68]:80`, in which case the mask is
// assumed to be a /128), an IPv6 address (e.g. `2001:4860:0:2001::68`, also
// with a `/128` mask), an IPv6 CIDR (e.g. `2001:4860:0:2001::68/64`, which has
// its IP port initialized to zero). ipv6Str can not be a hostname.
//
// NOTE: Many net.*() routines will initialize and return an IPv4 address.
// Always test to make sure the address returned cannot be converted to a 4 byte
// array using To4().
func NewIPv6Addr(ipv6Str string) (IPv6Addr, error) {
v6Addr := false
LOOP:
for i := 0; i < len(ipv6Str); i++ {
switch ipv6Str[i] {
case '.':
break LOOP
case ':':
v6Addr = true
break LOOP
}
}
if !v6Addr {
return IPv6Addr{}, fmt.Errorf("Unable to resolve %+q as an IPv6 address, appears to be an IPv4 address", ipv6Str)
}
// Attempt to parse ipv6Str as a /128 host with a port number.
tcpAddr, err := net.ResolveTCPAddr("tcp6", ipv6Str)
if err == nil {
ipv6 := tcpAddr.IP.To16()
if ipv6 == nil {
return IPv6Addr{}, fmt.Errorf("Unable to resolve %+q as a 16byte IPv6 address", ipv6Str)
}
ipv6BigIntAddr := new(big.Int)
ipv6BigIntAddr.SetBytes(ipv6)
ipv6BigIntMask := new(big.Int)
ipv6BigIntMask.Set(ipv6HostMask)
ipv6Addr := IPv6Addr{
Address: IPv6Address(ipv6BigIntAddr),
Mask: IPv6Mask(ipv6BigIntMask),
Port: IPPort(tcpAddr.Port),
}
return ipv6Addr, nil
}
// Parse as a naked IPv6 address. Trim square brackets if present.
if len(ipv6Str) > 2 && ipv6Str[0] == '[' && ipv6Str[len(ipv6Str)-1] == ']' {
ipv6Str = ipv6Str[1 : len(ipv6Str)-1]
}
ip := net.ParseIP(ipv6Str)
if ip != nil {
ipv6 := ip.To16()
if ipv6 == nil {
return IPv6Addr{}, fmt.Errorf("Unable to string convert %+q to a 16byte IPv6 address", ipv6Str)
}
ipv6BigIntAddr := new(big.Int)
ipv6BigIntAddr.SetBytes(ipv6)
ipv6BigIntMask := new(big.Int)
ipv6BigIntMask.Set(ipv6HostMask)
return IPv6Addr{
Address: IPv6Address(ipv6BigIntAddr),
Mask: IPv6Mask(ipv6BigIntMask),
}, nil
}
// Parse as an IPv6 CIDR
ipAddr, network, err := net.ParseCIDR(ipv6Str)
if err == nil {
ipv6 := ipAddr.To16()
if ipv6 == nil {
return IPv6Addr{}, fmt.Errorf("Unable to convert %+q to a 16byte IPv6 address", ipv6Str)
}
ipv6BigIntAddr := new(big.Int)
ipv6BigIntAddr.SetBytes(ipv6)
ipv6BigIntMask := new(big.Int)
ipv6BigIntMask.SetBytes(network.Mask)
ipv6Addr := IPv6Addr{
Address: IPv6Address(ipv6BigIntAddr),
Mask: IPv6Mask(ipv6BigIntMask),
}
return ipv6Addr, nil
}
return IPv6Addr{}, fmt.Errorf("Unable to parse %+q to an IPv6 address: %v", ipv6Str, err)
}
// AddressBinString returns a string with the IPv6Addr's Address represented
// as a sequence of '0' and '1' characters. This method is useful for
// debugging or by operators who want to inspect an address.
func (ipv6 IPv6Addr) AddressBinString() string {
bi := big.Int(*ipv6.Address)
return fmt.Sprintf("%0128s", bi.Text(2))
}
// AddressHexString returns a string with the IPv6Addr address represented as
// a sequence of hex characters. This method is useful for debugging or by
// operators who want to inspect an address.
func (ipv6 IPv6Addr) AddressHexString() string {
bi := big.Int(*ipv6.Address)
return fmt.Sprintf("%032s", bi.Text(16))
}
// CmpAddress follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its address is lower than arg
// - 0 if the SockAddr arg equal to the receiving IPv6Addr or the argument is of a
// different type.
// - 1 If the argument should sort first.
func (ipv6 IPv6Addr) CmpAddress(sa SockAddr) int {
ipv6b, ok := sa.(IPv6Addr)
if !ok {
return sortDeferDecision
}
ipv6aBigInt := new(big.Int)
ipv6aBigInt.Set(ipv6.Address)
ipv6bBigInt := new(big.Int)
ipv6bBigInt.Set(ipv6b.Address)
return ipv6aBigInt.Cmp(ipv6bBigInt)
}
// CmpPort follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its port is lower than arg
// - 0 if the SockAddr arg's port number is equal to the receiving IPv6Addr,
// regardless of type.
// - 1 If the argument should sort first.
func (ipv6 IPv6Addr) CmpPort(sa SockAddr) int {
var saPort IPPort
switch v := sa.(type) {
case IPv4Addr:
saPort = v.Port
case IPv6Addr:
saPort = v.Port
default:
return sortDeferDecision
}
switch {
case ipv6.Port == saPort:
return sortDeferDecision
case ipv6.Port < saPort:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// CmpRFC follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because it belongs to the RFC and its
// arg does not
// - 0 if the receiver and arg both belong to the same RFC or neither do.
// - 1 If the arg belongs to the RFC but receiver does not.
func (ipv6 IPv6Addr) CmpRFC(rfcNum uint, sa SockAddr) int {
recvInRFC := IsRFC(rfcNum, ipv6)
ipv6b, ok := sa.(IPv6Addr)
if !ok {
// If the receiver is part of the desired RFC and the SockAddr
// argument is not, sort receiver before the non-IPv6 SockAddr.
// Conversely, if the receiver is not part of the RFC, punt on
// sorting and leave it for the next sorter.
if recvInRFC {
return sortReceiverBeforeArg
} else {
return sortDeferDecision
}
}
argInRFC := IsRFC(rfcNum, ipv6b)
switch {
case (recvInRFC && argInRFC), (!recvInRFC && !argInRFC):
// If a and b both belong to the RFC, or neither belong to
// rfcNum, defer sorting to the next sorter.
return sortDeferDecision
case recvInRFC && !argInRFC:
return sortReceiverBeforeArg
default:
return sortArgBeforeReceiver
}
}
// Contains returns true if the SockAddr is contained within the receiver.
func (ipv6 IPv6Addr) Contains(sa SockAddr) bool {
ipv6b, ok := sa.(IPv6Addr)
if !ok {
return false
}
return ipv6.ContainsNetwork(ipv6b)
}
// ContainsAddress returns true if the IPv6Address is contained within the
// receiver.
func (ipv6 IPv6Addr) ContainsAddress(x IPv6Address) bool {
xAddr := IPv6Addr{
Address: x,
Mask: ipv6HostMask,
}
{
xIPv6 := xAddr.FirstUsable().(IPv6Addr)
yIPv6 := ipv6.FirstUsable().(IPv6Addr)
if xIPv6.CmpAddress(yIPv6) >= 1 {
return false
}
}
{
xIPv6 := xAddr.LastUsable().(IPv6Addr)
yIPv6 := ipv6.LastUsable().(IPv6Addr)
if xIPv6.CmpAddress(yIPv6) <= -1 {
return false
}
}
return true
}
// ContainsNetwork returns true if the network from IPv6Addr is contained within
// the receiver.
func (x IPv6Addr) ContainsNetwork(y IPv6Addr) bool {
{
xIPv6 := x.FirstUsable().(IPv6Addr)
yIPv6 := y.FirstUsable().(IPv6Addr)
if ret := xIPv6.CmpAddress(yIPv6); ret >= 1 {
return false
}
}
{
xIPv6 := x.LastUsable().(IPv6Addr)
yIPv6 := y.LastUsable().(IPv6Addr)
if ret := xIPv6.CmpAddress(yIPv6); ret <= -1 {
return false
}
}
return true
}
// DialPacketArgs returns the arguments required to be passed to
// net.DialUDP(). If the Mask of ipv6 is not a /128 or the Port is 0,
// DialPacketArgs() will fail. See Host() to create an IPv6Addr with its
// mask set to /128.
func (ipv6 IPv6Addr) DialPacketArgs() (network, dialArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 || ipv6.Port == 0 {
return "udp6", ""
}
return "udp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// DialStreamArgs returns the arguments required to be passed to
// net.DialTCP(). If the Mask of ipv6 is not a /128 or the Port is 0,
// DialStreamArgs() will fail. See Host() to create an IPv6Addr with its
// mask set to /128.
func (ipv6 IPv6Addr) DialStreamArgs() (network, dialArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 || ipv6.Port == 0 {
return "tcp6", ""
}
return "tcp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// Equal returns true if a SockAddr is equal to the receiving IPv4Addr.
func (ipv6a IPv6Addr) Equal(sa SockAddr) bool {
ipv6b, ok := sa.(IPv6Addr)
if !ok {
return false
}
if ipv6a.NetIP().String() != ipv6b.NetIP().String() {
return false
}
if ipv6a.NetIPNet().String() != ipv6b.NetIPNet().String() {
return false
}
if ipv6a.Port != ipv6b.Port {
return false
}
return true
}
// FirstUsable returns an IPv6Addr set to the first address following the
// network prefix. The first usable address in a network is normally the
// gateway and should not be used except by devices forwarding packets
// between two administratively distinct networks (i.e. a router). This
// function does not discriminate against first usable vs "first address that
// should be used." For example, FirstUsable() on "2001:0db8::0003/64" would
// return "2001:0db8::00011".
func (ipv6 IPv6Addr) FirstUsable() IPAddr {
return IPv6Addr{
Address: IPv6Address(ipv6.NetworkAddress()),
Mask: ipv6HostMask,
}
}
// Host returns a copy of ipv6 with its mask set to /128 so that it can be
// used by DialPacketArgs(), DialStreamArgs(), ListenPacketArgs(), or
// ListenStreamArgs().
func (ipv6 IPv6Addr) Host() IPAddr {
// Nothing should listen on a broadcast address.
return IPv6Addr{
Address: ipv6.Address,
Mask: ipv6HostMask,
Port: ipv6.Port,
}
}
// IPPort returns the Port number attached to the IPv6Addr
func (ipv6 IPv6Addr) IPPort() IPPort {
return ipv6.Port
}
// LastUsable returns the last address in a given network.
func (ipv6 IPv6Addr) LastUsable() IPAddr {
addr := new(big.Int)
addr.Set(ipv6.Address)
mask := new(big.Int)
mask.Set(ipv6.Mask)
negMask := new(big.Int)
negMask.Xor(ipv6HostMask, mask)
lastAddr := new(big.Int)
lastAddr.And(addr, mask)
lastAddr.Or(lastAddr, negMask)
return IPv6Addr{
Address: IPv6Address(lastAddr),
Mask: ipv6HostMask,
}
}
// ListenPacketArgs returns the arguments required to be passed to
// net.ListenUDP(). If the Mask of ipv6 is not a /128, ListenPacketArgs()
// will fail. See Host() to create an IPv6Addr with its mask set to /128.
func (ipv6 IPv6Addr) ListenPacketArgs() (network, listenArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 {
return "udp6", ""
}
return "udp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// ListenStreamArgs returns the arguments required to be passed to
// net.ListenTCP(). If the Mask of ipv6 is not a /128, ListenStreamArgs()
// will fail. See Host() to create an IPv6Addr with its mask set to /128.
func (ipv6 IPv6Addr) ListenStreamArgs() (network, listenArgs string) {
ipv6Mask := big.Int(*ipv6.Mask)
if ipv6Mask.Cmp(ipv6HostMask) != 0 {
return "tcp6", ""
}
return "tcp6", fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
// Maskbits returns the number of network mask bits in a given IPv6Addr. For
// example, the Maskbits() of "2001:0db8::0003/64" would return 64.
func (ipv6 IPv6Addr) Maskbits() int {
maskOnes, _ := ipv6.NetIPNet().Mask.Size()
return maskOnes
}
// MustIPv6Addr is a helper method that must return an IPv6Addr or panic on
// invalid input.
func MustIPv6Addr(addr string) IPv6Addr {
ipv6, err := NewIPv6Addr(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create an IPv6Addr from %+q: %v", addr, err))
}
return ipv6
}
// NetIP returns the address as a net.IP.
func (ipv6 IPv6Addr) NetIP() *net.IP {
return bigIntToNetIPv6(ipv6.Address)
}
// NetIPMask create a new net.IPMask from the IPv6Addr.
func (ipv6 IPv6Addr) NetIPMask() *net.IPMask {
ipv6Mask := make(net.IPMask, IPv6len)
m := big.Int(*ipv6.Mask)
copy(ipv6Mask, m.Bytes())
return &ipv6Mask
}
// Network returns a pointer to the net.IPNet within IPv4Addr receiver.
func (ipv6 IPv6Addr) NetIPNet() *net.IPNet {
ipv6net := &net.IPNet{}
ipv6net.IP = make(net.IP, IPv6len)
copy(ipv6net.IP, *ipv6.NetIP())
ipv6net.Mask = *ipv6.NetIPMask()
return ipv6net
}
// Network returns the network prefix or network address for a given network.
func (ipv6 IPv6Addr) Network() IPAddr {
return IPv6Addr{
Address: IPv6Address(ipv6.NetworkAddress()),
Mask: ipv6.Mask,
}
}
// NetworkAddress returns an IPv6Network of the IPv6Addr's network address.
func (ipv6 IPv6Addr) NetworkAddress() IPv6Network {
addr := new(big.Int)
addr.SetBytes((*ipv6.Address).Bytes())
mask := new(big.Int)
mask.SetBytes(*ipv6.NetIPMask())
netAddr := new(big.Int)
netAddr.And(addr, mask)
return IPv6Network(netAddr)
}
// Octets returns a slice of the 16 octets in an IPv6Addr's Address. The
// order of the bytes is big endian.
func (ipv6 IPv6Addr) Octets() []int {
x := make([]int, IPv6len)
for i, b := range *bigIntToNetIPv6(ipv6.Address) {
x[i] = int(b)
}
return x
}
// String returns a string representation of the IPv6Addr
func (ipv6 IPv6Addr) String() string {
if ipv6.Port != 0 {
return fmt.Sprintf("[%s]:%d", ipv6.NetIP().String(), ipv6.Port)
}
if ipv6.Maskbits() == 128 {
return ipv6.NetIP().String()
}
return fmt.Sprintf("%s/%d", ipv6.NetIP().String(), ipv6.Maskbits())
}
// Type is used as a type switch and returns TypeIPv6
func (IPv6Addr) Type() SockAddrType {
return TypeIPv6
}
// IPv6Attrs returns a list of attributes supported by the IPv6Addr type
func IPv6Attrs() []AttrName {
return ipv6AddrAttrs
}
// IPv6AddrAttr returns a string representation of an attribute for the given
// IPv6Addr.
func IPv6AddrAttr(ipv6 IPv6Addr, selector AttrName) string {
fn, found := ipv6AddrAttrMap[selector]
if !found {
return ""
}
return fn(ipv6)
}
// ipv6AddrInit is called once at init()
func ipv6AddrInit() {
// Sorted for human readability
ipv6AddrAttrs = []AttrName{
"size", // Same position as in IPv6 for output consistency
"uint128",
}
ipv6AddrAttrMap = map[AttrName]func(ipv6 IPv6Addr) string{
"size": func(ipv6 IPv6Addr) string {
netSize := big.NewInt(1)
netSize = netSize.Lsh(netSize, uint(IPv6len*8-ipv6.Maskbits()))
return netSize.Text(10)
},
"uint128": func(ipv6 IPv6Addr) string {
b := big.Int(*ipv6.Address)
return b.Text(10)
},
}
}
// bigIntToNetIPv6 is a helper function that correctly returns a net.IP with the
// correctly padded values.
func bigIntToNetIPv6(bi *big.Int) *net.IP {
x := make(net.IP, IPv6len)
ipv6Bytes := bi.Bytes()
// It's possibe for ipv6Bytes to be less than IPv6len bytes in size. If
// they are different sizes we to pad the size of response.
if len(ipv6Bytes) < IPv6len {
buf := new(bytes.Buffer)
buf.Grow(IPv6len)
for i := len(ipv6Bytes); i < IPv6len; i++ {
if err := binary.Write(buf, binary.BigEndian, byte(0)); err != nil {
panic(fmt.Sprintf("Unable to pad byte %d of input %v: %v", i, bi, err))
}
}
for _, b := range ipv6Bytes {
if err := binary.Write(buf, binary.BigEndian, b); err != nil {
panic(fmt.Sprintf("Unable to preserve endianness of input %v: %v", bi, err))
}
}
ipv6Bytes = buf.Bytes()
}
i := copy(x, ipv6Bytes)
if i != IPv6len {
panic("IPv6 wrong size")
}
return &x
}

View file

@ -0,0 +1,947 @@
package sockaddr
// ForwardingBlacklist is a faux RFC that includes a list of non-forwardable IP
// blocks.
const ForwardingBlacklist = 4294967295
// IsRFC tests to see if an SockAddr matches the specified RFC
func IsRFC(rfcNum uint, sa SockAddr) bool {
rfcNetMap := KnownRFCs()
rfcNets, ok := rfcNetMap[rfcNum]
if !ok {
return false
}
var contained bool
for _, rfcNet := range rfcNets {
if rfcNet.Contains(sa) {
contained = true
break
}
}
return contained
}
// KnownRFCs returns an initial set of known RFCs.
//
// NOTE (sean@): As this list evolves over time, please submit patches to keep
// this list current. If something isn't right, inquire, as it may just be a
// bug on my part. Some of the inclusions were based on my judgement as to what
// would be a useful value (e.g. RFC3330).
//
// Useful resources:
//
// * https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
// * https://www.iana.org/assignments/ipv6-unicast-address-assignments/ipv6-unicast-address-assignments.xhtml
// * https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
func KnownRFCs() map[uint]SockAddrs {
// NOTE(sean@): Multiple SockAddrs per RFC lend themselves well to a
// RADIX tree, but `ENOTIME`. Patches welcome.
return map[uint]SockAddrs{
919: {
// [RFC919] Broadcasting Internet Datagrams
MustIPv4Addr("255.255.255.255/32"), // [RFC1122], §7 Broadcast IP Addressing - Proposed Standards
},
1122: {
// [RFC1122] Requirements for Internet Hosts -- Communication Layers
MustIPv4Addr("0.0.0.0/8"), // [RFC1122], §3.2.1.3
MustIPv4Addr("127.0.0.0/8"), // [RFC1122], §3.2.1.3
},
1112: {
// [RFC1112] Host Extensions for IP Multicasting
MustIPv4Addr("224.0.0.0/4"), // [RFC1112], §4 Host Group Addresses
},
1918: {
// [RFC1918] Address Allocation for Private Internets
MustIPv4Addr("10.0.0.0/8"),
MustIPv4Addr("172.16.0.0/12"),
MustIPv4Addr("192.168.0.0/16"),
},
2544: {
// [RFC2544] Benchmarking Methodology for Network
// Interconnect Devices
MustIPv4Addr("198.18.0.0/15"),
},
2765: {
// [RFC2765] Stateless IP/ICMP Translation Algorithm
// (SIIT) (obsoleted by RFCs 6145, which itself was
// later obsoleted by 7915).
// [RFC2765], §2.1 Addresses
MustIPv6Addr("0:0:0:0:0:ffff:0:0/96"),
},
2928: {
// [RFC2928] Initial IPv6 Sub-TLA ID Assignments
MustIPv6Addr("2001::/16"), // Superblock
//MustIPv6Addr("2001:0000::/23"), // IANA
//MustIPv6Addr("2001:0200::/23"), // APNIC
//MustIPv6Addr("2001:0400::/23"), // ARIN
//MustIPv6Addr("2001:0600::/23"), // RIPE NCC
//MustIPv6Addr("2001:0800::/23"), // (future assignment)
// ...
//MustIPv6Addr("2001:FE00::/23"), // (future assignment)
},
3056: { // 6to4 address
// [RFC3056] Connection of IPv6 Domains via IPv4 Clouds
// [RFC3056], §2 IPv6 Prefix Allocation
MustIPv6Addr("2002::/16"),
},
3068: {
// [RFC3068] An Anycast Prefix for 6to4 Relay Routers
// (obsolete by RFC7526)
// [RFC3068], § 6to4 Relay anycast address
MustIPv4Addr("192.88.99.0/24"),
// [RFC3068], §2.5 6to4 IPv6 relay anycast address
//
// NOTE: /120 == 128-(32-24)
MustIPv6Addr("2002:c058:6301::/120"),
},
3171: {
// [RFC3171] IANA Guidelines for IPv4 Multicast Address Assignments
MustIPv4Addr("224.0.0.0/4"),
},
3330: {
// [RFC3330] Special-Use IPv4 Addresses
// Addresses in this block refer to source hosts on
// "this" network. Address 0.0.0.0/32 may be used as a
// source address for this host on this network; other
// addresses within 0.0.0.0/8 may be used to refer to
// specified hosts on this network [RFC1700, page 4].
MustIPv4Addr("0.0.0.0/8"),
// 10.0.0.0/8 - This block is set aside for use in
// private networks. Its intended use is documented in
// [RFC1918]. Addresses within this block should not
// appear on the public Internet.
MustIPv4Addr("10.0.0.0/8"),
// 14.0.0.0/8 - This block is set aside for assignments
// to the international system of Public Data Networks
// [RFC1700, page 181]. The registry of assignments
// within this block can be accessed from the "Public
// Data Network Numbers" link on the web page at
// http://www.iana.org/numbers.html. Addresses within
// this block are assigned to users and should be
// treated as such.
// 24.0.0.0/8 - This block was allocated in early 1996
// for use in provisioning IP service over cable
// television systems. Although the IANA initially was
// involved in making assignments to cable operators,
// this responsibility was transferred to American
// Registry for Internet Numbers (ARIN) in May 2001.
// Addresses within this block are assigned in the
// normal manner and should be treated as such.
// 39.0.0.0/8 - This block was used in the "Class A
// Subnet Experiment" that commenced in May 1995, as
// documented in [RFC1797]. The experiment has been
// completed and this block has been returned to the
// pool of addresses reserved for future allocation or
// assignment. This block therefore no longer has a
// special use and is subject to allocation to a
// Regional Internet Registry for assignment in the
// normal manner.
// 127.0.0.0/8 - This block is assigned for use as the Internet host
// loopback address. A datagram sent by a higher level protocol to an
// address anywhere within this block should loop back inside the host.
// This is ordinarily implemented using only 127.0.0.1/32 for loopback,
// but no addresses within this block should ever appear on any network
// anywhere [RFC1700, page 5].
MustIPv4Addr("127.0.0.0/8"),
// 128.0.0.0/16 - This block, corresponding to the
// numerically lowest of the former Class B addresses,
// was initially and is still reserved by the IANA.
// Given the present classless nature of the IP address
// space, the basis for the reservation no longer
// applies and addresses in this block are subject to
// future allocation to a Regional Internet Registry for
// assignment in the normal manner.
// 169.254.0.0/16 - This is the "link local" block. It
// is allocated for communication between hosts on a
// single link. Hosts obtain these addresses by
// auto-configuration, such as when a DHCP server may
// not be found.
MustIPv4Addr("169.254.0.0/16"),
// 172.16.0.0/12 - This block is set aside for use in
// private networks. Its intended use is documented in
// [RFC1918]. Addresses within this block should not
// appear on the public Internet.
MustIPv4Addr("172.16.0.0/12"),
// 191.255.0.0/16 - This block, corresponding to the numerically highest
// to the former Class B addresses, was initially and is still reserved
// by the IANA. Given the present classless nature of the IP address
// space, the basis for the reservation no longer applies and addresses
// in this block are subject to future allocation to a Regional Internet
// Registry for assignment in the normal manner.
// 192.0.0.0/24 - This block, corresponding to the
// numerically lowest of the former Class C addresses,
// was initially and is still reserved by the IANA.
// Given the present classless nature of the IP address
// space, the basis for the reservation no longer
// applies and addresses in this block are subject to
// future allocation to a Regional Internet Registry for
// assignment in the normal manner.
// 192.0.2.0/24 - This block is assigned as "TEST-NET" for use in
// documentation and example code. It is often used in conjunction with
// domain names example.com or example.net in vendor and protocol
// documentation. Addresses within this block should not appear on the
// public Internet.
MustIPv4Addr("192.0.2.0/24"),
// 192.88.99.0/24 - This block is allocated for use as 6to4 relay
// anycast addresses, according to [RFC3068].
MustIPv4Addr("192.88.99.0/24"),
// 192.168.0.0/16 - This block is set aside for use in private networks.
// Its intended use is documented in [RFC1918]. Addresses within this
// block should not appear on the public Internet.
MustIPv4Addr("192.168.0.0/16"),
// 198.18.0.0/15 - This block has been allocated for use
// in benchmark tests of network interconnect devices.
// Its use is documented in [RFC2544].
MustIPv4Addr("198.18.0.0/15"),
// 223.255.255.0/24 - This block, corresponding to the
// numerically highest of the former Class C addresses,
// was initially and is still reserved by the IANA.
// Given the present classless nature of the IP address
// space, the basis for the reservation no longer
// applies and addresses in this block are subject to
// future allocation to a Regional Internet Registry for
// assignment in the normal manner.
// 224.0.0.0/4 - This block, formerly known as the Class
// D address space, is allocated for use in IPv4
// multicast address assignments. The IANA guidelines
// for assignments from this space are described in
// [RFC3171].
MustIPv4Addr("224.0.0.0/4"),
// 240.0.0.0/4 - This block, formerly known as the Class E address
// space, is reserved. The "limited broadcast" destination address
// 255.255.255.255 should never be forwarded outside the (sub-)net of
// the source. The remainder of this space is reserved
// for future use. [RFC1700, page 4]
MustIPv4Addr("240.0.0.0/4"),
},
3849: {
// [RFC3849] IPv6 Address Prefix Reserved for Documentation
MustIPv6Addr("2001:db8::/32"), // [RFC3849], §4 IANA Considerations
},
3927: {
// [RFC3927] Dynamic Configuration of IPv4 Link-Local Addresses
MustIPv4Addr("169.254.0.0/16"), // [RFC3927], §2.1 Link-Local Address Selection
},
4038: {
// [RFC4038] Application Aspects of IPv6 Transition
// [RFC4038], §4.2. IPv6 Applications in a Dual-Stack Node
MustIPv6Addr("0:0:0:0:0:ffff::/96"),
},
4193: {
// [RFC4193] Unique Local IPv6 Unicast Addresses
MustIPv6Addr("fc00::/7"),
},
4291: {
// [RFC4291] IP Version 6 Addressing Architecture
// [RFC4291], §2.5.2 The Unspecified Address
MustIPv6Addr("::/128"),
// [RFC4291], §2.5.3 The Loopback Address
MustIPv6Addr("::1/128"),
// [RFC4291], §2.5.5.1. IPv4-Compatible IPv6 Address
MustIPv6Addr("::/96"),
// [RFC4291], §2.5.5.2. IPv4-Mapped IPv6 Address
MustIPv6Addr("::ffff:0:0/96"),
// [RFC4291], §2.5.6 Link-Local IPv6 Unicast Addresses
MustIPv6Addr("fe80::/10"),
// [RFC4291], §2.5.7 Site-Local IPv6 Unicast Addresses
// (depreciated)
MustIPv6Addr("fec0::/10"),
// [RFC4291], §2.7 Multicast Addresses
MustIPv6Addr("ff00::/8"),
// IPv6 Multicast Information.
//
// In the following "table" below, `ff0x` is replaced
// with the following values depending on the scope of
// the query:
//
// IPv6 Multicast Scopes:
// * ff00/9 // reserved
// * ff01/9 // interface-local
// * ff02/9 // link-local
// * ff03/9 // realm-local
// * ff04/9 // admin-local
// * ff05/9 // site-local
// * ff08/9 // organization-local
// * ff0e/9 // global
// * ff0f/9 // reserved
//
// IPv6 Multicast Addresses:
// * ff0x::2 // All routers
// * ff02::5 // OSPFIGP
// * ff02::6 // OSPFIGP Designated Routers
// * ff02::9 // RIP Routers
// * ff02::a // EIGRP Routers
// * ff02::d // All PIM Routers
// * ff02::1a // All RPL Routers
// * ff0x::fb // mDNSv6
// * ff0x::101 // All Network Time Protocol (NTP) servers
// * ff02::1:1 // Link Name
// * ff02::1:2 // All-dhcp-agents
// * ff02::1:3 // Link-local Multicast Name Resolution
// * ff05::1:3 // All-dhcp-servers
// * ff02::1:ff00:0/104 // Solicited-node multicast address.
// * ff02::2:ff00:0/104 // Node Information Queries
},
4380: {
// [RFC4380] Teredo: Tunneling IPv6 over UDP through
// Network Address Translations (NATs)
// [RFC4380], §2.6 Global Teredo IPv6 Service Prefix
MustIPv6Addr("2001:0000::/32"),
},
4773: {
// [RFC4773] Administration of the IANA Special Purpose IPv6 Address Block
MustIPv6Addr("2001:0000::/23"), // IANA
},
4843: {
// [RFC4843] An IPv6 Prefix for Overlay Routable Cryptographic Hash Identifiers (ORCHID)
MustIPv6Addr("2001:10::/28"), // [RFC4843], §7 IANA Considerations
},
5180: {
// [RFC5180] IPv6 Benchmarking Methodology for Network Interconnect Devices
MustIPv6Addr("2001:0200::/48"), // [RFC5180], §8 IANA Considerations
},
5735: {
// [RFC5735] Special Use IPv4 Addresses
MustIPv4Addr("192.0.2.0/24"), // TEST-NET-1
MustIPv4Addr("198.51.100.0/24"), // TEST-NET-2
MustIPv4Addr("203.0.113.0/24"), // TEST-NET-3
MustIPv4Addr("198.18.0.0/15"), // Benchmarks
},
5737: {
// [RFC5737] IPv4 Address Blocks Reserved for Documentation
MustIPv4Addr("192.0.2.0/24"), // TEST-NET-1
MustIPv4Addr("198.51.100.0/24"), // TEST-NET-2
MustIPv4Addr("203.0.113.0/24"), // TEST-NET-3
},
6052: {
// [RFC6052] IPv6 Addressing of IPv4/IPv6 Translators
MustIPv6Addr("64:ff9b::/96"), // [RFC6052], §2.1. Well-Known Prefix
},
6333: {
// [RFC6333] Dual-Stack Lite Broadband Deployments Following IPv4 Exhaustion
MustIPv4Addr("192.0.0.0/29"), // [RFC6333], §5.7 Well-Known IPv4 Address
},
6598: {
// [RFC6598] IANA-Reserved IPv4 Prefix for Shared Address Space
MustIPv4Addr("100.64.0.0/10"),
},
6666: {
// [RFC6666] A Discard Prefix for IPv6
MustIPv6Addr("0100::/64"),
},
6890: {
// [RFC6890] Special-Purpose IP Address Registries
// From "RFC6890 §2.2.1 Information Requirements":
/*
The IPv4 and IPv6 Special-Purpose Address Registries maintain the
following information regarding each entry:
o Address Block - A block of IPv4 or IPv6 addresses that has been
registered for a special purpose.
o Name - A descriptive name for the special-purpose address block.
o RFC - The RFC through which the special-purpose address block was
requested.
o Allocation Date - The date upon which the special-purpose address
block was allocated.
o Termination Date - The date upon which the allocation is to be
terminated. This field is applicable for limited-use allocations
only.
o Source - A boolean value indicating whether an address from the
allocated special-purpose address block is valid when used as the
source address of an IP datagram that transits two devices.
o Destination - A boolean value indicating whether an address from
the allocated special-purpose address block is valid when used as
the destination address of an IP datagram that transits two
devices.
o Forwardable - A boolean value indicating whether a router may
forward an IP datagram whose destination address is drawn from the
allocated special-purpose address block between external
interfaces.
o Global - A boolean value indicating whether an IP datagram whose
destination address is drawn from the allocated special-purpose
address block is forwardable beyond a specified administrative
domain.
o Reserved-by-Protocol - A boolean value indicating whether the
special-purpose address block is reserved by IP, itself. This
value is "TRUE" if the RFC that created the special-purpose
address block requires all compliant IP implementations to behave
in a special way when processing packets either to or from
addresses contained by the address block.
If the value of "Destination" is FALSE, the values of "Forwardable"
and "Global" must also be false.
*/
/*+----------------------+----------------------------+
* | Attribute | Value |
* +----------------------+----------------------------+
* | Address Block | 0.0.0.0/8 |
* | Name | "This host on this network"|
* | RFC | [RFC1122], Section 3.2.1.3 |
* | Allocation Date | September 1981 |
* | Termination Date | N/A |
* | Source | True |
* | Destination | False |
* | Forwardable | False |
* | Global | False |
* | Reserved-by-Protocol | True |
* +----------------------+----------------------------+*/
MustIPv4Addr("0.0.0.0/8"),
/*+----------------------+---------------+
* | Attribute | Value |
* +----------------------+---------------+
* | Address Block | 10.0.0.0/8 |
* | Name | Private-Use |
* | RFC | [RFC1918] |
* | Allocation Date | February 1996 |
* | Termination Date | N/A |
* | Source | True |
* | Destination | True |
* | Forwardable | True |
* | Global | False |
* | Reserved-by-Protocol | False |
* +----------------------+---------------+ */
MustIPv4Addr("10.0.0.0/8"),
/*+----------------------+----------------------+
| Attribute | Value |
+----------------------+----------------------+
| Address Block | 100.64.0.0/10 |
| Name | Shared Address Space |
| RFC | [RFC6598] |
| Allocation Date | April 2012 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------+*/
MustIPv4Addr("100.64.0.0/10"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 127.0.0.0/8 |
| Name | Loopback |
| RFC | [RFC1122], Section 3.2.1.3 |
| Allocation Date | September 1981 |
| Termination Date | N/A |
| Source | False [1] |
| Destination | False [1] |
| Forwardable | False [1] |
| Global | False [1] |
| Reserved-by-Protocol | True |
+----------------------+----------------------------+*/
// [1] Several protocols have been granted exceptions to
// this rule. For examples, see [RFC4379] and
// [RFC5884].
MustIPv4Addr("127.0.0.0/8"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 169.254.0.0/16 |
| Name | Link Local |
| RFC | [RFC3927] |
| Allocation Date | May 2005 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+----------------+*/
MustIPv4Addr("169.254.0.0/16"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 172.16.0.0/12 |
| Name | Private-Use |
| RFC | [RFC1918] |
| Allocation Date | February 1996 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
MustIPv4Addr("172.16.0.0/12"),
/*+----------------------+---------------------------------+
| Attribute | Value |
+----------------------+---------------------------------+
| Address Block | 192.0.0.0/24 [2] |
| Name | IETF Protocol Assignments |
| RFC | Section 2.1 of this document |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------------------------+*/
// [2] Not usable unless by virtue of a more specific
// reservation.
MustIPv4Addr("192.0.0.0/24"),
/*+----------------------+--------------------------------+
| Attribute | Value |
+----------------------+--------------------------------+
| Address Block | 192.0.0.0/29 |
| Name | IPv4 Service Continuity Prefix |
| RFC | [RFC6333], [RFC7335] |
| Allocation Date | June 2011 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+--------------------------------+*/
MustIPv4Addr("192.0.0.0/29"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 192.0.2.0/24 |
| Name | Documentation (TEST-NET-1) |
| RFC | [RFC5737] |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv4Addr("192.0.2.0/24"),
/*+----------------------+--------------------+
| Attribute | Value |
+----------------------+--------------------+
| Address Block | 192.88.99.0/24 |
| Name | 6to4 Relay Anycast |
| RFC | [RFC3068] |
| Allocation Date | June 2001 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | True |
| Reserved-by-Protocol | False |
+----------------------+--------------------+*/
MustIPv4Addr("192.88.99.0/24"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 192.168.0.0/16 |
| Name | Private-Use |
| RFC | [RFC1918] |
| Allocation Date | February 1996 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------+*/
MustIPv4Addr("192.168.0.0/16"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 198.18.0.0/15 |
| Name | Benchmarking |
| RFC | [RFC2544] |
| Allocation Date | March 1999 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
MustIPv4Addr("198.18.0.0/15"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 198.51.100.0/24 |
| Name | Documentation (TEST-NET-2) |
| RFC | [RFC5737] |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv4Addr("198.51.100.0/24"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 203.0.113.0/24 |
| Name | Documentation (TEST-NET-3) |
| RFC | [RFC5737] |
| Allocation Date | January 2010 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv4Addr("203.0.113.0/24"),
/*+----------------------+----------------------+
| Attribute | Value |
+----------------------+----------------------+
| Address Block | 240.0.0.0/4 |
| Name | Reserved |
| RFC | [RFC1112], Section 4 |
| Allocation Date | August 1989 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+----------------------+*/
MustIPv4Addr("240.0.0.0/4"),
/*+----------------------+----------------------+
| Attribute | Value |
+----------------------+----------------------+
| Address Block | 255.255.255.255/32 |
| Name | Limited Broadcast |
| RFC | [RFC0919], Section 7 |
| Allocation Date | October 1984 |
| Termination Date | N/A |
| Source | False |
| Destination | True |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------+*/
MustIPv4Addr("255.255.255.255/32"),
/*+----------------------+------------------+
| Attribute | Value |
+----------------------+------------------+
| Address Block | ::1/128 |
| Name | Loopback Address |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+------------------+*/
MustIPv6Addr("::1/128"),
/*+----------------------+---------------------+
| Attribute | Value |
+----------------------+---------------------+
| Address Block | ::/128 |
| Name | Unspecified Address |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | True |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+---------------------+*/
MustIPv6Addr("::/128"),
/*+----------------------+---------------------+
| Attribute | Value |
+----------------------+---------------------+
| Address Block | 64:ff9b::/96 |
| Name | IPv4-IPv6 Translat. |
| RFC | [RFC6052] |
| Allocation Date | October 2010 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | True |
| Reserved-by-Protocol | False |
+----------------------+---------------------+*/
MustIPv6Addr("64:ff9b::/96"),
/*+----------------------+---------------------+
| Attribute | Value |
+----------------------+---------------------+
| Address Block | ::ffff:0:0/96 |
| Name | IPv4-mapped Address |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+---------------------+*/
MustIPv6Addr("::ffff:0:0/96"),
/*+----------------------+----------------------------+
| Attribute | Value |
+----------------------+----------------------------+
| Address Block | 100::/64 |
| Name | Discard-Only Address Block |
| RFC | [RFC6666] |
| Allocation Date | June 2012 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------------------+*/
MustIPv6Addr("100::/64"),
/*+----------------------+---------------------------+
| Attribute | Value |
+----------------------+---------------------------+
| Address Block | 2001::/23 |
| Name | IETF Protocol Assignments |
| RFC | [RFC2928] |
| Allocation Date | September 2000 |
| Termination Date | N/A |
| Source | False[1] |
| Destination | False[1] |
| Forwardable | False[1] |
| Global | False[1] |
| Reserved-by-Protocol | False |
+----------------------+---------------------------+*/
// [1] Unless allowed by a more specific allocation.
MustIPv6Addr("2001::/16"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 2001::/32 |
| Name | TEREDO |
| RFC | [RFC4380] |
| Allocation Date | January 2006 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001::/16"),
/*+----------------------+----------------+
| Attribute | Value |
+----------------------+----------------+
| Address Block | 2001:2::/48 |
| Name | Benchmarking |
| RFC | [RFC5180] |
| Allocation Date | April 2008 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+----------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001:2::/48"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 2001:db8::/32 |
| Name | Documentation |
| RFC | [RFC3849] |
| Allocation Date | July 2004 |
| Termination Date | N/A |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001:db8::/32"),
/*+----------------------+--------------+
| Attribute | Value |
+----------------------+--------------+
| Address Block | 2001:10::/28 |
| Name | ORCHID |
| RFC | [RFC4843] |
| Allocation Date | March 2007 |
| Termination Date | March 2014 |
| Source | False |
| Destination | False |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+--------------+*/
// Covered by previous entry, included for completeness.
//
// MustIPv6Addr("2001:10::/28"),
/*+----------------------+---------------+
| Attribute | Value |
+----------------------+---------------+
| Address Block | 2002::/16 [2] |
| Name | 6to4 |
| RFC | [RFC3056] |
| Allocation Date | February 2001 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | N/A [2] |
| Reserved-by-Protocol | False |
+----------------------+---------------+*/
// [2] See [RFC3056] for details.
MustIPv6Addr("2002::/16"),
/*+----------------------+--------------+
| Attribute | Value |
+----------------------+--------------+
| Address Block | fc00::/7 |
| Name | Unique-Local |
| RFC | [RFC4193] |
| Allocation Date | October 2005 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | True |
| Global | False |
| Reserved-by-Protocol | False |
+----------------------+--------------+*/
MustIPv6Addr("fc00::/7"),
/*+----------------------+-----------------------+
| Attribute | Value |
+----------------------+-----------------------+
| Address Block | fe80::/10 |
| Name | Linked-Scoped Unicast |
| RFC | [RFC4291] |
| Allocation Date | February 2006 |
| Termination Date | N/A |
| Source | True |
| Destination | True |
| Forwardable | False |
| Global | False |
| Reserved-by-Protocol | True |
+----------------------+-----------------------+*/
MustIPv6Addr("fe80::/10"),
},
7335: {
// [RFC7335] IPv4 Service Continuity Prefix
MustIPv4Addr("192.0.0.0/29"), // [RFC7335], §6 IANA Considerations
},
ForwardingBlacklist: { // Pseudo-RFC
// Blacklist of non-forwardable IP blocks taken from RFC6890
//
// TODO: the attributes for forwardable should be
// searcahble and embedded in the main list of RFCs
// above.
MustIPv4Addr("0.0.0.0/8"),
MustIPv4Addr("127.0.0.0/8"),
MustIPv4Addr("169.254.0.0/16"),
MustIPv4Addr("192.0.0.0/24"),
MustIPv4Addr("192.0.2.0/24"),
MustIPv4Addr("198.51.100.0/24"),
MustIPv4Addr("203.0.113.0/24"),
MustIPv4Addr("240.0.0.0/4"),
MustIPv4Addr("255.255.255.255/32"),
MustIPv6Addr("::1/128"),
MustIPv6Addr("::/128"),
MustIPv6Addr("::ffff:0:0/96"),
// There is no way of expressing a whitelist per RFC2928
// atm without creating a negative mask, which I don't
// want to do atm.
//MustIPv6Addr("2001::/23"),
MustIPv6Addr("2001:db8::/32"),
MustIPv6Addr("2001:10::/28"),
MustIPv6Addr("fe80::/10"),
},
}
}
// VisitAllRFCs iterates over all known RFCs and calls the visitor
func VisitAllRFCs(fn func(rfcNum uint, sockaddrs SockAddrs)) {
rfcNetMap := KnownRFCs()
// Blacklist of faux-RFCs. Don't show the world that we're abusing the
// RFC system in this library.
rfcBlacklist := map[uint]struct{}{
ForwardingBlacklist: {},
}
for rfcNum, sas := range rfcNetMap {
if _, found := rfcBlacklist[rfcNum]; !found {
fn(rfcNum, sas)
}
}
}

View file

@ -0,0 +1,19 @@
package sockaddr
// RouteInterface specifies an interface for obtaining memoized route table and
// network information from a given OS.
type RouteInterface interface {
// GetDefaultInterfaceName returns the name of the interface that has a
// default route or an error and an empty string if a problem was
// encountered.
GetDefaultInterfaceName() (string, error)
}
// VisitCommands visits each command used by the platform-specific RouteInfo
// implementation.
func (ri routeInfo) VisitCommands(fn func(name string, cmd []string)) {
for k, v := range ri.cmds {
cmds := append([]string(nil), v...)
fn(k, cmds)
}
}

View file

@ -0,0 +1,36 @@
// +build darwin dragonfly freebsd netbsd openbsd
package sockaddr
import "os/exec"
var cmds map[string][]string = map[string][]string{
"route": {"/sbin/route", "-n", "get", "default"},
}
type routeInfo struct {
cmds map[string][]string
}
// NewRouteInfo returns a BSD-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
return routeInfo{
cmds: cmds,
}, 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["route"][0], cmds["route"][1:]...).Output()
if err != nil {
return "", err
}
var ifName string
if ifName, err = parseDefaultIfNameFromRoute(string(out)); err != nil {
return "", err
}
return ifName, nil
}

View file

@ -0,0 +1,10 @@
// +build android nacl plan9
package sockaddr
import "errors"
// getDefaultIfName is the default interface function for unsupported platforms.
func getDefaultIfName() (string, error) {
return "", errors.New("No default interface found (unsupported platform)")
}

View file

@ -0,0 +1,37 @@
package sockaddr
import (
"errors"
"os/exec"
)
var cmds map[string][]string = map[string][]string{
"ip": {"/sbin/ip", "route"},
}
type routeInfo struct {
cmds map[string][]string
}
// NewRouteInfo returns a Linux-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
return routeInfo{
cmds: cmds,
}, 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()
if err != nil {
return "", err
}
var ifName string
if ifName, err = parseDefaultIfNameFromIPCmd(string(out)); err != nil {
return "", errors.New("No default interface found")
}
return ifName, nil
}

View file

@ -0,0 +1,37 @@
package sockaddr
import (
"errors"
"os/exec"
)
var cmds map[string][]string = map[string][]string{
"route": {"/usr/sbin/route", "-n", "get", "default"},
}
type routeInfo struct {
cmds map[string][]string
}
// NewRouteInfo returns a BSD-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
return routeInfo{
cmds: cmds,
}, 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["route"][0], cmds["route"][1:]...).Output()
if err != nil {
return "", err
}
var ifName string
if ifName, err = parseDefaultIfNameFromRoute(string(out)); err != nil {
return "", errors.New("No default interface found")
}
return ifName, nil
}

View file

@ -0,0 +1,41 @@
package sockaddr
import "os/exec"
var cmds map[string][]string = map[string][]string{
"netstat": {"netstat", "-rn"},
"ipconfig": {"ipconfig"},
}
type routeInfo struct {
cmds map[string][]string
}
// NewRouteInfo returns a BSD-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
return routeInfo{
cmds: cmds,
}, nil
}
// GetDefaultInterfaceName returns the interface name attached to the default
// route on the default interface.
func (ri routeInfo) GetDefaultInterfaceName() (string, error) {
ifNameOut, err := exec.Command(cmds["netstat"][0], cmds["netstat"][1:]...).Output()
if err != nil {
return "", err
}
ipconfigOut, err := exec.Command(cmds["ipconfig"][0], cmds["ipconfig"][1:]...).Output()
if err != nil {
return "", err
}
ifName, err := parseDefaultIfNameWindows(string(ifNameOut), string(ipconfigOut))
if err != nil {
return "", err
}
return ifName, nil
}

View file

@ -0,0 +1,178 @@
package sockaddr
import (
"fmt"
"strings"
)
type SockAddrType int
type AttrName string
const (
TypeUnknown SockAddrType = 0x0
TypeUnix = 0x1
TypeIPv4 = 0x2
TypeIPv6 = 0x4
// TypeIP is the union of TypeIPv4 and TypeIPv6
TypeIP = 0x6
)
type SockAddr interface {
// CmpRFC returns 0 if SockAddr exactly matches one of the matched RFC
// networks, -1 if the receiver is contained within the RFC network, or
// 1 if the address is not contained within the RFC.
CmpRFC(rfcNum uint, sa SockAddr) int
// Contains returns true if the SockAddr arg is contained within the
// receiver
Contains(SockAddr) bool
// Equal allows for the comparison of two SockAddrs
Equal(SockAddr) bool
DialPacketArgs() (string, string)
DialStreamArgs() (string, string)
ListenPacketArgs() (string, string)
ListenStreamArgs() (string, string)
// String returns the string representation of SockAddr
String() string
// Type returns the SockAddrType
Type() SockAddrType
}
// sockAddrAttrMap is a map of the SockAddr type-specific attributes.
var sockAddrAttrMap map[AttrName]func(SockAddr) string
var sockAddrAttrs []AttrName
func init() {
sockAddrInit()
}
// New creates a new SockAddr from the string. The order in which New()
// attempts to construct a SockAddr is: IPv4Addr, IPv6Addr, SockAddrUnix.
//
// NOTE: New() relies on the heuristic wherein if the path begins with either a
// '.' or '/' character before creating a new UnixSock. For UNIX sockets that
// are absolute paths or are nested within a sub-directory, this works as
// expected, however if the UNIX socket is contained in the current working
// directory, this will fail unless the path begins with "./"
// (e.g. "./my-local-socket"). Calls directly to NewUnixSock() do not suffer
// this limitation. Invalid IP addresses such as "256.0.0.0/-1" will run afoul
// of this heuristic and be assumed to be a valid UNIX socket path (which they
// are, but it is probably not what you want and you won't realize it until you
// stat(2) the file system to discover it doesn't exist).
func NewSockAddr(s string) (SockAddr, error) {
ipv4Addr, err := NewIPv4Addr(s)
if err == nil {
return ipv4Addr, nil
}
ipv6Addr, err := NewIPv6Addr(s)
if err == nil {
return ipv6Addr, nil
}
// Check to make sure the string begins with either a '.' or '/', or
// contains a '/'.
if len(s) > 1 && (strings.IndexAny(s[0:1], "./") != -1 || strings.IndexByte(s, '/') != -1) {
unixSock, err := NewUnixSock(s)
if err == nil {
return unixSock, nil
}
}
return nil, fmt.Errorf("Unable to convert %q to an IPv4 or IPv6 address, or a UNIX Socket", s)
}
// ToIPAddr returns an IPAddr type or nil if the type conversion fails.
func ToIPAddr(sa SockAddr) *IPAddr {
ipa, ok := sa.(IPAddr)
if !ok {
return nil
}
return &ipa
}
// ToIPv4Addr returns an IPv4Addr type or nil if the type conversion fails.
func ToIPv4Addr(sa SockAddr) *IPv4Addr {
switch v := sa.(type) {
case IPv4Addr:
return &v
default:
return nil
}
}
// ToIPv6Addr returns an IPv6Addr type or nil if the type conversion fails.
func ToIPv6Addr(sa SockAddr) *IPv6Addr {
switch v := sa.(type) {
case IPv6Addr:
return &v
default:
return nil
}
}
// ToUnixSock returns a UnixSock type or nil if the type conversion fails.
func ToUnixSock(sa SockAddr) *UnixSock {
switch v := sa.(type) {
case UnixSock:
return &v
default:
return nil
}
}
// SockAddrAttr returns a string representation of an attribute for the given
// SockAddr.
func SockAddrAttr(sa SockAddr, selector AttrName) string {
fn, found := sockAddrAttrMap[selector]
if !found {
return ""
}
return fn(sa)
}
// String() for SockAddrType returns a string representation of the
// SockAddrType (e.g. "IPv4", "IPv6", "UNIX", "IP", or "unknown").
func (sat SockAddrType) String() string {
switch sat {
case TypeIPv4:
return "IPv4"
case TypeIPv6:
return "IPv6"
// There is no concrete "IP" type. Leaving here as a reminder.
// case TypeIP:
// return "IP"
case TypeUnix:
return "UNIX"
default:
panic("unsupported type")
}
}
// sockAddrInit is called once at init()
func sockAddrInit() {
sockAddrAttrs = []AttrName{
"type", // type should be first
"string",
}
sockAddrAttrMap = map[AttrName]func(sa SockAddr) string{
"string": func(sa SockAddr) string {
return sa.String()
},
"type": func(sa SockAddr) string {
return sa.Type().String()
},
}
}
// UnixSockAttrs returns a list of attributes supported by the UnixSock type
func SockAddrAttrs() []AttrName {
return sockAddrAttrs
}

View file

@ -0,0 +1,193 @@
package sockaddr
import (
"bytes"
"sort"
)
// SockAddrs is a slice of SockAddrs
type SockAddrs []SockAddr
func (s SockAddrs) Len() int { return len(s) }
func (s SockAddrs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// CmpAddrFunc is the function signature that must be met to be used in the
// OrderedAddrBy multiAddrSorter
type CmpAddrFunc func(p1, p2 *SockAddr) int
// multiAddrSorter implements the Sort interface, sorting the SockAddrs within.
type multiAddrSorter struct {
addrs SockAddrs
cmp []CmpAddrFunc
}
// Sort sorts the argument slice according to the Cmp functions passed to
// OrderedAddrBy.
func (ms *multiAddrSorter) Sort(sockAddrs SockAddrs) {
ms.addrs = sockAddrs
sort.Sort(ms)
}
// OrderedAddrBy sorts SockAddr by the list of sort function pointers.
func OrderedAddrBy(cmpFuncs ...CmpAddrFunc) *multiAddrSorter {
return &multiAddrSorter{
cmp: cmpFuncs,
}
}
// Len is part of sort.Interface.
func (ms *multiAddrSorter) Len() int {
return len(ms.addrs)
}
// Less is part of sort.Interface. It is implemented by looping along the
// Cmp() functions until it finds a comparison that is either less than,
// equal to, or greater than.
func (ms *multiAddrSorter) Less(i, j int) bool {
p, q := &ms.addrs[i], &ms.addrs[j]
// Try all but the last comparison.
var k int
for k = 0; k < len(ms.cmp)-1; k++ {
cmp := ms.cmp[k]
x := cmp(p, q)
switch x {
case -1:
// p < q, so we have a decision.
return true
case 1:
// p > q, so we have a decision.
return false
}
// p == q; try the next comparison.
}
// All comparisons to here said "equal", so just return whatever the
// final comparison reports.
switch ms.cmp[k](p, q) {
case -1:
return true
case 1:
return false
default:
// Still a tie! Now what?
return false
}
}
// Swap is part of sort.Interface.
func (ms *multiAddrSorter) Swap(i, j int) {
ms.addrs[i], ms.addrs[j] = ms.addrs[j], ms.addrs[i]
}
const (
// NOTE (sean@): These constants are here for code readability only and
// are sprucing up the code for readability purposes. Some of the
// Cmp*() variants have confusing logic (especially when dealing with
// mixed-type comparisons) and this, I think, has made it easier to grok
// the code faster.
sortReceiverBeforeArg = -1
sortDeferDecision = 0
sortArgBeforeReceiver = 1
)
// AscAddress is a sorting function to sort SockAddrs by their respective
// address type. Non-equal types are deferred in the sort.
func AscAddress(p1Ptr, p2Ptr *SockAddr) int {
p1 := *p1Ptr
p2 := *p2Ptr
switch v := p1.(type) {
case IPv4Addr:
return v.CmpAddress(p2)
case IPv6Addr:
return v.CmpAddress(p2)
case UnixSock:
return v.CmpAddress(p2)
default:
return sortDeferDecision
}
}
// AscPort is a sorting function to sort SockAddrs by their respective address
// type. Non-equal types are deferred in the sort.
func AscPort(p1Ptr, p2Ptr *SockAddr) int {
p1 := *p1Ptr
p2 := *p2Ptr
switch v := p1.(type) {
case IPv4Addr:
return v.CmpPort(p2)
case IPv6Addr:
return v.CmpPort(p2)
default:
return sortDeferDecision
}
}
// AscPrivate is a sorting function to sort "more secure" private values before
// "more public" values. Both IPv4 and IPv6 are compared against RFC6890
// (RFC6890 includes, and is not limited to, RFC1918 and RFC6598 for IPv4, and
// IPv6 includes RFC4193).
func AscPrivate(p1Ptr, p2Ptr *SockAddr) int {
p1 := *p1Ptr
p2 := *p2Ptr
switch v := p1.(type) {
case IPv4Addr, IPv6Addr:
return v.CmpRFC(6890, p2)
default:
return sortDeferDecision
}
}
// AscNetworkSize is a sorting function to sort SockAddrs based on their network
// size. Non-equal types are deferred in the sort.
func AscNetworkSize(p1Ptr, p2Ptr *SockAddr) int {
p1 := *p1Ptr
p2 := *p2Ptr
p1Type := p1.Type()
p2Type := p2.Type()
// Network size operations on non-IP types make no sense
if p1Type != p2Type && p1Type != TypeIP {
return sortDeferDecision
}
ipA := p1.(IPAddr)
ipB := p2.(IPAddr)
return bytes.Compare([]byte(*ipA.NetIPMask()), []byte(*ipB.NetIPMask()))
}
// AscType is a sorting function to sort "more secure" types before
// "less-secure" types.
func AscType(p1Ptr, p2Ptr *SockAddr) int {
p1 := *p1Ptr
p2 := *p2Ptr
p1Type := p1.Type()
p2Type := p2.Type()
switch {
case p1Type < p2Type:
return sortReceiverBeforeArg
case p1Type == p2Type:
return sortDeferDecision
case p1Type > p2Type:
return sortArgBeforeReceiver
default:
return sortDeferDecision
}
}
// FilterByType returns two lists: a list of matched and unmatched SockAddrs
func (sas SockAddrs) FilterByType(type_ SockAddrType) (matched, excluded SockAddrs) {
matched = make(SockAddrs, 0, len(sas))
excluded = make(SockAddrs, 0, len(sas))
for _, sa := range sas {
if sa.Type()&type_ != 0 {
matched = append(matched, sa)
} else {
excluded = append(excluded, sa)
}
}
return matched, excluded
}

View file

@ -0,0 +1,135 @@
package sockaddr
import (
"fmt"
"strings"
)
type UnixSock struct {
SockAddr
path string
}
type UnixSocks []*UnixSock
// unixAttrMap is a map of the UnixSockAddr type-specific attributes.
var unixAttrMap map[AttrName]func(UnixSock) string
var unixAttrs []AttrName
func init() {
unixAttrInit()
}
// NewUnixSock creates an UnixSock from a string path. String can be in the
// form of either URI-based string (e.g. `file:///etc/passwd`), an absolute
// path (e.g. `/etc/passwd`), or a relative path (e.g. `./foo`).
func NewUnixSock(s string) (ret UnixSock, err error) {
ret.path = s
return ret, nil
}
// CmpAddress follows the Cmp() standard protocol and returns:
//
// - -1 If the receiver should sort first because its name lexically sorts before arg
// - 0 if the SockAddr arg is not a UnixSock, or is a UnixSock with the same path.
// - 1 If the argument should sort first.
func (us UnixSock) CmpAddress(sa SockAddr) int {
usb, ok := sa.(UnixSock)
if !ok {
return sortDeferDecision
}
return strings.Compare(us.Path(), usb.Path())
}
// DialPacketArgs returns the arguments required to be passed to net.DialUnix()
// with the `unixgram` network type.
func (us UnixSock) DialPacketArgs() (network, dialArgs string) {
return "unixgram", us.path
}
// DialStreamArgs returns the arguments required to be passed to net.DialUnix()
// with the `unix` network type.
func (us UnixSock) DialStreamArgs() (network, dialArgs string) {
return "unix", us.path
}
// Equal returns true if a SockAddr is equal to the receiving UnixSock.
func (us UnixSock) Equal(sa SockAddr) bool {
usb, ok := sa.(UnixSock)
if !ok {
return false
}
if us.Path() != usb.Path() {
return false
}
return true
}
// ListenPacketArgs returns the arguments required to be passed to
// net.ListenUnixgram() with the `unixgram` network type.
func (us UnixSock) ListenPacketArgs() (network, dialArgs string) {
return "unixgram", us.path
}
// ListenStreamArgs returns the arguments required to be passed to
// net.ListenUnix() with the `unix` network type.
func (us UnixSock) ListenStreamArgs() (network, dialArgs string) {
return "unix", us.path
}
// MustUnixSock is a helper method that must return an UnixSock or panic on
// invalid input.
func MustUnixSock(addr string) UnixSock {
us, err := NewUnixSock(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create a UnixSock from %+q: %v", addr, err))
}
return us
}
// Path returns the given path of the UnixSock
func (us UnixSock) Path() string {
return us.path
}
// String returns the path of the UnixSock
func (us UnixSock) String() string {
return fmt.Sprintf("%+q", us.path)
}
// Type is used as a type switch and returns TypeUnix
func (UnixSock) Type() SockAddrType {
return TypeUnix
}
// UnixSockAttrs returns a list of attributes supported by the UnixSockAddr type
func UnixSockAttrs() []AttrName {
return unixAttrs
}
// UnixSockAttr returns a string representation of an attribute for the given
// UnixSock.
func UnixSockAttr(us UnixSock, attrName AttrName) string {
fn, found := unixAttrMap[attrName]
if !found {
return ""
}
return fn(us)
}
// unixAttrInit is called once at init()
func unixAttrInit() {
// Sorted for human readability
unixAttrs = []AttrName{
"path",
}
unixAttrMap = map[AttrName]func(us UnixSock) string{
"path": func(us UnixSock) string {
return us.Path()
},
}
}

View file

@ -82,7 +82,7 @@ least one existing member in order to join the cluster. The new member
does a full state sync with the existing member over TCP and begins gossiping its
existence to the cluster.
Gossip is done over UDP to a with a configurable but fixed fanout and interval.
Gossip is done over UDP with a configurable but fixed fanout and interval.
This ensures that network usage is constant with regards to number of nodes, as opposed to
exponential growth that can occur with traditional heartbeat mechanisms.
Complete state exchanges with a random node are done periodically over

View file

@ -0,0 +1,69 @@
package memberlist
import (
"sync"
"time"
"github.com/armon/go-metrics"
)
// awareness manages a simple metric for tracking the estimated health of the
// local node. Health is primary the node's ability to respond in the soft
// real-time manner required for correct health checking of other nodes in the
// cluster.
type awareness struct {
sync.RWMutex
// max is the upper threshold for the timeout scale (the score will be
// constrained to be from 0 <= score < max).
max int
// score is the current awareness score. Lower values are healthier and
// zero is the minimum value.
score int
}
// newAwareness returns a new awareness object.
func newAwareness(max int) *awareness {
return &awareness{
max: max,
score: 0,
}
}
// ApplyDelta takes the given delta and applies it to the score in a thread-safe
// manner. It also enforces a floor of zero and a max of max, so deltas may not
// change the overall score if it's railed at one of the extremes.
func (a *awareness) ApplyDelta(delta int) {
a.Lock()
initial := a.score
a.score += delta
if a.score < 0 {
a.score = 0
} else if a.score > (a.max - 1) {
a.score = (a.max - 1)
}
final := a.score
a.Unlock()
if initial != final {
metrics.SetGauge([]string{"memberlist", "health", "score"}, float32(final))
}
}
// GetHealthScore returns the raw health score.
func (a *awareness) GetHealthScore() int {
a.RLock()
score := a.score
a.RUnlock()
return score
}
// ScaleTimeout takes the given duration and scales it based on the current
// score. Less healthyness will lead to longer timeouts.
func (a *awareness) ScaleTimeout(timeout time.Duration) time.Duration {
a.RLock()
score := a.score
a.RUnlock()
return timeout * (time.Duration(score) + 1)
}

View file

@ -11,10 +11,15 @@ type Config struct {
// The name of this node. This must be unique in the cluster.
Name string
// Transport is a hook for providing custom code to communicate with
// other nodes. If this is left nil, then memberlist will by default
// make a NetTransport using BindAddr and BindPort from this structure.
Transport Transport
// Configuration related to what address to bind to and ports to
// listen on. The port is used for both UDP and TCP gossip.
// It is assumed other nodes are running on this port, but they
// do not need to.
// listen on. The port is used for both UDP and TCP gossip. It is
// assumed other nodes are running on this port, but they do not need
// to.
BindAddr string
BindPort int
@ -28,8 +33,11 @@ type Config struct {
// ProtocolVersionMax.
ProtocolVersion uint8
// TCPTimeout is the timeout for establishing a TCP connection with
// a remote node for a full state sync.
// TCPTimeout is the timeout for establishing a stream connection with
// a remote node for a full state sync, and for stream read and write
// operations. This is a legacy name for backwards compatibility, but
// should really be called StreamTimeout now that we have generalized
// the transport.
TCPTimeout time.Duration
// IndirectChecks is the number of nodes that will be asked to perform
@ -63,6 +71,23 @@ type Config struct {
// still alive.
SuspicionMult int
// SuspicionMaxTimeoutMult is the multiplier applied to the
// SuspicionTimeout used as an upper bound on detection time. This max
// timeout is calculated using the formula:
//
// SuspicionMaxTimeout = SuspicionMaxTimeoutMult * SuspicionTimeout
//
// If everything is working properly, confirmations from other nodes will
// accelerate suspicion timers in a manner which will cause the timeout
// to reach the base SuspicionTimeout before that elapses, so this value
// will typically only come into play if a node is experiencing issues
// communicating with other nodes. It should be set to a something fairly
// large so that a node having problems will have a lot of chances to
// recover before falsely declaring other nodes as failed, but short
// enough for a legitimately isolated node to still make progress marking
// nodes failed in a reasonable amount of time.
SuspicionMaxTimeoutMult int
// PushPullInterval is the interval between complete state syncs.
// Complete state syncs are done with a single node over TCP and are
// quite expensive relative to standard gossiped messages. Setting this
@ -91,6 +116,11 @@ type Config struct {
// indirect UDP pings.
DisableTcpPings bool
// AwarenessMaxMultiplier will increase the probe interval if the node
// becomes aware that it might be degraded and not meeting the soft real
// time requirements to reliably probe other nodes.
AwarenessMaxMultiplier int
// GossipInterval and GossipNodes are used to configure the gossip
// behavior of memberlist.
//
@ -104,8 +134,12 @@ type Config struct {
// per GossipInterval. Increasing this number causes the gossip messages
// to propagate across the cluster more quickly at the expense of
// increased bandwidth.
GossipInterval time.Duration
GossipNodes int
//
// GossipToTheDeadTime is the interval after which a node has died that
// we will still try to gossip to it. This gives it a chance to refute.
GossipInterval time.Duration
GossipNodes int
GossipToTheDeadTime time.Duration
// EnableCompression is used to control message compression. This can
// be used to reduce bandwidth usage at the cost of slightly more CPU
@ -157,6 +191,20 @@ type Config struct {
// behavior for using LogOutput. You cannot specify both LogOutput and Logger
// at the same time.
Logger *log.Logger
// Size of Memberlist's internal channel which handles UDP messages. The
// size of this determines the size of the queue which Memberlist will keep
// while UDP messages are handled.
HandoffQueueDepth int
// Maximum number of bytes that memberlist will put in a packet (this
// will be for UDP packets by default with a NetTransport). A safe value
// for this is typically 1400 bytes (which is the default). However,
// depending on your network's MTU (Maximum Transmission Unit) you may
// be able to increase this to get more content into each gossip packet.
// This is a legacy name for backward compatibility but should really be
// called PacketBufferSize now that we have generalized the transport.
UDPBufferSize int
}
// DefaultLANConfig returns a sane set of configurations for Memberlist.
@ -168,23 +216,26 @@ type Config struct {
func DefaultLANConfig() *Config {
hostname, _ := os.Hostname()
return &Config{
Name: hostname,
BindAddr: "0.0.0.0",
BindPort: 7946,
AdvertiseAddr: "",
AdvertisePort: 7946,
ProtocolVersion: ProtocolVersion2Compatible,
TCPTimeout: 10 * time.Second, // Timeout after 10 seconds
IndirectChecks: 3, // Use 3 nodes for the indirect ping
RetransmitMult: 4, // Retransmit a message 4 * log(N+1) nodes
SuspicionMult: 5, // Suspect a node for 5 * log(N+1) * Interval
PushPullInterval: 30 * time.Second, // Low frequency
ProbeTimeout: 500 * time.Millisecond, // Reasonable RTT time for LAN
ProbeInterval: 1 * time.Second, // Failure check every second
DisableTcpPings: false, // TCP pings are safe, even with mixed versions
Name: hostname,
BindAddr: "0.0.0.0",
BindPort: 7946,
AdvertiseAddr: "",
AdvertisePort: 7946,
ProtocolVersion: ProtocolVersion2Compatible,
TCPTimeout: 10 * time.Second, // Timeout after 10 seconds
IndirectChecks: 3, // Use 3 nodes for the indirect ping
RetransmitMult: 4, // Retransmit a message 4 * log(N+1) nodes
SuspicionMult: 5, // Suspect a node for 5 * log(N+1) * Interval
SuspicionMaxTimeoutMult: 6, // For 10k nodes this will give a max timeout of 120 seconds
PushPullInterval: 30 * time.Second, // Low frequency
ProbeTimeout: 500 * time.Millisecond, // Reasonable RTT time for LAN
ProbeInterval: 1 * time.Second, // Failure check every second
DisableTcpPings: false, // TCP pings are safe, even with mixed versions
AwarenessMaxMultiplier: 8, // Probe interval backs off to 8 seconds
GossipNodes: 3, // Gossip to 3 nodes
GossipInterval: 200 * time.Millisecond, // Gossip more rapidly
GossipNodes: 3, // Gossip to 3 nodes
GossipInterval: 200 * time.Millisecond, // Gossip more rapidly
GossipToTheDeadTime: 30 * time.Second, // Same as push/pull
EnableCompression: true, // Enable compression by default
@ -192,6 +243,9 @@ func DefaultLANConfig() *Config {
Keyring: nil,
DNSConfigPath: "/etc/resolv.conf",
HandoffQueueDepth: 1024,
UDPBufferSize: 1400,
}
}
@ -207,6 +261,7 @@ func DefaultWANConfig() *Config {
conf.ProbeInterval = 5 * time.Second
conf.GossipNodes = 4 // Gossip less frequently, but to an additional node
conf.GossipInterval = 500 * time.Millisecond
conf.GossipToTheDeadTime = 60 * time.Second
return conf
}
@ -223,6 +278,7 @@ func DefaultLocalConfig() *Config {
conf.ProbeTimeout = 200 * time.Millisecond
conf.ProbeInterval = time.Second
conf.GossipInterval = 100 * time.Millisecond
conf.GossipToTheDeadTime = 15 * time.Second
return conf
}

View file

@ -12,7 +12,7 @@ type Delegate interface {
// NotifyMsg is called when a user-data message is received.
// Care should be taken that this method does not block, since doing
// so would block the entire UDP packet receive loop. Additionally, the byte
// slice may be modified after the call returns, so it should be copied if needed.
// slice may be modified after the call returns, so it should be copied if needed
NotifyMsg([]byte)
// GetBroadcasts is called when user data messages can be broadcast.

View file

@ -58,6 +58,17 @@ func NewKeyring(keys [][]byte, primaryKey []byte) (*Keyring, error) {
return keyring, nil
}
// ValidateKey will check to see if the key is valid and returns an error if not.
//
// key should be either 16, 24, or 32 bytes to select AES-128,
// AES-192, or AES-256.
func ValidateKey(key []byte) error {
if l := len(key); l != 16 && l != 24 && l != 32 {
return fmt.Errorf("key size must be 16, 24 or 32 bytes")
}
return nil
}
// AddKey will install a new key on the ring. Adding a key to the ring will make
// it available for use in decryption. If the key already exists on the ring,
// this function will just return noop.
@ -65,8 +76,8 @@ func NewKeyring(keys [][]byte, primaryKey []byte) (*Keyring, error) {
// key should be either 16, 24, or 32 bytes to select AES-128,
// AES-192, or AES-256.
func (k *Keyring) AddKey(key []byte) error {
if l := len(key); l != 16 && l != 24 && l != 32 {
return fmt.Errorf("key size must be 16, 24 or 32 bytes")
if err := ValidateKey(key); err != nil {
return err
}
// No-op if key is already installed

View file

@ -25,6 +25,7 @@ import (
"time"
"github.com/hashicorp/go-multierror"
sockaddr "github.com/hashicorp/go-sockaddr"
"github.com/miekg/dns"
)
@ -39,13 +40,14 @@ type Memberlist struct {
leave bool
leaveBroadcast chan struct{}
udpListener *net.UDPConn
tcpListener *net.TCPListener
handoff chan msgHandoff
transport Transport
handoff chan msgHandoff
nodeLock sync.RWMutex
nodes []*nodeState // Known nodes
nodeMap map[string]*nodeState // Maps Addr.String() -> NodeState
nodeLock sync.RWMutex
nodes []*nodeState // Known nodes
nodeMap map[string]*nodeState // Maps Addr.String() -> NodeState
nodeTimers map[string]*suspicion // Maps Addr.String() -> suspicion timer
awareness *awareness
tickerLock sync.Mutex
tickers []*time.Ticker
@ -61,7 +63,7 @@ type Memberlist struct {
}
// newMemberlist creates the network listeners.
// Does not schedule execution of background maintenence.
// Does not schedule execution of background maintenance.
func newMemberlist(conf *Config) (*Memberlist, error) {
if conf.ProtocolVersion < ProtocolVersionMin {
return nil, fmt.Errorf("Protocol version '%d' too low. Must be in range: [%d, %d]",
@ -88,25 +90,6 @@ func newMemberlist(conf *Config) (*Memberlist, error) {
}
}
tcpAddr := &net.TCPAddr{IP: net.ParseIP(conf.BindAddr), Port: conf.BindPort}
tcpLn, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
return nil, fmt.Errorf("Failed to start TCP listener. Err: %s", err)
}
if conf.BindPort == 0 {
conf.BindPort = tcpLn.Addr().(*net.TCPAddr).Port
}
udpAddr := &net.UDPAddr{IP: net.ParseIP(conf.BindAddr), Port: conf.BindPort}
udpLn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
tcpLn.Close()
return nil, fmt.Errorf("Failed to start UDP listener. Err: %s", err)
}
// Set the UDP receive window size
setUDPRecvBuf(udpLn)
if conf.LogOutput != nil && conf.Logger != nil {
return nil, fmt.Errorf("Cannot specify both LogOutput and Logger. Please choose a single log configuration setting.")
}
@ -121,14 +104,37 @@ func newMemberlist(conf *Config) (*Memberlist, error) {
logger = log.New(logDest, "", log.LstdFlags)
}
// Set up a network transport by default if a custom one wasn't given
// by the config.
transport := conf.Transport
if transport == nil {
nc := &NetTransportConfig{
BindAddrs: []string{conf.BindAddr},
BindPort: conf.BindPort,
Logger: logger,
}
nt, err := NewNetTransport(nc)
if err != nil {
return nil, fmt.Errorf("Could not set up network transport: %v", err)
}
if conf.BindPort == 0 {
port := nt.GetAutoBindPort()
conf.BindPort = port
logger.Printf("[DEBUG] Using dynamic bind port %d", port)
}
transport = nt
}
m := &Memberlist{
config: conf,
shutdownCh: make(chan struct{}),
leaveBroadcast: make(chan struct{}, 1),
udpListener: udpLn,
tcpListener: tcpLn,
handoff: make(chan msgHandoff, 1024),
transport: transport,
handoff: make(chan msgHandoff, conf.HandoffQueueDepth),
nodeMap: make(map[string]*nodeState),
nodeTimers: make(map[string]*suspicion),
awareness: newAwareness(conf.AwarenessMaxMultiplier),
ackHandlers: make(map[uint32]*ackHandler),
broadcasts: &TransmitLimitedQueue{RetransmitMult: conf.RetransmitMult},
logger: logger,
@ -136,9 +142,9 @@ func newMemberlist(conf *Config) (*Memberlist, error) {
m.broadcasts.NumNodes = func() int {
return m.estNumNodes()
}
go m.tcpListen()
go m.udpListen()
go m.udpHandler()
go m.streamListen()
go m.packetListen()
go m.packetHandler()
return m, nil
}
@ -182,7 +188,8 @@ func (m *Memberlist) Join(existing []string) (int, error) {
}
for _, addr := range addrs {
if err := m.pushPullNode(addr.ip, addr.port, true); err != nil {
hp := joinHostPort(addr.ip.String(), addr.port)
if err := m.pushPullNode(hp, true); err != nil {
err = fmt.Errorf("Failed to join %s: %v", addr.ip, err)
errs = multierror.Append(errs, err)
m.logger.Printf("[DEBUG] memberlist: %v", err)
@ -322,78 +329,30 @@ func (m *Memberlist) resolveAddr(hostStr string) ([]ipPort, error) {
// as if we received an alive notification our own network channel for
// ourself.
func (m *Memberlist) setAlive() error {
var advertiseAddr []byte
var advertisePort int
if m.config.AdvertiseAddr != "" {
// If AdvertiseAddr is not empty, then advertise
// the given address and port.
ip := net.ParseIP(m.config.AdvertiseAddr)
if ip == nil {
return fmt.Errorf("Failed to parse advertise address!")
}
// Ensure IPv4 conversion if necessary
if ip4 := ip.To4(); ip4 != nil {
ip = ip4
}
advertiseAddr = ip
advertisePort = m.config.AdvertisePort
} else {
if m.config.BindAddr == "0.0.0.0" {
// Otherwise, if we're not bound to a specific IP,
//let's list the interfaces on this machine and use
// the first private IP we find.
addresses, err := net.InterfaceAddrs()
if err != nil {
return fmt.Errorf("Failed to get interface addresses! Err: %v", err)
}
// Find private IPv4 address
for _, rawAddr := range addresses {
var ip net.IP
switch addr := rawAddr.(type) {
case *net.IPAddr:
ip = addr.IP
case *net.IPNet:
ip = addr.IP
default:
continue
}
if ip.To4() == nil {
continue
}
if !IsPrivateIP(ip.String()) {
continue
}
advertiseAddr = ip
break
}
// Failed to find private IP, error
if advertiseAddr == nil {
return fmt.Errorf("No private IP address found, and explicit IP not provided")
}
} else {
// Use the IP that we're bound to.
addr := m.tcpListener.Addr().(*net.TCPAddr)
advertiseAddr = addr.IP
}
// Use the port we are bound to.
advertisePort = m.tcpListener.Addr().(*net.TCPAddr).Port
// Get the final advertise address from the transport, which may need
// to see which address we bound to.
addr, port, err := m.transport.FinalAdvertiseAddr(
m.config.AdvertiseAddr, m.config.AdvertisePort)
if err != nil {
return fmt.Errorf("Failed to get final advertise address: %v", err)
}
// Check if this is a public address without encryption
addrStr := net.IP(advertiseAddr).String()
if !IsPrivateIP(addrStr) && !isLoopbackIP(addrStr) && !m.config.EncryptionEnabled() {
ipAddr, err := sockaddr.NewIPAddr(addr.String())
if err != nil {
return fmt.Errorf("Failed to parse interface addresses: %v", err)
}
ifAddrs := []sockaddr.IfAddr{
sockaddr.IfAddr{
SockAddr: ipAddr,
},
}
_, publicIfs, err := sockaddr.IfByRFC("6890", ifAddrs)
if len(publicIfs) > 0 && !m.config.EncryptionEnabled() {
m.logger.Printf("[WARN] memberlist: Binding to public address without encryption!")
}
// Get the node meta data
// Set any metadata from the delegate.
var meta []byte
if m.config.Delegate != nil {
meta = m.config.Delegate.NodeMeta(MetaMaxSize)
@ -405,8 +364,8 @@ func (m *Memberlist) setAlive() error {
a := alive{
Incarnation: m.nextIncarnation(),
Node: m.config.Name,
Addr: advertiseAddr,
Port: uint16(advertisePort),
Addr: addr,
Port: uint16(port),
Meta: meta,
Vsn: []uint8{
ProtocolVersionMin, ProtocolVersionMax, m.config.ProtocolVersion,
@ -415,7 +374,6 @@ func (m *Memberlist) setAlive() error {
},
}
m.aliveNode(&a, nil, true)
return nil
}
@ -478,13 +436,8 @@ func (m *Memberlist) UpdateNode(timeout time.Duration) error {
return nil
}
// SendTo is used to directly send a message to another node, without
// the use of the gossip mechanism. This will encode the message as a
// user-data message, which a delegate will receive through NotifyMsg
// The actual data is transmitted over UDP, which means this is a
// best-effort transmission mechanism, and the maximum size of the
// message is the size of a single UDP datagram, after compression.
// This method is DEPRECATED in favor or SendToUDP
// SendTo is deprecated in favor of SendBestEffort, which requires a node to
// target.
func (m *Memberlist) SendTo(to net.Addr, msg []byte) error {
// Encode as a user message
buf := make([]byte, 1, len(msg)+1)
@ -492,36 +445,39 @@ func (m *Memberlist) SendTo(to net.Addr, msg []byte) error {
buf = append(buf, msg...)
// Send the message
return m.rawSendMsgUDP(to, buf)
return m.rawSendMsgPacket(to.String(), nil, buf)
}
// SendToUDP is used to directly send a message to another node, without
// the use of the gossip mechanism. This will encode the message as a
// user-data message, which a delegate will receive through NotifyMsg
// The actual data is transmitted over UDP, which means this is a
// best-effort transmission mechanism, and the maximum size of the
// message is the size of a single UDP datagram, after compression
// SendToUDP is deprecated in favor of SendBestEffort.
func (m *Memberlist) SendToUDP(to *Node, msg []byte) error {
return m.SendBestEffort(to, msg)
}
// SendToTCP is deprecated in favor of SendReliable.
func (m *Memberlist) SendToTCP(to *Node, msg []byte) error {
return m.SendReliable(to, msg)
}
// SendBestEffort uses the unreliable packet-oriented interface of the transport
// to target a user message at the given node (this does not use the gossip
// mechanism). The maximum size of the message depends on the configured
// UDPBufferSize for this memberlist instance.
func (m *Memberlist) SendBestEffort(to *Node, msg []byte) error {
// Encode as a user message
buf := make([]byte, 1, len(msg)+1)
buf[0] = byte(userMsg)
buf = append(buf, msg...)
// Send the message
destAddr := &net.UDPAddr{IP: to.Addr, Port: int(to.Port)}
return m.rawSendMsgUDP(destAddr, buf)
return m.rawSendMsgPacket(to.Address(), to, buf)
}
// SendToTCP is used to directly send a message to another node, without
// the use of the gossip mechanism. This will encode the message as a
// user-data message, which a delegate will receive through NotifyMsg
// The actual data is transmitted over TCP, which means delivery
// is guaranteed if no error is returned. There is no limit
// to the size of the message
func (m *Memberlist) SendToTCP(to *Node, msg []byte) error {
// Send the message
destAddr := &net.TCPAddr{IP: to.Addr, Port: int(to.Port)}
return m.sendTCPUserMsg(destAddr, msg)
// SendReliable uses the reliable stream-oriented interface of the transport to
// target a user message at the given node (this does not use the gossip
// mechanism). Delivery is guaranteed if no error is returned, and there is no
// limit on the size of the message.
func (m *Memberlist) SendReliable(to *Node, msg []byte) error {
return m.sendUserMsg(to.Address(), msg)
}
// Members returns a list of all known live nodes. The node structures
@ -625,6 +581,13 @@ func (m *Memberlist) anyAlive() bool {
return false
}
// GetHealthScore gives this instance's idea of how well it is meeting the soft
// real-time requirements of the protocol. Lower numbers are better, and zero
// means "totally healthy".
func (m *Memberlist) GetHealthScore() int {
return m.awareness.GetHealthScore()
}
// ProtocolVersion returns the protocol version currently in use by
// this memberlist.
func (m *Memberlist) ProtocolVersion() uint8 {
@ -649,10 +612,14 @@ func (m *Memberlist) Shutdown() error {
return nil
}
// Shut down the transport first, which should block until it's
// completely torn down. If we kill the memberlist-side handlers
// those I/O handlers might get stuck.
m.transport.Shutdown()
// Now tear down everything else.
m.shutdown = true
close(m.shutdownCh)
m.deschedule()
m.udpListener.Close()
m.tcpListener.Close()
return nil
}

View file

@ -0,0 +1,121 @@
package memberlist
import (
"fmt"
"net"
"strconv"
"time"
)
// MockNetwork is used as a factory that produces MockTransport instances which
// are uniquely addressed and wired up to talk to each other.
type MockNetwork struct {
transports map[string]*MockTransport
port int
}
// NewTransport returns a new MockTransport with a unique address, wired up to
// talk to the other transports in the MockNetwork.
func (n *MockNetwork) NewTransport() *MockTransport {
n.port += 1
addr := fmt.Sprintf("127.0.0.1:%d", n.port)
transport := &MockTransport{
net: n,
addr: &MockAddress{addr},
packetCh: make(chan *Packet),
streamCh: make(chan net.Conn),
}
if n.transports == nil {
n.transports = make(map[string]*MockTransport)
}
n.transports[addr] = transport
return transport
}
// MockAddress is a wrapper which adds the net.Addr interface to our mock
// address scheme.
type MockAddress struct {
addr string
}
// See net.Addr.
func (a *MockAddress) Network() string {
return "mock"
}
// See net.Addr.
func (a *MockAddress) String() string {
return a.addr
}
// MockTransport directly plumbs messages to other transports its MockNetwork.
type MockTransport struct {
net *MockNetwork
addr *MockAddress
packetCh chan *Packet
streamCh chan net.Conn
}
// See Transport.
func (t *MockTransport) FinalAdvertiseAddr(string, int) (net.IP, int, error) {
host, portStr, err := net.SplitHostPort(t.addr.String())
if err != nil {
return nil, 0, err
}
ip := net.ParseIP(host)
if ip == nil {
return nil, 0, fmt.Errorf("Failed to parse IP %q", host)
}
port, err := strconv.ParseInt(portStr, 10, 16)
if err != nil {
return nil, 0, err
}
return ip, int(port), nil
}
// See Transport.
func (t *MockTransport) WriteTo(b []byte, addr string) (time.Time, error) {
dest, ok := t.net.transports[addr]
if !ok {
return time.Time{}, fmt.Errorf("No route to %q", addr)
}
now := time.Now()
dest.packetCh <- &Packet{
Buf: b,
From: t.addr,
Timestamp: now,
}
return now, nil
}
// See Transport.
func (t *MockTransport) PacketCh() <-chan *Packet {
return t.packetCh
}
// See Transport.
func (t *MockTransport) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
dest, ok := t.net.transports[addr]
if !ok {
return nil, fmt.Errorf("No route to %q", addr)
}
p1, p2 := net.Pipe()
dest.streamCh <- p1
return p2, nil
}
// See Transport.
func (t *MockTransport) StreamCh() <-chan net.Conn {
return t.streamCh
}
// See Transport.
func (t *MockTransport) Shutdown() error {
return nil
}

View file

@ -5,6 +5,7 @@ import (
"bytes"
"encoding/binary"
"fmt"
"hash/crc32"
"io"
"net"
"time"
@ -24,9 +25,15 @@ const (
// A memberlist speaking version 2 of the protocol will attempt
// to TCP ping another memberlist who understands version 3 or
// greater.
//
// Version 4 added support for nacks as part of indirect probes.
// A memberlist speaking version 2 of the protocol will expect
// nacks from another memberlist who understands version 4 or
// greater, and likewise nacks will be sent to memberlists who
// understand version 4 or greater.
ProtocolVersion2Compatible = 2
ProtocolVersionMax = 3
ProtocolVersionMax = 5
)
// messageType is an integer ID of a type of message that can be received
@ -46,6 +53,8 @@ const (
userMsg // User mesg, not handled by us
compressMsg
encryptMsg
nackRespMsg
hasCrcMsg
)
// compressionType is used to specify the compression algorithm
@ -59,9 +68,6 @@ const (
MetaMaxSize = 512 // Maximum size for node meta data
compoundHeaderOverhead = 2 // Assumed header overhead
compoundOverhead = 2 // Assumed overhead per entry in compoundHeader
udpBufSize = 65536
udpRecvBuf = 2 * 1024 * 1024
udpSendBuf = 1400
userMsgOverhead = 1
blockingWarning = 10 * time.Millisecond // Warn if a UDP packet takes this long to process
maxPushStateBytes = 10 * 1024 * 1024
@ -83,6 +89,7 @@ type indirectPingReq struct {
Target []byte
Port uint16
Node string
Nack bool // true if we'd like a nack back
}
// ack response is sent for a ping
@ -91,6 +98,13 @@ type ackResp struct {
Payload []byte
}
// nack response is sent for an indirect ping when the pinger doesn't hear from
// the ping-ee within the configured timeout. This lets the original node know
// that the indirect ping attempt happened but didn't succeed.
type nackResp struct {
SeqNo uint32
}
// suspect is broadcast when we suspect a node is dead
type suspect struct {
Incarnation uint32
@ -121,7 +135,7 @@ type dead struct {
}
// pushPullHeader is used to inform the
// otherside how many states we are transfering
// otherside how many states we are transferring
type pushPullHeader struct {
Nodes int
UserStateLen int // Encodes the byte lengh of user state
@ -134,7 +148,7 @@ type userMsgHeader struct {
}
// pushNodeState is used for pushPullReq when we are
// transfering out node states
// transferring out node states
type pushNodeState struct {
Name string
Addr []byte
@ -169,45 +183,33 @@ func (m *Memberlist) encryptionVersion() encryptionVersion {
}
}
// setUDPRecvBuf is used to resize the UDP receive window. The function
// attempts to set the read buffer to `udpRecvBuf` but backs off until
// the read buffer can be set.
func setUDPRecvBuf(c *net.UDPConn) {
size := udpRecvBuf
// streamListen is a long running goroutine that pulls incoming streams from the
// transport and hands them off for processing.
func (m *Memberlist) streamListen() {
for {
if err := c.SetReadBuffer(size); err == nil {
break
select {
case conn := <-m.transport.StreamCh():
go m.handleConn(conn)
case <-m.shutdownCh:
return
}
size = size / 2
}
}
// tcpListen listens for and handles incoming connections
func (m *Memberlist) tcpListen() {
for {
conn, err := m.tcpListener.AcceptTCP()
if err != nil {
if m.shutdown {
break
}
m.logger.Printf("[ERR] memberlist: Error accepting TCP connection: %s", err)
continue
}
go m.handleConn(conn)
}
}
// handleConn handles a single incoming TCP connection
func (m *Memberlist) handleConn(conn *net.TCPConn) {
m.logger.Printf("[DEBUG] memberlist: TCP connection %s", LogConn(conn))
// handleConn handles a single incoming stream connection from the transport.
func (m *Memberlist) handleConn(conn net.Conn) {
m.logger.Printf("[DEBUG] memberlist: Stream connection %s", LogConn(conn))
defer conn.Close()
metrics.IncrCounter([]string{"memberlist", "tcp", "accept"}, 1)
conn.SetDeadline(time.Now().Add(m.config.TCPTimeout))
msgType, bufConn, dec, err := m.readTCP(conn)
msgType, bufConn, dec, err := m.readStream(conn)
if err != nil {
m.logger.Printf("[ERR] memberlist: failed to receive: %s %s", err, LogConn(conn))
if err != io.EOF {
m.logger.Printf("[ERR] memberlist: failed to receive: %s %s", err, LogConn(conn))
}
return
}
@ -235,7 +237,7 @@ func (m *Memberlist) handleConn(conn *net.TCPConn) {
case pingMsg:
var p ping
if err := dec.Decode(&p); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to decode TCP ping: %s %s", err, LogConn(conn))
m.logger.Printf("[ERR] memberlist: Failed to decode ping: %s %s", err, LogConn(conn))
return
}
@ -247,13 +249,13 @@ func (m *Memberlist) handleConn(conn *net.TCPConn) {
ack := ackResp{p.SeqNo, nil}
out, err := encode(ackRespMsg, &ack)
if err != nil {
m.logger.Printf("[ERR] memberlist: Failed to encode TCP ack: %s", err)
m.logger.Printf("[ERR] memberlist: Failed to encode ack: %s", err)
return
}
err = m.rawSendMsgTCP(conn, out.Bytes())
err = m.rawSendMsgStream(conn, out.Bytes())
if err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send TCP ack: %s %s", err, LogConn(conn))
m.logger.Printf("[ERR] memberlist: Failed to send ack: %s %s", err, LogConn(conn))
return
}
default:
@ -261,49 +263,17 @@ func (m *Memberlist) handleConn(conn *net.TCPConn) {
}
}
// udpListen listens for and handles incoming UDP packets
func (m *Memberlist) udpListen() {
var n int
var addr net.Addr
var err error
var lastPacket time.Time
// packetListen is a long running goroutine that pulls packets out of the
// transport and hands them off for processing.
func (m *Memberlist) packetListen() {
for {
// Do a check for potentially blocking operations
if !lastPacket.IsZero() && time.Now().Sub(lastPacket) > blockingWarning {
diff := time.Now().Sub(lastPacket)
m.logger.Printf(
"[DEBUG] memberlist: Potential blocking operation. Last command took %v",
diff)
select {
case packet := <-m.transport.PacketCh():
m.ingestPacket(packet.Buf, packet.From, packet.Timestamp)
case <-m.shutdownCh:
return
}
// Create a new buffer
// TODO: Use Sync.Pool eventually
buf := make([]byte, udpBufSize)
// Read a packet
n, addr, err = m.udpListener.ReadFrom(buf)
if err != nil {
if m.shutdown {
break
}
m.logger.Printf("[ERR] memberlist: Error reading UDP packet: %s", err)
continue
}
// Capture the reception time of the packet as close to the
// system calls as possible.
lastPacket = time.Now()
// Check the length
if n < 1 {
m.logger.Printf("[ERR] memberlist: UDP packet too short (%d bytes) %s",
len(buf), LogAddress(addr))
continue
}
// Ingest this packet
metrics.IncrCounter([]string{"memberlist", "udp", "received"}, float32(n))
m.ingestPacket(buf[:n], addr, lastPacket)
}
}
@ -321,8 +291,18 @@ func (m *Memberlist) ingestPacket(buf []byte, from net.Addr, timestamp time.Time
buf = plain
}
// Handle the command
m.handleCommand(buf, from, timestamp)
// See if there's a checksum included to verify the contents of the message
if len(buf) >= 5 && messageType(buf[0]) == hasCrcMsg {
crc := crc32.ChecksumIEEE(buf[5:])
expected := binary.BigEndian.Uint32(buf[1:5])
if crc != expected {
m.logger.Printf("[WARN] memberlist: Got invalid checksum for UDP packet: %x, %x", crc, expected)
return
}
m.handleCommand(buf[5:], from, timestamp)
} else {
m.handleCommand(buf, from, timestamp)
}
}
func (m *Memberlist) handleCommand(buf []byte, from net.Addr, timestamp time.Time) {
@ -343,6 +323,8 @@ func (m *Memberlist) handleCommand(buf []byte, from net.Addr, timestamp time.Tim
m.handleIndirectPing(buf, from)
case ackRespMsg:
m.handleAck(buf, from, timestamp)
case nackRespMsg:
m.handleNack(buf, from)
case suspectMsg:
fallthrough
@ -354,18 +336,18 @@ func (m *Memberlist) handleCommand(buf []byte, from net.Addr, timestamp time.Tim
select {
case m.handoff <- msgHandoff{msgType, buf, from}:
default:
m.logger.Printf("[WARN] memberlist: UDP handler queue full, dropping message (%d) %s", msgType, LogAddress(from))
m.logger.Printf("[WARN] memberlist: handler queue full, dropping message (%d) %s", msgType, LogAddress(from))
}
default:
m.logger.Printf("[ERR] memberlist: UDP msg type (%d) not supported %s", msgType, LogAddress(from))
m.logger.Printf("[ERR] memberlist: msg type (%d) not supported %s", msgType, LogAddress(from))
}
}
// udpHandler processes messages received over UDP, but is decoupled
// from the listener to avoid blocking the listener which may cause
// ping/ack messages to be delayed.
func (m *Memberlist) udpHandler() {
// packetHandler is a long running goroutine that processes messages received
// over the packet interface, but is decoupled from the listener to avoid
// blocking the listener which may cause ping/ack messages to be delayed.
func (m *Memberlist) packetHandler() {
for {
select {
case msg := <-m.handoff:
@ -383,7 +365,7 @@ func (m *Memberlist) udpHandler() {
case userMsg:
m.handleUser(buf, from)
default:
m.logger.Printf("[ERR] memberlist: UDP msg type (%d) not supported %s (handler)", msgType, LogAddress(from))
m.logger.Printf("[ERR] memberlist: Message type (%d) not supported %s (packet handler)", msgType, LogAddress(from))
}
case <-m.shutdownCh:
@ -427,7 +409,7 @@ func (m *Memberlist) handlePing(buf []byte, from net.Addr) {
if m.config.Ping != nil {
ack.Payload = m.config.Ping.AckPayload()
}
if err := m.encodeAndSendMsg(from, ackRespMsg, &ack); err != nil {
if err := m.encodeAndSendMsg(from.String(), ackRespMsg, &ack); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send ack: %s %s", err, LogAddress(from))
}
}
@ -440,29 +422,49 @@ func (m *Memberlist) handleIndirectPing(buf []byte, from net.Addr) {
}
// For proto versions < 2, there is no port provided. Mask old
// behavior by using the configured port
// behavior by using the configured port.
if m.ProtocolVersion() < 2 || ind.Port == 0 {
ind.Port = uint16(m.config.BindPort)
}
// Send a ping to the correct host
// Send a ping to the correct host.
localSeqNo := m.nextSeqNo()
ping := ping{SeqNo: localSeqNo, Node: ind.Node}
destAddr := &net.UDPAddr{IP: ind.Target, Port: int(ind.Port)}
// Setup a response handler to relay the ack
cancelCh := make(chan struct{})
respHandler := func(payload []byte, timestamp time.Time) {
// Try to prevent the nack if we've caught it in time.
close(cancelCh)
// Forward the ack back to the requestor.
ack := ackResp{ind.SeqNo, nil}
if err := m.encodeAndSendMsg(from, ackRespMsg, &ack); err != nil {
if err := m.encodeAndSendMsg(from.String(), ackRespMsg, &ack); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to forward ack: %s %s", err, LogAddress(from))
}
}
m.setAckHandler(localSeqNo, respHandler, m.config.ProbeTimeout)
// Send the ping
if err := m.encodeAndSendMsg(destAddr, pingMsg, &ping); err != nil {
// Send the ping.
addr := joinHostPort(net.IP(ind.Target).String(), ind.Port)
if err := m.encodeAndSendMsg(addr, pingMsg, &ping); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send ping: %s %s", err, LogAddress(from))
}
// Setup a timer to fire off a nack if no ack is seen in time.
if ind.Nack {
go func() {
select {
case <-cancelCh:
return
case <-time.After(m.config.ProbeTimeout):
nack := nackResp{ind.SeqNo}
if err := m.encodeAndSendMsg(from.String(), nackRespMsg, &nack); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send nack: %s %s", err, LogAddress(from))
}
}
}()
}
}
func (m *Memberlist) handleAck(buf []byte, from net.Addr, timestamp time.Time) {
@ -474,6 +476,15 @@ func (m *Memberlist) handleAck(buf []byte, from net.Addr, timestamp time.Time) {
m.invokeAckHandler(ack, timestamp)
}
func (m *Memberlist) handleNack(buf []byte, from net.Addr) {
var nack nackResp
if err := decode(buf, &nack); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to decode nack response: %s %s", err, LogAddress(from))
return
}
m.invokeNackHandler(nack)
}
func (m *Memberlist) handleSuspect(buf []byte, from net.Addr) {
var sus suspect
if err := decode(buf, &sus); err != nil {
@ -530,22 +541,22 @@ func (m *Memberlist) handleCompressed(buf []byte, from net.Addr, timestamp time.
}
// encodeAndSendMsg is used to combine the encoding and sending steps
func (m *Memberlist) encodeAndSendMsg(to net.Addr, msgType messageType, msg interface{}) error {
func (m *Memberlist) encodeAndSendMsg(addr string, msgType messageType, msg interface{}) error {
out, err := encode(msgType, msg)
if err != nil {
return err
}
if err := m.sendMsg(to, out.Bytes()); err != nil {
if err := m.sendMsg(addr, out.Bytes()); err != nil {
return err
}
return nil
}
// sendMsg is used to send a UDP message to another host. It will opportunistically
// create a compoundMsg and piggy back other broadcasts
func (m *Memberlist) sendMsg(to net.Addr, msg []byte) error {
// sendMsg is used to send a message via packet to another host. It will
// opportunistically create a compoundMsg and piggy back other broadcasts.
func (m *Memberlist) sendMsg(addr string, msg []byte) error {
// Check if we can piggy back any messages
bytesAvail := udpSendBuf - len(msg) - compoundHeaderOverhead
bytesAvail := m.config.UDPBufferSize - len(msg) - compoundHeaderOverhead
if m.config.EncryptionEnabled() {
bytesAvail -= encryptOverhead(m.encryptionVersion())
}
@ -553,7 +564,7 @@ func (m *Memberlist) sendMsg(to net.Addr, msg []byte) error {
// Fast path if nothing to piggypack
if len(extra) == 0 {
return m.rawSendMsgUDP(to, msg)
return m.rawSendMsgPacket(addr, nil, msg)
}
// Join all the messages
@ -565,11 +576,12 @@ func (m *Memberlist) sendMsg(to net.Addr, msg []byte) error {
compound := makeCompoundMessage(msgs)
// Send the message
return m.rawSendMsgUDP(to, compound.Bytes())
return m.rawSendMsgPacket(addr, nil, compound.Bytes())
}
// rawSendMsgUDP is used to send a UDP message to another host without modification
func (m *Memberlist) rawSendMsgUDP(to net.Addr, msg []byte) error {
// rawSendMsgPacket is used to send message via packet to another host without
// modification, other than compression or encryption if enabled.
func (m *Memberlist) rawSendMsgPacket(addr string, node *Node, msg []byte) error {
// Check if we have compression enabled
if m.config.EnableCompression {
buf, err := compressPayload(msg)
@ -583,6 +595,31 @@ func (m *Memberlist) rawSendMsgUDP(to net.Addr, msg []byte) error {
}
}
// Try to look up the destination node
if node == nil {
toAddr, _, err := net.SplitHostPort(addr)
if err != nil {
m.logger.Printf("[ERR] memberlist: Failed to parse address %q: %v", addr, err)
return err
}
m.nodeLock.RLock()
nodeState, ok := m.nodeMap[toAddr]
m.nodeLock.RUnlock()
if ok {
node = &nodeState.Node
}
}
// Add a CRC to the end of the payload if the recipient understands
// ProtocolVersion >= 5
if node != nil && node.PMax >= 5 {
crc := crc32.ChecksumIEEE(msg)
header := make([]byte, 5, 5+len(msg))
header[0] = byte(hasCrcMsg)
binary.BigEndian.PutUint32(header[1:], crc)
msg = append(header, msg...)
}
// Check if we have encryption enabled
if m.config.EncryptionEnabled() {
// Encrypt the payload
@ -597,12 +634,13 @@ func (m *Memberlist) rawSendMsgUDP(to net.Addr, msg []byte) error {
}
metrics.IncrCounter([]string{"memberlist", "udp", "sent"}, float32(len(msg)))
_, err := m.udpListener.WriteTo(msg, to)
_, err := m.transport.WriteTo(msg, addr)
return err
}
// rawSendMsgTCP is used to send a TCP message to another host without modification
func (m *Memberlist) rawSendMsgTCP(conn net.Conn, sendBuf []byte) error {
// rawSendMsgStream is used to stream a message to another host without
// modification, other than applying compression and encryption if enabled.
func (m *Memberlist) rawSendMsgStream(conn net.Conn, sendBuf []byte) error {
// Check if compresion is enabled
if m.config.EnableCompression {
compBuf, err := compressPayload(sendBuf)
@ -635,43 +673,36 @@ func (m *Memberlist) rawSendMsgTCP(conn net.Conn, sendBuf []byte) error {
return nil
}
// sendTCPUserMsg is used to send a TCP userMsg to another host
func (m *Memberlist) sendTCPUserMsg(to net.Addr, sendBuf []byte) error {
dialer := net.Dialer{Timeout: m.config.TCPTimeout}
conn, err := dialer.Dial("tcp", to.String())
// sendUserMsg is used to stream a user message to another host.
func (m *Memberlist) sendUserMsg(addr string, sendBuf []byte) error {
conn, err := m.transport.DialTimeout(addr, m.config.TCPTimeout)
if err != nil {
return err
}
defer conn.Close()
bufConn := bytes.NewBuffer(nil)
if err := bufConn.WriteByte(byte(userMsg)); err != nil {
return err
}
// Send our node state
header := userMsgHeader{UserMsgLen: len(sendBuf)}
hd := codec.MsgpackHandle{}
enc := codec.NewEncoder(bufConn, &hd)
if err := enc.Encode(&header); err != nil {
return err
}
if _, err := bufConn.Write(sendBuf); err != nil {
return err
}
return m.rawSendMsgTCP(conn, bufConn.Bytes())
return m.rawSendMsgStream(conn, bufConn.Bytes())
}
// sendAndReceiveState is used to initiate a push/pull over TCP with a remote node
func (m *Memberlist) sendAndReceiveState(addr []byte, port uint16, join bool) ([]pushNodeState, []byte, error) {
// sendAndReceiveState is used to initiate a push/pull over a stream with a
// remote host.
func (m *Memberlist) sendAndReceiveState(addr string, join bool) ([]pushNodeState, []byte, error) {
// Attempt to connect
dialer := net.Dialer{Timeout: m.config.TCPTimeout}
dest := net.TCPAddr{IP: addr, Port: int(port)}
conn, err := dialer.Dial("tcp", dest.String())
conn, err := m.transport.DialTimeout(addr, m.config.TCPTimeout)
if err != nil {
return nil, nil, err
}
@ -685,7 +716,7 @@ func (m *Memberlist) sendAndReceiveState(addr []byte, port uint16, join bool) ([
}
conn.SetDeadline(time.Now().Add(m.config.TCPTimeout))
msgType, bufConn, dec, err := m.readTCP(conn)
msgType, bufConn, dec, err := m.readStream(conn)
if err != nil {
return nil, nil, err
}
@ -701,7 +732,7 @@ func (m *Memberlist) sendAndReceiveState(addr []byte, port uint16, join bool) ([
return remoteNodes, userState, err
}
// sendLocalState is invoked to send our local state over a tcp connection
// sendLocalState is invoked to send our local state over a stream connection.
func (m *Memberlist) sendLocalState(conn net.Conn, join bool) error {
// Setup a deadline
conn.SetDeadline(time.Now().Add(m.config.TCPTimeout))
@ -759,7 +790,7 @@ func (m *Memberlist) sendLocalState(conn net.Conn, join bool) error {
}
// Get the send buffer
return m.rawSendMsgTCP(conn, bufConn.Bytes())
return m.rawSendMsgStream(conn, bufConn.Bytes())
}
// encryptLocalState is used to help encrypt local state before sending
@ -817,9 +848,9 @@ func (m *Memberlist) decryptRemoteState(bufConn io.Reader) ([]byte, error) {
return decryptPayload(keys, cipherBytes, dataBytes)
}
// readTCP is used to read the start of a TCP stream.
// it decrypts and decompresses the stream if necessary
func (m *Memberlist) readTCP(conn net.Conn) (messageType, io.Reader, *codec.Decoder, error) {
// readStream is used to read from a stream connection, decrypting and
// decompressing the stream if necessary.
func (m *Memberlist) readStream(conn net.Conn) (messageType, io.Reader, *codec.Decoder, error) {
// Created a buffered reader
var bufConn io.Reader = bufio.NewReader(conn)
@ -960,7 +991,7 @@ func (m *Memberlist) mergeRemoteState(join bool, remoteNodes []pushNodeState, us
return nil
}
// readUserMsg is used to decode a userMsg from a TCP stream
// readUserMsg is used to decode a userMsg from a stream.
func (m *Memberlist) readUserMsg(bufConn io.Reader, dec *codec.Decoder) error {
// Read the user message header
var header userMsgHeader
@ -991,13 +1022,12 @@ func (m *Memberlist) readUserMsg(bufConn io.Reader, dec *codec.Decoder) error {
return nil
}
// sendPingAndWaitForAck makes a TCP connection to the given address, sends
// sendPingAndWaitForAck makes a stream connection to the given address, sends
// a ping, and waits for an ack. All of this is done as a series of blocking
// operations, given the deadline. The bool return parameter is true if we
// we able to round trip a ping to the other node.
func (m *Memberlist) sendPingAndWaitForAck(destAddr net.Addr, ping ping, deadline time.Time) (bool, error) {
dialer := net.Dialer{Deadline: deadline}
conn, err := dialer.Dial("tcp", destAddr.String())
func (m *Memberlist) sendPingAndWaitForAck(addr string, ping ping, deadline time.Time) (bool, error) {
conn, err := m.transport.DialTimeout(addr, m.config.TCPTimeout)
if err != nil {
// If the node is actually dead we expect this to fail, so we
// shouldn't spam the logs with it. After this point, errors
@ -1013,17 +1043,17 @@ func (m *Memberlist) sendPingAndWaitForAck(destAddr net.Addr, ping ping, deadlin
return false, err
}
if err = m.rawSendMsgTCP(conn, out.Bytes()); err != nil {
if err = m.rawSendMsgStream(conn, out.Bytes()); err != nil {
return false, err
}
msgType, _, dec, err := m.readTCP(conn)
msgType, _, dec, err := m.readStream(conn)
if err != nil {
return false, err
}
if msgType != ackRespMsg {
return false, fmt.Errorf("Unexpected msgType (%d) from TCP ping %s", msgType, LogConn(conn))
return false, fmt.Errorf("Unexpected msgType (%d) from ping %s", msgType, LogConn(conn))
}
var ack ackResp
@ -1032,7 +1062,7 @@ func (m *Memberlist) sendPingAndWaitForAck(destAddr net.Addr, ping ping, deadlin
}
if ack.SeqNo != ping.SeqNo {
return false, fmt.Errorf("Sequence number from ack (%d) doesn't match ping (%d) from TCP ping %s", ack.SeqNo, ping.SeqNo, LogConn(conn))
return false, fmt.Errorf("Sequence number from ack (%d) doesn't match ping (%d)", ack.SeqNo, ping.SeqNo, LogConn(conn))
}
return true, nil

View file

@ -0,0 +1,289 @@
package memberlist
import (
"fmt"
"log"
"net"
"sync"
"sync/atomic"
"time"
"github.com/armon/go-metrics"
sockaddr "github.com/hashicorp/go-sockaddr"
)
const (
// udpPacketBufSize is used to buffer incoming packets during read
// operations.
udpPacketBufSize = 65536
// udpRecvBufSize is a large buffer size that we attempt to set UDP
// sockets to in order to handle a large volume of messages.
udpRecvBufSize = 2 * 1024 * 1024
)
// NetTransportConfig is used to configure a net transport.
type NetTransportConfig struct {
// BindAddrs is a list of addresses to bind to for both TCP and UDP
// communications.
BindAddrs []string
// BindPort is the port to listen on, for each address above.
BindPort int
// Logger is a logger for operator messages.
Logger *log.Logger
}
// NetTransport is a Transport implementation that uses connectionless UDP for
// packet operations, and ad-hoc TCP connections for stream operations.
type NetTransport struct {
config *NetTransportConfig
packetCh chan *Packet
streamCh chan net.Conn
logger *log.Logger
wg sync.WaitGroup
tcpListeners []*net.TCPListener
udpListeners []*net.UDPConn
shutdown int32
}
// NewNetTransport returns a net transport with the given configuration. On
// success all the network listeners will be created and listening.
func NewNetTransport(config *NetTransportConfig) (*NetTransport, error) {
// If we reject the empty list outright we can assume that there's at
// least one listener of each type later during operation.
if len(config.BindAddrs) == 0 {
return nil, fmt.Errorf("At least one bind address is required")
}
// Build out the new transport.
var ok bool
t := NetTransport{
config: config,
packetCh: make(chan *Packet),
streamCh: make(chan net.Conn),
logger: config.Logger,
}
// Clean up listeners if there's an error.
defer func() {
if !ok {
t.Shutdown()
}
}()
// Build all the TCP and UDP listeners.
port := config.BindPort
for _, addr := range config.BindAddrs {
ip := net.ParseIP(addr)
tcpAddr := &net.TCPAddr{IP: ip, Port: port}
tcpLn, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
return nil, fmt.Errorf("Failed to start TCP listener on %q port %d: %v", addr, port, err)
}
t.tcpListeners = append(t.tcpListeners, tcpLn)
// If the config port given was zero, use the first TCP listener
// to pick an available port and then apply that to everything
// else.
if port == 0 {
port = tcpLn.Addr().(*net.TCPAddr).Port
}
udpAddr := &net.UDPAddr{IP: ip, Port: port}
udpLn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return nil, fmt.Errorf("Failed to start UDP listener on %q port %d: %v", addr, port, err)
}
if err := setUDPRecvBuf(udpLn); err != nil {
return nil, fmt.Errorf("Failed to resize UDP buffer: %v", err)
}
t.udpListeners = append(t.udpListeners, udpLn)
}
// Fire them up now that we've been able to create them all.
for i := 0; i < len(config.BindAddrs); i++ {
t.wg.Add(2)
go t.tcpListen(t.tcpListeners[i])
go t.udpListen(t.udpListeners[i])
}
ok = true
return &t, nil
}
// GetAutoBindPort returns the bind port that was automatically given by the
// kernel, if a bind port of 0 was given.
func (t *NetTransport) GetAutoBindPort() int {
// We made sure there's at least one TCP listener, and that one's
// port was applied to all the others for the dynamic bind case.
return t.tcpListeners[0].Addr().(*net.TCPAddr).Port
}
// See Transport.
func (t *NetTransport) FinalAdvertiseAddr(ip string, port int) (net.IP, int, error) {
var advertiseAddr net.IP
var advertisePort int
if ip != "" {
// If they've supplied an address, use that.
advertiseAddr = net.ParseIP(ip)
if advertiseAddr == nil {
return nil, 0, fmt.Errorf("Failed to parse advertise address %q", ip)
}
// Ensure IPv4 conversion if necessary.
if ip4 := advertiseAddr.To4(); ip4 != nil {
advertiseAddr = ip4
}
advertisePort = port
} else {
if t.config.BindAddrs[0] == "0.0.0.0" {
// Otherwise, if we're not bound to a specific IP, let's
// use a suitable private IP address.
var err error
ip, err = sockaddr.GetPrivateIP()
if err != nil {
return nil, 0, fmt.Errorf("Failed to get interface addresses: %v", err)
}
if ip == "" {
return nil, 0, fmt.Errorf("No private IP address found, and explicit IP not provided")
}
advertiseAddr = net.ParseIP(ip)
if advertiseAddr == nil {
return nil, 0, fmt.Errorf("Failed to parse advertise address: %q", ip)
}
} else {
// Use the IP that we're bound to, based on the first
// TCP listener, which we already ensure is there.
advertiseAddr = t.tcpListeners[0].Addr().(*net.TCPAddr).IP
}
// Use the port we are bound to.
advertisePort = t.GetAutoBindPort()
}
return advertiseAddr, advertisePort, nil
}
// See Transport.
func (t *NetTransport) WriteTo(b []byte, addr string) (time.Time, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return time.Time{}, err
}
// We made sure there's at least one UDP listener, so just use the
// packet sending interface on the first one. Take the time after the
// write call comes back, which will underestimate the time a little,
// but help account for any delays before the write occurs.
_, err = t.udpListeners[0].WriteTo(b, udpAddr)
return time.Now(), err
}
// See Transport.
func (t *NetTransport) PacketCh() <-chan *Packet {
return t.packetCh
}
// See Transport.
func (t *NetTransport) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
dialer := net.Dialer{Timeout: timeout}
return dialer.Dial("tcp", addr)
}
// See Transport.
func (t *NetTransport) StreamCh() <-chan net.Conn {
return t.streamCh
}
// See Transport.
func (t *NetTransport) Shutdown() error {
// This will avoid log spam about errors when we shut down.
atomic.StoreInt32(&t.shutdown, 1)
// Rip through all the connections and shut them down.
for _, conn := range t.tcpListeners {
conn.Close()
}
for _, conn := range t.udpListeners {
conn.Close()
}
// Block until all the listener threads have died.
t.wg.Wait()
return nil
}
// tcpListen is a long running goroutine that accepts incoming TCP connections
// and hands them off to the stream channel.
func (t *NetTransport) tcpListen(tcpLn *net.TCPListener) {
defer t.wg.Done()
for {
conn, err := tcpLn.AcceptTCP()
if err != nil {
if s := atomic.LoadInt32(&t.shutdown); s == 1 {
break
}
t.logger.Printf("[ERR] memberlist: Error accepting TCP connection: %v", err)
continue
}
t.streamCh <- conn
}
}
// udpListen is a long running goroutine that accepts incoming UDP packets and
// hands them off to the packet channel.
func (t *NetTransport) udpListen(udpLn *net.UDPConn) {
defer t.wg.Done()
for {
// Do a blocking read into a fresh buffer. Grab a time stamp as
// close as possible to the I/O.
buf := make([]byte, udpPacketBufSize)
n, addr, err := udpLn.ReadFrom(buf)
ts := time.Now()
if err != nil {
if s := atomic.LoadInt32(&t.shutdown); s == 1 {
break
}
t.logger.Printf("[ERR] memberlist: Error reading UDP packet: %v", err)
continue
}
// Check the length - it needs to have at least one byte to be a
// proper message.
if n < 1 {
t.logger.Printf("[ERR] memberlist: UDP packet too short (%d bytes) %s",
len(buf), LogAddress(addr))
continue
}
// Ingest the packet.
metrics.IncrCounter([]string{"memberlist", "udp", "received"}, float32(n))
t.packetCh <- &Packet{
Buf: buf[:n],
From: addr,
Timestamp: ts,
}
}
}
// setUDPRecvBuf is used to resize the UDP receive window. The function
// attempts to set the read buffer to `udpRecvBuf` but backs off until
// the read buffer can be set.
func setUDPRecvBuf(c *net.UDPConn) error {
size := udpRecvBufSize
var err error
for size > 0 {
if err = c.SetReadBuffer(size); err == nil {
return nil
}
size = size / 2
}
return err
}

View file

@ -34,6 +34,12 @@ type Node struct {
DCur uint8 // Current version delegate is speaking
}
// Address returns the host:port form of a node's address, suitable for use
// with a transport.
func (n *Node) Address() string {
return joinHostPort(n.Addr.String(), n.Port)
}
// NodeState is used to manage our state view of another node
type nodeState struct {
Node
@ -42,10 +48,17 @@ type nodeState struct {
StateChange time.Time // Time last state change happened
}
// ackHandler is used to register handlers for incoming acks
// Address returns the host:port form of a node's address, suitable for use
// with a transport.
func (n *nodeState) Address() string {
return n.Node.Address()
}
// ackHandler is used to register handlers for incoming acks and nacks.
type ackHandler struct {
handler func([]byte, time.Time)
timer *time.Timer
ackFn func([]byte, time.Time)
nackFn func()
timer *time.Timer
}
// NoPingResponseError is used to indicate a 'ping' packet was
@ -148,7 +161,7 @@ func (m *Memberlist) pushPullTrigger(stop <-chan struct{}) {
}
}
// Deschedule is used to stop the background maintenence. This is safe
// Deschedule is used to stop the background maintenance. This is safe
// to call multiple times.
func (m *Memberlist) deschedule() {
m.tickerLock.Lock()
@ -219,17 +232,51 @@ START:
func (m *Memberlist) probeNode(node *nodeState) {
defer metrics.MeasureSince([]string{"memberlist", "probeNode"}, time.Now())
// We use our health awareness to scale the overall probe interval, so we
// slow down if we detect problems. The ticker that calls us can handle
// us running over the base interval, and will skip missed ticks.
probeInterval := m.awareness.ScaleTimeout(m.config.ProbeInterval)
if probeInterval > m.config.ProbeInterval {
metrics.IncrCounter([]string{"memberlist", "degraded", "probe"}, 1)
}
// Prepare a ping message and setup an ack handler.
ping := ping{SeqNo: m.nextSeqNo(), Node: node.Name}
ackCh := make(chan ackMessage, m.config.IndirectChecks+1)
m.setAckChannel(ping.SeqNo, ackCh, m.config.ProbeInterval)
nackCh := make(chan struct{}, m.config.IndirectChecks+1)
m.setProbeChannels(ping.SeqNo, ackCh, nackCh, probeInterval)
// Send a ping to the node.
deadline := time.Now().Add(m.config.ProbeInterval)
destAddr := &net.UDPAddr{IP: node.Addr, Port: int(node.Port)}
if err := m.encodeAndSendMsg(destAddr, pingMsg, &ping); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send ping: %s", err)
return
// Send a ping to the node. If this node looks like it's suspect or dead,
// also tack on a suspect message so that it has a chance to refute as
// soon as possible.
deadline := time.Now().Add(probeInterval)
addr := node.Address()
if node.State == stateAlive {
if err := m.encodeAndSendMsg(addr, pingMsg, &ping); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send ping: %s", err)
return
}
} else {
var msgs [][]byte
if buf, err := encode(pingMsg, &ping); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to encode ping message: %s", err)
return
} else {
msgs = append(msgs, buf.Bytes())
}
s := suspect{Incarnation: node.Incarnation, Node: node.Name, From: m.config.Name}
if buf, err := encode(suspectMsg, &s); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to encode suspect message: %s", err)
return
} else {
msgs = append(msgs, buf.Bytes())
}
compound := makeCompoundMessage(msgs)
if err := m.rawSendMsgPacket(addr, &node.Node, compound.Bytes()); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send compound ping and suspect message to %s: %s", addr, err)
return
}
}
// Mark the sent time here, which should be after any pre-processing and
@ -237,6 +284,16 @@ func (m *Memberlist) probeNode(node *nodeState) {
// but it's the best we can do.
sent := time.Now()
// Arrange for our self-awareness to get updated. At this point we've
// sent the ping, so any return statement means the probe succeeded
// which will improve our health until we get to the failure scenarios
// at the end of this function, which will alter this delta variable
// accordingly.
awarenessDelta := -1
defer func() {
m.awareness.ApplyDelta(awarenessDelta)
}()
// Wait for response or round-trip-time.
select {
case v := <-ackCh:
@ -254,20 +311,35 @@ func (m *Memberlist) probeNode(node *nodeState) {
ackCh <- v
}
case <-time.After(m.config.ProbeTimeout):
m.logger.Printf("[DEBUG] memberlist: Failed UDP ping: %v (timeout reached)", node.Name)
// Note that we don't scale this timeout based on awareness and
// the health score. That's because we don't really expect waiting
// longer to help get UDP through. Since health does extend the
// probe interval it will give the TCP fallback more time, which
// is more active in dealing with lost packets, and it gives more
// time to wait for indirect acks/nacks.
m.logger.Printf("[DEBUG] memberlist: Failed ping: %v (timeout reached)", node.Name)
}
// Get some random live nodes.
m.nodeLock.RLock()
excludes := []string{m.config.Name, node.Name}
kNodes := kRandomNodes(m.config.IndirectChecks, excludes, m.nodes)
kNodes := kRandomNodes(m.config.IndirectChecks, m.nodes, func(n *nodeState) bool {
return n.Name == m.config.Name ||
n.Name == node.Name ||
n.State != stateAlive
})
m.nodeLock.RUnlock()
// Attempt an indirect ping.
expectedNacks := 0
ind := indirectPingReq{SeqNo: ping.SeqNo, Target: node.Addr, Port: node.Port, Node: node.Name}
for _, peer := range kNodes {
destAddr := &net.UDPAddr{IP: peer.Addr, Port: int(peer.Port)}
if err := m.encodeAndSendMsg(destAddr, indirectPingMsg, &ind); err != nil {
// We only expect nack to be sent from peers who understand
// version 4 of the protocol.
if ind.Nack = peer.PMax >= 4; ind.Nack {
expectedNacks++
}
if err := m.encodeAndSendMsg(peer.Address(), indirectPingMsg, &ind); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send indirect ping: %s", err)
}
}
@ -284,12 +356,11 @@ func (m *Memberlist) probeNode(node *nodeState) {
// config option to turn this off if desired.
fallbackCh := make(chan bool, 1)
if (!m.config.DisableTcpPings) && (node.PMax >= 3) {
destAddr := &net.TCPAddr{IP: node.Addr, Port: int(node.Port)}
go func() {
defer close(fallbackCh)
didContact, err := m.sendPingAndWaitForAck(destAddr, ping, deadline)
didContact, err := m.sendPingAndWaitForAck(node.Address(), ping, deadline)
if err != nil {
m.logger.Printf("[ERR] memberlist: Failed TCP fallback ping: %s", err)
m.logger.Printf("[ERR] memberlist: Failed fallback ping: %s", err)
} else {
fallbackCh <- didContact
}
@ -314,12 +385,28 @@ func (m *Memberlist) probeNode(node *nodeState) {
// any additional time here.
for didContact := range fallbackCh {
if didContact {
m.logger.Printf("[WARN] memberlist: Was able to reach %s via TCP but not UDP, network may be misconfigured and not allowing bidirectional UDP", node.Name)
m.logger.Printf("[WARN] memberlist: Was able to connect to %s but other probes failed, network may be misconfigured", node.Name)
return
}
}
// No acks received from target, suspect
// Update our self-awareness based on the results of this failed probe.
// If we don't have peers who will send nacks then we penalize for any
// failed probe as a simple health metric. If we do have peers to nack
// verify, then we can use that as a more sophisticated measure of self-
// health because we assume them to be working, and they can help us
// decide if the probed node was really dead or if it was something wrong
// with ourselves.
awarenessDelta = 0
if expectedNacks > 0 {
if nackCount := len(nackCh); nackCount < expectedNacks {
awarenessDelta += (expectedNacks - nackCount)
}
} else {
awarenessDelta += 1
}
// No acks received from target, suspect it as failed.
m.logger.Printf("[INFO] memberlist: Suspect %s has failed, no acks received", node.Name)
s := suspect{Incarnation: node.Incarnation, Node: node.Name, From: m.config.Name}
m.suspectNode(&s)
@ -330,10 +417,10 @@ func (m *Memberlist) Ping(node string, addr net.Addr) (time.Duration, error) {
// Prepare a ping message and setup an ack handler.
ping := ping{SeqNo: m.nextSeqNo(), Node: node}
ackCh := make(chan ackMessage, m.config.IndirectChecks+1)
m.setAckChannel(ping.SeqNo, ackCh, m.config.ProbeInterval)
m.setProbeChannels(ping.SeqNo, ackCh, nil, m.config.ProbeInterval)
// Send a ping to the node.
if err := m.encodeAndSendMsg(addr, pingMsg, &ping); err != nil {
if err := m.encodeAndSendMsg(addr.String(), pingMsg, &ping); err != nil {
return 0, err
}
@ -362,8 +449,8 @@ func (m *Memberlist) resetNodes() {
m.nodeLock.Lock()
defer m.nodeLock.Unlock()
// Move the dead nodes
deadIdx := moveDeadNodes(m.nodes)
// Move dead nodes, but respect gossip to the dead interval
deadIdx := moveDeadNodes(m.nodes, m.config.GossipToTheDeadTime)
// Deregister the dead nodes
for i := deadIdx; i < len(m.nodes); i++ {
@ -386,14 +473,28 @@ func (m *Memberlist) resetNodes() {
func (m *Memberlist) gossip() {
defer metrics.MeasureSince([]string{"memberlist", "gossip"}, time.Now())
// Get some random live nodes
// Get some random live, suspect, or recently dead nodes
m.nodeLock.RLock()
excludes := []string{m.config.Name}
kNodes := kRandomNodes(m.config.GossipNodes, excludes, m.nodes)
kNodes := kRandomNodes(m.config.GossipNodes, m.nodes, func(n *nodeState) bool {
if n.Name == m.config.Name {
return true
}
switch n.State {
case stateAlive, stateSuspect:
return false
case stateDead:
return time.Since(n.StateChange) > m.config.GossipToTheDeadTime
default:
return true
}
})
m.nodeLock.RUnlock()
// Compute the bytes available
bytesAvail := udpSendBuf - compoundHeaderOverhead
bytesAvail := m.config.UDPBufferSize - compoundHeaderOverhead
if m.config.EncryptionEnabled() {
bytesAvail -= encryptOverhead(m.encryptionVersion())
}
@ -405,13 +506,18 @@ func (m *Memberlist) gossip() {
return
}
// Create a compound message
compound := makeCompoundMessage(msgs)
// Send the compound message
destAddr := &net.UDPAddr{IP: node.Addr, Port: int(node.Port)}
if err := m.rawSendMsgUDP(destAddr, compound.Bytes()); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send gossip to %s: %s", destAddr, err)
addr := node.Address()
if len(msgs) == 1 {
// Send single message as is
if err := m.rawSendMsgPacket(addr, &node.Node, msgs[0]); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send gossip to %s: %s", addr, err)
}
} else {
// Otherwise create and send a compound message
compound := makeCompoundMessage(msgs)
if err := m.rawSendMsgPacket(addr, &node.Node, compound.Bytes()); err != nil {
m.logger.Printf("[ERR] memberlist: Failed to send gossip to %s: %s", addr, err)
}
}
}
}
@ -423,8 +529,10 @@ func (m *Memberlist) gossip() {
func (m *Memberlist) pushPull() {
// Get a random live node
m.nodeLock.RLock()
excludes := []string{m.config.Name}
nodes := kRandomNodes(1, excludes, m.nodes)
nodes := kRandomNodes(1, m.nodes, func(n *nodeState) bool {
return n.Name == m.config.Name ||
n.State != stateAlive
})
m.nodeLock.RUnlock()
// If no nodes, bail
@ -434,17 +542,17 @@ func (m *Memberlist) pushPull() {
node := nodes[0]
// Attempt a push pull
if err := m.pushPullNode(node.Addr, node.Port, false); err != nil {
if err := m.pushPullNode(node.Address(), false); err != nil {
m.logger.Printf("[ERR] memberlist: Push/Pull with %s failed: %s", node.Name, err)
}
}
// pushPullNode does a complete state exchange with a specific node.
func (m *Memberlist) pushPullNode(addr []byte, port uint16, join bool) error {
func (m *Memberlist) pushPullNode(addr string, join bool) error {
defer metrics.MeasureSince([]string{"memberlist", "pushPullNode"}, time.Now())
// Attempt to send and receive with the node
remote, userState, err := m.sendAndReceiveState(addr, port, join)
remote, userState, err := m.sendAndReceiveState(addr, join)
if err != nil {
return err
}
@ -584,6 +692,11 @@ func (m *Memberlist) nextIncarnation() uint32 {
return atomic.AddUint32(&m.incarnation, 1)
}
// skipIncarnation adds the positive offset to the incarnation number.
func (m *Memberlist) skipIncarnation(offset uint32) uint32 {
return atomic.AddUint32(&m.incarnation, offset)
}
// estNumNodes is used to get the current estimate of the number of nodes
func (m *Memberlist) estNumNodes() int {
return int(atomic.LoadUint32(&m.numNodes))
@ -595,19 +708,27 @@ type ackMessage struct {
Timestamp time.Time
}
// setAckChannel is used to attach a channel to receive a message when an ack with a given
// sequence number is received. The `complete` field of the message will be false on timeout
func (m *Memberlist) setAckChannel(seqNo uint32, ch chan ackMessage, timeout time.Duration) {
// Create a handler function
handler := func(payload []byte, timestamp time.Time) {
// setProbeChannels is used to attach the ackCh to receive a message when an ack
// with a given sequence number is received. The `complete` field of the message
// will be false on timeout. Any nack messages will cause an empty struct to be
// passed to the nackCh, which can be nil if not needed.
func (m *Memberlist) setProbeChannels(seqNo uint32, ackCh chan ackMessage, nackCh chan struct{}, timeout time.Duration) {
// Create handler functions for acks and nacks
ackFn := func(payload []byte, timestamp time.Time) {
select {
case ch <- ackMessage{true, payload, timestamp}:
case ackCh <- ackMessage{true, payload, timestamp}:
default:
}
}
nackFn := func() {
select {
case nackCh <- struct{}{}:
default:
}
}
// Add the handler
ah := &ackHandler{handler, nil}
// Add the handlers
ah := &ackHandler{ackFn, nackFn, nil}
m.ackLock.Lock()
m.ackHandlers[seqNo] = ah
m.ackLock.Unlock()
@ -618,18 +739,19 @@ func (m *Memberlist) setAckChannel(seqNo uint32, ch chan ackMessage, timeout tim
delete(m.ackHandlers, seqNo)
m.ackLock.Unlock()
select {
case ch <- ackMessage{false, nil, time.Now()}:
case ackCh <- ackMessage{false, nil, time.Now()}:
default:
}
})
}
// setAckHandler is used to attach a handler to be invoked when an
// ack with a given sequence number is received. If a timeout is reached,
// the handler is deleted
func (m *Memberlist) setAckHandler(seqNo uint32, handler func([]byte, time.Time), timeout time.Duration) {
// setAckHandler is used to attach a handler to be invoked when an ack with a
// given sequence number is received. If a timeout is reached, the handler is
// deleted. This is used for indirect pings so does not configure a function
// for nacks.
func (m *Memberlist) setAckHandler(seqNo uint32, ackFn func([]byte, time.Time), timeout time.Duration) {
// Add the handler
ah := &ackHandler{handler, nil}
ah := &ackHandler{ackFn, nil, nil}
m.ackLock.Lock()
m.ackHandlers[seqNo] = ah
m.ackLock.Unlock()
@ -642,7 +764,7 @@ func (m *Memberlist) setAckHandler(seqNo uint32, handler func([]byte, time.Time)
})
}
// Invokes an Ack handler if any is associated, and reaps the handler immediately
// Invokes an ack handler if any is associated, and reaps the handler immediately
func (m *Memberlist) invokeAckHandler(ack ackResp, timestamp time.Time) {
m.ackLock.Lock()
ah, ok := m.ackHandlers[ack.SeqNo]
@ -652,7 +774,49 @@ func (m *Memberlist) invokeAckHandler(ack ackResp, timestamp time.Time) {
return
}
ah.timer.Stop()
ah.handler(ack.Payload, timestamp)
ah.ackFn(ack.Payload, timestamp)
}
// Invokes nack handler if any is associated.
func (m *Memberlist) invokeNackHandler(nack nackResp) {
m.ackLock.Lock()
ah, ok := m.ackHandlers[nack.SeqNo]
m.ackLock.Unlock()
if !ok || ah.nackFn == nil {
return
}
ah.nackFn()
}
// refute gossips an alive message in response to incoming information that we
// are suspect or dead. It will make sure the incarnation number beats the given
// accusedInc value, or you can supply 0 to just get the next incarnation number.
// This alters the node state that's passed in so this MUST be called while the
// nodeLock is held.
func (m *Memberlist) refute(me *nodeState, accusedInc uint32) {
// Make sure the incarnation number beats the accusation.
inc := m.nextIncarnation()
if accusedInc >= inc {
inc = m.skipIncarnation(accusedInc - inc + 1)
}
me.Incarnation = inc
// Decrease our health because we are being asked to refute a problem.
m.awareness.ApplyDelta(1)
// Format and broadcast an alive message.
a := alive{
Incarnation: inc,
Node: me.Name,
Addr: me.Addr,
Port: me.Port,
Meta: me.Meta,
Vsn: []uint8{
me.PMin, me.PMax, me.PCur,
me.DMin, me.DMax, me.DCur,
},
}
m.encodeAndBroadcast(me.Addr.String(), aliveMsg, a)
}
// aliveNode is invoked by the network layer when we get a message about a
@ -754,6 +918,9 @@ func (m *Memberlist) aliveNode(a *alive, notify chan struct{}, bootstrap bool) {
return
}
// Clear out any suspicion timer that may be in effect.
delete(m.nodeTimers, a.Node)
// Store the old state and meta data
oldState := state.State
oldMeta := state.Meta
@ -783,21 +950,7 @@ func (m *Memberlist) aliveNode(a *alive, notify chan struct{}, bootstrap bool) {
return
}
inc := m.nextIncarnation()
for a.Incarnation >= inc {
inc = m.nextIncarnation()
}
state.Incarnation = inc
a := alive{
Incarnation: inc,
Node: state.Name,
Addr: state.Addr,
Port: state.Port,
Meta: state.Meta,
Vsn: versions,
}
m.encodeBroadcastNotify(a.Node, aliveMsg, a, notify)
m.refute(state, a.Incarnation)
m.logger.Printf("[WARN] memberlist: Refuting an alive message")
} else {
m.encodeBroadcastNotify(a.Node, aliveMsg, a, notify)
@ -854,6 +1007,17 @@ func (m *Memberlist) suspectNode(s *suspect) {
return
}
// See if there's a suspicion timer we can confirm. If the info is new
// to us we will go ahead and re-gossip it. This allows for multiple
// independent confirmations to flow even when a node probes a node
// that's already suspect.
if timer, ok := m.nodeTimers[s.Node]; ok {
if timer.Confirm(s.From) {
m.encodeAndBroadcast(s.Node, suspectMsg, s)
}
return
}
// Ignore non-alive nodes
if state.State != stateAlive {
return
@ -861,24 +1025,7 @@ func (m *Memberlist) suspectNode(s *suspect) {
// If this is us we need to refute, otherwise re-broadcast
if state.Name == m.config.Name {
inc := m.nextIncarnation()
for s.Incarnation >= inc {
inc = m.nextIncarnation()
}
state.Incarnation = inc
a := alive{
Incarnation: inc,
Node: state.Name,
Addr: state.Addr,
Port: state.Port,
Meta: state.Meta,
Vsn: []uint8{
state.PMin, state.PMax, state.PCur,
state.DMin, state.DMax, state.DCur,
},
}
m.encodeAndBroadcast(s.Node, aliveMsg, a)
m.refute(state, s.Incarnation)
m.logger.Printf("[WARN] memberlist: Refuting a suspect message (from: %s)", s.From)
return // Do not mark ourself suspect
} else {
@ -894,26 +1041,41 @@ func (m *Memberlist) suspectNode(s *suspect) {
changeTime := time.Now()
state.StateChange = changeTime
// Setup a timeout for this
timeout := suspicionTimeout(m.config.SuspicionMult, m.estNumNodes(), m.config.ProbeInterval)
time.AfterFunc(timeout, func() {
// Setup a suspicion timer. Given that we don't have any known phase
// relationship with our peers, we set up k such that we hit the nominal
// timeout two probe intervals short of what we expect given the suspicion
// multiplier.
k := m.config.SuspicionMult - 2
// If there aren't enough nodes to give the expected confirmations, just
// set k to 0 to say that we don't expect any. Note we subtract 2 from n
// here to take out ourselves and the node being probed.
n := m.estNumNodes()
if n-2 < k {
k = 0
}
// Compute the timeouts based on the size of the cluster.
min := suspicionTimeout(m.config.SuspicionMult, n, m.config.ProbeInterval)
max := time.Duration(m.config.SuspicionMaxTimeoutMult) * min
fn := func(numConfirmations int) {
m.nodeLock.Lock()
state, ok := m.nodeMap[s.Node]
timeout := ok && state.State == stateSuspect && state.StateChange == changeTime
m.nodeLock.Unlock()
if timeout {
m.suspectTimeout(state)
}
})
}
if k > 0 && numConfirmations < k {
metrics.IncrCounter([]string{"memberlist", "degraded", "timeout"}, 1)
}
// suspectTimeout is invoked when a suspect timeout has occurred
func (m *Memberlist) suspectTimeout(n *nodeState) {
// Construct a dead message
m.logger.Printf("[INFO] memberlist: Marking %s as failed, suspect timeout reached", n.Name)
d := dead{Incarnation: n.Incarnation, Node: n.Name, From: m.config.Name}
m.deadNode(&d)
m.logger.Printf("[INFO] memberlist: Marking %s as failed, suspect timeout reached (%d peer confirmations)",
state.Name, numConfirmations)
d := dead{Incarnation: state.Incarnation, Node: state.Name, From: m.config.Name}
m.deadNode(&d)
}
}
m.nodeTimers[s.Node] = newSuspicion(s.From, k, min, max, fn)
}
// deadNode is invoked by the network layer when we get a message
@ -933,6 +1095,9 @@ func (m *Memberlist) deadNode(d *dead) {
return
}
// Clear out any suspicion timer that may be in effect.
delete(m.nodeTimers, d.Node)
// Ignore if node is already dead
if state.State == stateDead {
return
@ -942,24 +1107,7 @@ func (m *Memberlist) deadNode(d *dead) {
if state.Name == m.config.Name {
// If we are not leaving we need to refute
if !m.leave {
inc := m.nextIncarnation()
for d.Incarnation >= inc {
inc = m.nextIncarnation()
}
state.Incarnation = inc
a := alive{
Incarnation: inc,
Node: state.Name,
Addr: state.Addr,
Port: state.Port,
Meta: state.Meta,
Vsn: []uint8{
state.PMin, state.PMax, state.PCur,
state.DMin, state.DMax, state.DCur,
},
}
m.encodeAndBroadcast(d.Node, aliveMsg, a)
m.refute(state, d.Incarnation)
m.logger.Printf("[WARN] memberlist: Refuting a dead message (from: %s)", d.From)
return // Do not mark ourself dead
}
@ -1001,7 +1149,7 @@ func (m *Memberlist) mergeState(remote []pushNodeState) {
m.aliveNode(&a, nil, false)
case stateDead:
// If the remote node belives a node is dead, we prefer to
// If the remote node believes a node is dead, we prefer to
// suspect that node instead of declaring it dead instantly
fallthrough
case stateSuspect:

View file

@ -0,0 +1,130 @@
package memberlist
import (
"math"
"sync/atomic"
"time"
)
// suspicion manages the suspect timer for a node and provides an interface
// to accelerate the timeout as we get more independent confirmations that
// a node is suspect.
type suspicion struct {
// n is the number of independent confirmations we've seen. This must
// be updated using atomic instructions to prevent contention with the
// timer callback.
n int32
// k is the number of independent confirmations we'd like to see in
// order to drive the timer to its minimum value.
k int32
// min is the minimum timer value.
min time.Duration
// max is the maximum timer value.
max time.Duration
// start captures the timestamp when we began the timer. This is used
// so we can calculate durations to feed the timer during updates in
// a way the achieves the overall time we'd like.
start time.Time
// timer is the underlying timer that implements the timeout.
timer *time.Timer
// f is the function to call when the timer expires. We hold on to this
// because there are cases where we call it directly.
timeoutFn func()
// confirmations is a map of "from" nodes that have confirmed a given
// node is suspect. This prevents double counting.
confirmations map[string]struct{}
}
// newSuspicion returns a timer started with the max time, and that will drive
// to the min time after seeing k or more confirmations. The from node will be
// excluded from confirmations since we might get our own suspicion message
// gossiped back to us. The minimum time will be used if no confirmations are
// called for (k <= 0).
func newSuspicion(from string, k int, min time.Duration, max time.Duration, fn func(int)) *suspicion {
s := &suspicion{
k: int32(k),
min: min,
max: max,
confirmations: make(map[string]struct{}),
}
// Exclude the from node from any confirmations.
s.confirmations[from] = struct{}{}
// Pass the number of confirmations into the timeout function for
// easy telemetry.
s.timeoutFn = func() {
fn(int(atomic.LoadInt32(&s.n)))
}
// If there aren't any confirmations to be made then take the min
// time from the start.
timeout := max
if k < 1 {
timeout = min
}
s.timer = time.AfterFunc(timeout, s.timeoutFn)
// Capture the start time right after starting the timer above so
// we should always err on the side of a little longer timeout if
// there's any preemption that separates this and the step above.
s.start = time.Now()
return s
}
// remainingSuspicionTime takes the state variables of the suspicion timer and
// calculates the remaining time to wait before considering a node dead. The
// return value can be negative, so be prepared to fire the timer immediately in
// that case.
func remainingSuspicionTime(n, k int32, elapsed time.Duration, min, max time.Duration) time.Duration {
frac := math.Log(float64(n)+1.0) / math.Log(float64(k)+1.0)
raw := max.Seconds() - frac*(max.Seconds()-min.Seconds())
timeout := time.Duration(math.Floor(1000.0*raw)) * time.Millisecond
if timeout < min {
timeout = min
}
// We have to take into account the amount of time that has passed so
// far, so we get the right overall timeout.
return timeout - elapsed
}
// Confirm registers that a possibly new peer has also determined the given
// node is suspect. This returns true if this was new information, and false
// if it was a duplicate confirmation, or if we've got enough confirmations to
// hit the minimum.
func (s *suspicion) Confirm(from string) bool {
// If we've got enough confirmations then stop accepting them.
if atomic.LoadInt32(&s.n) >= s.k {
return false
}
// Only allow one confirmation from each possible peer.
if _, ok := s.confirmations[from]; ok {
return false
}
s.confirmations[from] = struct{}{}
// Compute the new timeout given the current number of confirmations and
// adjust the timer. If the timeout becomes negative *and* we can cleanly
// stop the timer then we will call the timeout function directly from
// here.
n := atomic.AddInt32(&s.n, 1)
elapsed := time.Now().Sub(s.start)
remaining := remainingSuspicionTime(n, s.k, elapsed, s.min, s.max)
if s.timer.Stop() {
if remaining > 0 {
s.timer.Reset(remaining)
} else {
go s.timeoutFn()
}
}
return true
}

View file

@ -0,0 +1,65 @@
package memberlist
import (
"net"
"time"
)
// Packet is used to provide some metadata about incoming packets from peers
// over a packet connection, as well as the packet payload.
type Packet struct {
// Buf has the raw contents of the packet.
Buf []byte
// From has the address of the peer. This is an actual net.Addr so we
// can expose some concrete details about incoming packets.
From net.Addr
// Timestamp is the time when the packet was received. This should be
// taken as close as possible to the actual receipt time to help make an
// accurate RTT measurements during probes.
Timestamp time.Time
}
// Transport is used to abstract over communicating with other peers. The packet
// interface is assumed to be best-effort and the stream interface is assumed to
// be reliable.
type Transport interface {
// FinalAdvertiseAddr is given the user's configured values (which
// might be empty) and returns the desired IP and port to advertise to
// the rest of the cluster.
FinalAdvertiseAddr(ip string, port int) (net.IP, int, error)
// WriteTo is a packet-oriented interface that fires off the given
// payload to the given address in a connectionless fashion. This should
// return a time stamp that's as close as possible to when the packet
// was transmitted to help make accurate RTT measurements during probes.
//
// This is similar to net.PacketConn, though we didn't want to expose
// that full set of required methods to keep assumptions about the
// underlying plumbing to a minimum. We also treat the address here as a
// string, similar to Dial, so it's network neutral, so this usually is
// in the form of "host:port".
WriteTo(b []byte, addr string) (time.Time, error)
// PacketCh returns a channel that can be read to receive incoming
// packets from other peers. How this is set up for listening is left as
// an exercise for the concrete transport implementations.
PacketCh() <-chan *Packet
// DialTimeout is used to create a connection that allows us to perform
// two-way communication with a peer. This is generally more expensive
// than packet connections so is used for more infrequent operations
// such as anti-entropy or fallback probes if the packet-oriented probe
// failed.
DialTimeout(addr string, timeout time.Duration) (net.Conn, error)
// StreamCh returns a channel that can be read to handle incoming stream
// connections from other peers. How this is set up for listening is
// left as an exercise for the concrete transport implementations.
StreamCh() <-chan net.Conn
// Shutdown is called when memberlist is shutting down; this gives the
// transport a chance to clean up any listeners.
Shutdown() error
}

View file

@ -9,10 +9,12 @@ import (
"math"
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/hashicorp/go-msgpack/codec"
"github.com/sean-/seed"
)
// pushPullScale is the minimum number of nodes
@ -22,72 +24,13 @@ import (
// while the 65th will triple it.
const pushPullScaleThreshold = 32
/*
* Contains an entry for each private block:
* 10.0.0.0/8
* 100.64.0.0/10
* 127.0.0.0/8
* 169.254.0.0/16
* 172.16.0.0/12
* 192.168.0.0/16
*/
var privateBlocks []*net.IPNet
var loopbackBlock *net.IPNet
const (
// Constant litWidth 2-8
lzwLitWidth = 8
)
func init() {
// Seed the random number generator
rand.Seed(time.Now().UnixNano())
// Add each private block
privateBlocks = make([]*net.IPNet, 6)
_, block, err := net.ParseCIDR("10.0.0.0/8")
if err != nil {
panic(fmt.Sprintf("Bad cidr. Got %v", err))
}
privateBlocks[0] = block
_, block, err = net.ParseCIDR("100.64.0.0/10")
if err != nil {
panic(fmt.Sprintf("Bad cidr. Got %v", err))
}
privateBlocks[1] = block
_, block, err = net.ParseCIDR("127.0.0.0/8")
if err != nil {
panic(fmt.Sprintf("Bad cidr. Got %v", err))
}
privateBlocks[2] = block
_, block, err = net.ParseCIDR("169.254.0.0/16")
if err != nil {
panic(fmt.Sprintf("Bad cidr. Got %v", err))
}
privateBlocks[3] = block
_, block, err = net.ParseCIDR("172.16.0.0/12")
if err != nil {
panic(fmt.Sprintf("Bad cidr. Got %v", err))
}
privateBlocks[4] = block
_, block, err = net.ParseCIDR("192.168.0.0/16")
if err != nil {
panic(fmt.Sprintf("Bad cidr. Got %v", err))
}
privateBlocks[5] = block
_, block, err = net.ParseCIDR("127.0.0.0/8")
if err != nil {
panic(fmt.Sprintf("Bad cidr. Got %v", err))
}
loopbackBlock = block
seed.Init()
}
// Decode reverses the encode operation on a byte slice input
@ -108,42 +51,6 @@ func encode(msgType messageType, in interface{}) (*bytes.Buffer, error) {
return buf, err
}
// GetPrivateIP returns the first private IP address found in a list of
// addresses.
func GetPrivateIP(addresses []net.Addr) (net.IP, error) {
var candidates []net.IP
// Find private IPv4 address
for _, rawAddr := range addresses {
var ip net.IP
switch addr := rawAddr.(type) {
case *net.IPAddr:
ip = addr.IP
case *net.IPNet:
ip = addr.IP
default:
continue
}
if ip.To4() == nil {
continue
}
if !IsPrivateIP(ip.String()) {
continue
}
candidates = append(candidates, ip)
}
numIps := len(candidates)
switch numIps {
case 0:
return nil, fmt.Errorf("No private IP address found")
case 1:
return candidates[0], nil
default:
return nil, fmt.Errorf("Multiple private IPs found. Please configure one.")
}
}
// Returns a random offset between 0 and n
func randomOffset(n int) int {
if n == 0 {
@ -155,8 +62,9 @@ func randomOffset(n int) int {
// suspicionTimeout computes the timeout that should be used when
// a node is suspected
func suspicionTimeout(suspicionMult, n int, interval time.Duration) time.Duration {
nodeScale := math.Ceil(math.Log10(float64(n + 1)))
timeout := time.Duration(suspicionMult) * time.Duration(nodeScale) * interval
nodeScale := math.Max(1.0, math.Log10(math.Max(1.0, float64(n))))
// multiply by 1000 to keep some precision because time.Duration is an int64 type
timeout := time.Duration(suspicionMult) * time.Duration(nodeScale*1000) * interval / 1000
return timeout
}
@ -189,9 +97,9 @@ func pushPullScale(interval time.Duration, n int) time.Duration {
return time.Duration(multiplier) * interval
}
// moveDeadNodes moves all the nodes in the dead state
// to the end of the slice and returns the index of the first dead node.
func moveDeadNodes(nodes []*nodeState) int {
// moveDeadNodes moves nodes that are dead and beyond the gossip to the dead interval
// to the end of the slice and returns the index of the first moved node.
func moveDeadNodes(nodes []*nodeState, gossipToTheDeadTime time.Duration) int {
numDead := 0
n := len(nodes)
for i := 0; i < n-numDead; i++ {
@ -199,6 +107,11 @@ func moveDeadNodes(nodes []*nodeState) int {
continue
}
// Respect the gossip to the dead interval
if time.Since(nodes[i].StateChange) <= gossipToTheDeadTime {
continue
}
// Move this node to the end
nodes[i], nodes[n-numDead-1] = nodes[n-numDead-1], nodes[i]
numDead++
@ -207,9 +120,10 @@ func moveDeadNodes(nodes []*nodeState) int {
return n - numDead
}
// kRandomNodes is used to select up to k random nodes, excluding a given
// node and any non-alive nodes. It is possible that less than k nodes are returned.
func kRandomNodes(k int, excludes []string, nodes []*nodeState) []*nodeState {
// kRandomNodes is used to select up to k random nodes, excluding any nodes where
// the filter function returns true. It is possible that less than k nodes are
// returned.
func kRandomNodes(k int, nodes []*nodeState, filterFn func(*nodeState) bool) []*nodeState {
n := len(nodes)
kNodes := make([]*nodeState, 0, k)
OUTER:
@ -221,16 +135,9 @@ OUTER:
idx := randomOffset(n)
node := nodes[idx]
// Exclude node if match
for _, exclude := range excludes {
if node.Name == exclude {
continue OUTER
}
}
// Exclude if not alive
if node.State != stateAlive {
continue
// Give the filter a shot at it.
if filterFn != nil && filterFn(node) {
continue OUTER
}
// Check if we have this node already
@ -310,27 +217,18 @@ func decodeCompoundMessage(buf []byte) (trunc int, parts [][]byte, err error) {
return
}
// Returns if the given IP is in a private block
func IsPrivateIP(ip_str string) bool {
ip := net.ParseIP(ip_str)
for _, priv := range privateBlocks {
if priv.Contains(ip) {
return true
}
}
return false
}
// Returns if the given IP is in a loopback block
func isLoopbackIP(ip_str string) bool {
ip := net.ParseIP(ip_str)
return loopbackBlock.Contains(ip)
}
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
// Given a string of the form "host", "host:port",
// "ipv6::addr" or "[ipv6::address]:port",
// return true if the string includes a port.
func hasPort(s string) bool {
return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
last := strings.LastIndex(s, ":")
if last == -1 {
return false
}
if s[0] == '[' {
return s[last-1] == ']'
}
return strings.Index(s, ":") == last
}
// compressPayload takes an opaque input buffer, compresses it
@ -390,3 +288,9 @@ func decompressBuffer(c *compress) ([]byte, error) {
// Return the uncompressed bytes
return b.Bytes(), nil
}
// joinHostPort returns the host:port form of an address, for use with a
// transport.
func joinHostPort(host string, port uint16) string {
return net.JoinHostPort(host, strconv.Itoa(int(port)))
}

54
libnetwork/vendor/github.com/sean-/seed/LICENSE generated vendored Normal file
View file

@ -0,0 +1,54 @@
MIT License
Copyright (c) 2017 Sean Chittenden
Copyright (c) 2016 Alex Dadgar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=====
Bits of Go-lang's `once.Do()` were cribbed and reused here, too.
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

44
libnetwork/vendor/github.com/sean-/seed/README.md generated vendored Normal file
View file

@ -0,0 +1,44 @@
# `seed` - Quickly Seed Go's Random Number Generator
Boiler-plate to securely [seed](https://en.wikipedia.org/wiki/Random_seed) Go's
random number generator (if possible). This library isn't anything fancy, it's
just a canonical way of seeding Go's random number generator. Cribbed from
[`Nomad`](https://github.com/hashicorp/nomad/commit/f89a993ec6b91636a3384dd568898245fbc273a1)
before it was moved into
[`Consul`](https://github.com/hashicorp/consul/commit/d695bcaae6e31ee307c11fdf55bb0bf46ea9fcf4)
and made into a helper function, and now further modularized to be a super
lightweight and reusable library.
Time is better than
[Go's default seed of `1`](https://golang.org/pkg/math/rand/#Seed), but friends
don't let friends use time as a seed to a random number generator. Use
`seed.MustInit()` instead.
`seed.Init()` is an idempotent and reentrant call that will return an error if
it can't seed the value the first time it is called. `Init()` is reentrant.
`seed.MustInit()` is idempotent and reentrant call that will `panic()` if it
can't seed the value the first time it is called. `MustInit()` is reentrant.
## Usage
```
package mypackage
import (
"github.com/sean-/seed"
)
// MustInit will panic() if it is unable to set a high-entropy random seed:
func init() {
seed.MustInit()
}
// Or if you want to not panic() and can actually handle this error:
func init() {
if secure, err := !seed.Init(); !secure {
// Handle the error
//panic(fmt.Sprintf("Unable to securely seed Go's RNG: %v", err))
}
}
```

84
libnetwork/vendor/github.com/sean-/seed/init.go generated vendored Normal file
View file

@ -0,0 +1,84 @@
package seed
import (
crand "crypto/rand"
"fmt"
"math"
"math/big"
"math/rand"
"sync"
"sync/atomic"
"time"
)
var (
m sync.Mutex
secure int32
seeded int32
)
func cryptoSeed() error {
defer atomic.StoreInt32(&seeded, 1)
var err error
var n *big.Int
n, err = crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
rand.Seed(time.Now().UTC().UnixNano())
return err
}
rand.Seed(n.Int64())
atomic.StoreInt32(&secure, 1)
return nil
}
// Init provides best-effort seeding (which is better than running with Go's
// default seed of 1). If `/dev/urandom` is available, Init() will seed Go's
// runtime with entropy from `/dev/urandom` and return true because the runtime
// was securely seeded. If Init() has already initialized the random number or
// it had failed to securely initialize the random number generation, Init()
// will return false. See MustInit().
func Init() (seededSecurely bool, err error) {
if atomic.LoadInt32(&seeded) == 1 {
return false, nil
}
// Slow-path
m.Lock()
defer m.Unlock()
if err := cryptoSeed(); err != nil {
return false, err
}
return true, nil
}
// MustInit provides guaranteed secure seeding. If `/dev/urandom` is not
// available, MustInit will panic() with an error indicating why reading from
// `/dev/urandom` failed. MustInit() will upgrade the seed if for some reason a
// call to Init() failed in the past.
func MustInit() {
if atomic.LoadInt32(&secure) == 1 {
return
}
// Slow-path
m.Lock()
defer m.Unlock()
if err := cryptoSeed(); err != nil {
panic(fmt.Sprintf("Unable to seed the random number generator: %v", err))
}
}
// Secure returns true if a cryptographically secure seed was used to
// initialize rand.
func Secure() bool {
return atomic.LoadInt32(&secure) == 1
}
// Seeded returns true if Init has seeded the random number generator.
func Seeded() bool {
return atomic.LoadInt32(&seeded) == 1
}