зеркало из https://github.com/microsoft/docker.git
Vendoring memberlist tag 0.1.0
Signed-off-by: Flavio Crisciani <flavio.crisciani@docker.com>
This commit is contained in:
Родитель
cfae6278be
Коммит
01fd4e49c3
|
@ -29,7 +29,9 @@ github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
|
|||
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
|
||||
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
|
||||
github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
|
||||
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/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e
|
||||
github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870
|
||||
github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
Package sockaddr is a Go implementation of the UNIX socket family data types and
|
||||
related helper functions.
|
||||
*/
|
||||
package sockaddr
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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))
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
36
vendor/github.com/hashicorp/go-sockaddr/route_info_bsd.go
сгенерированный
поставляемый
Normal file
36
vendor/github.com/hashicorp/go-sockaddr/route_info_bsd.go
сгенерированный
поставляемый
Normal 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
|
||||
}
|
10
vendor/github.com/hashicorp/go-sockaddr/route_info_default.go
сгенерированный
поставляемый
Normal file
10
vendor/github.com/hashicorp/go-sockaddr/route_info_default.go
сгенерированный
поставляемый
Normal 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)")
|
||||
}
|
37
vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go
сгенерированный
поставляемый
Normal file
37
vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go
сгенерированный
поставляемый
Normal 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
|
||||
}
|
37
vendor/github.com/hashicorp/go-sockaddr/route_info_solaris.go
сгенерированный
поставляемый
Normal file
37
vendor/github.com/hashicorp/go-sockaddr/route_info_solaris.go
сгенерированный
поставляемый
Normal 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
|
||||
}
|
41
vendor/github.com/hashicorp/go-sockaddr/route_info_windows.go
сгенерированный
поставляемый
Normal file
41
vendor/github.com/hashicorp/go-sockaddr/route_info_windows.go
сгенерированный
поставляемый
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
121
vendor/github.com/hashicorp/memberlist/mock_transport.go
сгенерированный
поставляемый
Normal file
121
vendor/github.com/hashicorp/memberlist/mock_transport.go
сгенерированный
поставляемый
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
289
vendor/github.com/hashicorp/memberlist/net_transport.go
сгенерированный
поставляемый
Normal file
289
vendor/github.com/hashicorp/memberlist/net_transport.go
сгенерированный
поставляемый
Normal 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
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
```
|
|
@ -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
|
||||
}
|
Загрузка…
Ссылка в новой задаче