Merge pull request #6533 from hashicorp/vault_integration

add template function allowing user to read keys from vault
This commit is contained in:
Megan Marsh
2018-08-28 14:17:29 -07:00
committed by GitHub
164 changed files with 59675 additions and 60 deletions
+47
View File
@@ -13,6 +13,7 @@ import (
consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/version"
vaultapi "github.com/hashicorp/vault/api"
)
// InitTime is the UTC time when this package was initialized. It is
@@ -38,6 +39,7 @@ var FuncGens = map[string]FuncGenerator{
"user": funcGenUser,
"packer_version": funcGenPackerVersion,
"consul_key": funcGenConsul,
"vault": funcGenVault,
"upper": funcGenPrimitive(strings.ToUpper),
"lower": funcGenPrimitive(strings.ToLower),
@@ -206,3 +208,48 @@ func funcGenConsul(ctx *Context) interface{} {
return value, nil
}
}
func funcGenVault(ctx *Context) interface{} {
return func(path string, key string) (string, error) {
// Only allow interpolation from Vault when env vars are being read.
if !ctx.EnableEnv {
// The error message doesn't have to be that detailed since
// semantic checks should catch this.
return "", errors.New("Vault vars are only allowed in the variables section")
}
if token := os.Getenv("VAULT_TOKEN"); token == "" {
return "", errors.New("Must set VAULT_TOKEN env var in order to " +
"use vault template function")
}
// const EnvVaultAddress = "VAULT_ADDR"
// const EnvVaultToken = "VAULT_TOKEN"
vaultConfig := vaultapi.DefaultConfig()
cli, err := vaultapi.NewClient(vaultConfig)
if err != nil {
return "", errors.New(fmt.Sprintf("Error getting Vault client: %s", err))
}
secret, err := cli.Logical().Read(path)
if err != nil {
return "", errors.New(fmt.Sprintf("Error reading vault secret: %s", err))
}
if secret == nil {
return "", errors.New(fmt.Sprintf("Vault Secret does not exist at the given path."))
}
data, ok := secret.Data["data"]
if !ok {
// maybe ths is v1, not v2 kv store
value, ok := secret.Data[key]
if ok {
return value.(string), nil
}
// neither v1 nor v2 proudced a valid value
return "", errors.New(fmt.Sprintf("Vault data was empty at the "+
"given path. Warnings: %s", strings.Join(secret.Warnings, "; ")))
}
value := data.(map[string]interface{})[key].(string)
return value, nil
}
}
+15
View File
@@ -0,0 +1,15 @@
# This is the official list of Snappy-Go authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Damian Gryski <[email protected]>
Google Inc.
Jan Mercl <[email protected]>
Rodolfo Carvalho <[email protected]>
Sebastien Binet <[email protected]>
+37
View File
@@ -0,0 +1,37 @@
# This is the official list of people who can contribute
# (and typically have contributed) code to the Snappy-Go repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
# Please keep the list sorted.
Damian Gryski <[email protected]>
Jan Mercl <[email protected]>
Kai Backman <[email protected]>
Marc-Antoine Ruel <[email protected]>
Nigel Tao <[email protected]>
Rob Pike <[email protected]>
Rodolfo Carvalho <[email protected]>
Russ Cox <[email protected]>
Sebastien Binet <[email protected]>
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2011 The Snappy-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.
+107
View File
@@ -0,0 +1,107 @@
The Snappy compression format in the Go programming language.
To download and install from source:
$ go get github.com/golang/snappy
Unless otherwise noted, the Snappy-Go source files are distributed
under the BSD-style license found in the LICENSE file.
Benchmarks.
The golang/snappy benchmarks include compressing (Z) and decompressing (U) ten
or so files, the same set used by the C++ Snappy code (github.com/google/snappy
and note the "google", not "golang"). On an "Intel(R) Core(TM) i7-3770 CPU @
3.40GHz", Go's GOARCH=amd64 numbers as of 2016-05-29:
"go test -test.bench=."
_UFlat0-8 2.19GB/s ± 0% html
_UFlat1-8 1.41GB/s ± 0% urls
_UFlat2-8 23.5GB/s ± 2% jpg
_UFlat3-8 1.91GB/s ± 0% jpg_200
_UFlat4-8 14.0GB/s ± 1% pdf
_UFlat5-8 1.97GB/s ± 0% html4
_UFlat6-8 814MB/s ± 0% txt1
_UFlat7-8 785MB/s ± 0% txt2
_UFlat8-8 857MB/s ± 0% txt3
_UFlat9-8 719MB/s ± 1% txt4
_UFlat10-8 2.84GB/s ± 0% pb
_UFlat11-8 1.05GB/s ± 0% gaviota
_ZFlat0-8 1.04GB/s ± 0% html
_ZFlat1-8 534MB/s ± 0% urls
_ZFlat2-8 15.7GB/s ± 1% jpg
_ZFlat3-8 740MB/s ± 3% jpg_200
_ZFlat4-8 9.20GB/s ± 1% pdf
_ZFlat5-8 991MB/s ± 0% html4
_ZFlat6-8 379MB/s ± 0% txt1
_ZFlat7-8 352MB/s ± 0% txt2
_ZFlat8-8 396MB/s ± 1% txt3
_ZFlat9-8 327MB/s ± 1% txt4
_ZFlat10-8 1.33GB/s ± 1% pb
_ZFlat11-8 605MB/s ± 1% gaviota
"go test -test.bench=. -tags=noasm"
_UFlat0-8 621MB/s ± 2% html
_UFlat1-8 494MB/s ± 1% urls
_UFlat2-8 23.2GB/s ± 1% jpg
_UFlat3-8 1.12GB/s ± 1% jpg_200
_UFlat4-8 4.35GB/s ± 1% pdf
_UFlat5-8 609MB/s ± 0% html4
_UFlat6-8 296MB/s ± 0% txt1
_UFlat7-8 288MB/s ± 0% txt2
_UFlat8-8 309MB/s ± 1% txt3
_UFlat9-8 280MB/s ± 1% txt4
_UFlat10-8 753MB/s ± 0% pb
_UFlat11-8 400MB/s ± 0% gaviota
_ZFlat0-8 409MB/s ± 1% html
_ZFlat1-8 250MB/s ± 1% urls
_ZFlat2-8 12.3GB/s ± 1% jpg
_ZFlat3-8 132MB/s ± 0% jpg_200
_ZFlat4-8 2.92GB/s ± 0% pdf
_ZFlat5-8 405MB/s ± 1% html4
_ZFlat6-8 179MB/s ± 1% txt1
_ZFlat7-8 170MB/s ± 1% txt2
_ZFlat8-8 189MB/s ± 1% txt3
_ZFlat9-8 164MB/s ± 1% txt4
_ZFlat10-8 479MB/s ± 1% pb
_ZFlat11-8 270MB/s ± 1% gaviota
For comparison (Go's encoded output is byte-for-byte identical to C++'s), here
are the numbers from C++ Snappy's
make CXXFLAGS="-O2 -DNDEBUG -g" clean snappy_unittest.log && cat snappy_unittest.log
BM_UFlat/0 2.4GB/s html
BM_UFlat/1 1.4GB/s urls
BM_UFlat/2 21.8GB/s jpg
BM_UFlat/3 1.5GB/s jpg_200
BM_UFlat/4 13.3GB/s pdf
BM_UFlat/5 2.1GB/s html4
BM_UFlat/6 1.0GB/s txt1
BM_UFlat/7 959.4MB/s txt2
BM_UFlat/8 1.0GB/s txt3
BM_UFlat/9 864.5MB/s txt4
BM_UFlat/10 2.9GB/s pb
BM_UFlat/11 1.2GB/s gaviota
BM_ZFlat/0 944.3MB/s html (22.31 %)
BM_ZFlat/1 501.6MB/s urls (47.78 %)
BM_ZFlat/2 14.3GB/s jpg (99.95 %)
BM_ZFlat/3 538.3MB/s jpg_200 (73.00 %)
BM_ZFlat/4 8.3GB/s pdf (83.30 %)
BM_ZFlat/5 903.5MB/s html4 (22.52 %)
BM_ZFlat/6 336.0MB/s txt1 (57.88 %)
BM_ZFlat/7 312.3MB/s txt2 (61.91 %)
BM_ZFlat/8 353.1MB/s txt3 (54.99 %)
BM_ZFlat/9 289.9MB/s txt4 (66.26 %)
BM_ZFlat/10 1.2GB/s pb (19.68 %)
BM_ZFlat/11 527.4MB/s gaviota (37.72 %)
+237
View File
@@ -0,0 +1,237 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package snappy
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrCorrupt reports that the input is invalid.
ErrCorrupt = errors.New("snappy: corrupt input")
// ErrTooLarge reports that the uncompressed length is too large.
ErrTooLarge = errors.New("snappy: decoded block is too large")
// ErrUnsupported reports that the input isn't supported.
ErrUnsupported = errors.New("snappy: unsupported input")
errUnsupportedLiteralLength = errors.New("snappy: unsupported literal length")
)
// DecodedLen returns the length of the decoded block.
func DecodedLen(src []byte) (int, error) {
v, _, err := decodedLen(src)
return v, err
}
// decodedLen returns the length of the decoded block and the number of bytes
// that the length header occupied.
func decodedLen(src []byte) (blockLen, headerLen int, err error) {
v, n := binary.Uvarint(src)
if n <= 0 || v > 0xffffffff {
return 0, 0, ErrCorrupt
}
const wordSize = 32 << (^uint(0) >> 32 & 1)
if wordSize == 32 && v > 0x7fffffff {
return 0, 0, ErrTooLarge
}
return int(v), n, nil
}
const (
decodeErrCodeCorrupt = 1
decodeErrCodeUnsupportedLiteralLength = 2
)
// Decode returns the decoded form of src. The returned slice may be a sub-
// slice of dst if dst was large enough to hold the entire decoded block.
// Otherwise, a newly allocated slice will be returned.
//
// The dst and src must not overlap. It is valid to pass a nil dst.
func Decode(dst, src []byte) ([]byte, error) {
dLen, s, err := decodedLen(src)
if err != nil {
return nil, err
}
if dLen <= len(dst) {
dst = dst[:dLen]
} else {
dst = make([]byte, dLen)
}
switch decode(dst, src[s:]) {
case 0:
return dst, nil
case decodeErrCodeUnsupportedLiteralLength:
return nil, errUnsupportedLiteralLength
}
return nil, ErrCorrupt
}
// NewReader returns a new Reader that decompresses from r, using the framing
// format described at
// https://github.com/google/snappy/blob/master/framing_format.txt
func NewReader(r io.Reader) *Reader {
return &Reader{
r: r,
decoded: make([]byte, maxBlockSize),
buf: make([]byte, maxEncodedLenOfMaxBlockSize+checksumSize),
}
}
// Reader is an io.Reader that can read Snappy-compressed bytes.
type Reader struct {
r io.Reader
err error
decoded []byte
buf []byte
// decoded[i:j] contains decoded bytes that have not yet been passed on.
i, j int
readHeader bool
}
// Reset discards any buffered data, resets all state, and switches the Snappy
// reader to read from r. This permits reusing a Reader rather than allocating
// a new one.
func (r *Reader) Reset(reader io.Reader) {
r.r = reader
r.err = nil
r.i = 0
r.j = 0
r.readHeader = false
}
func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) {
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) {
r.err = ErrCorrupt
}
return false
}
return true
}
// Read satisfies the io.Reader interface.
func (r *Reader) Read(p []byte) (int, error) {
if r.err != nil {
return 0, r.err
}
for {
if r.i < r.j {
n := copy(p, r.decoded[r.i:r.j])
r.i += n
return n, nil
}
if !r.readFull(r.buf[:4], true) {
return 0, r.err
}
chunkType := r.buf[0]
if !r.readHeader {
if chunkType != chunkTypeStreamIdentifier {
r.err = ErrCorrupt
return 0, r.err
}
r.readHeader = true
}
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
if chunkLen > len(r.buf) {
r.err = ErrUnsupported
return 0, r.err
}
// The chunk types are specified at
// https://github.com/google/snappy/blob/master/framing_format.txt
switch chunkType {
case chunkTypeCompressedData:
// Section 4.2. Compressed data (chunk type 0x00).
if chunkLen < checksumSize {
r.err = ErrCorrupt
return 0, r.err
}
buf := r.buf[:chunkLen]
if !r.readFull(buf, false) {
return 0, r.err
}
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
buf = buf[checksumSize:]
n, err := DecodedLen(buf)
if err != nil {
r.err = err
return 0, r.err
}
if n > len(r.decoded) {
r.err = ErrCorrupt
return 0, r.err
}
if _, err := Decode(r.decoded, buf); err != nil {
r.err = err
return 0, r.err
}
if crc(r.decoded[:n]) != checksum {
r.err = ErrCorrupt
return 0, r.err
}
r.i, r.j = 0, n
continue
case chunkTypeUncompressedData:
// Section 4.3. Uncompressed data (chunk type 0x01).
if chunkLen < checksumSize {
r.err = ErrCorrupt
return 0, r.err
}
buf := r.buf[:checksumSize]
if !r.readFull(buf, false) {
return 0, r.err
}
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
// Read directly into r.decoded instead of via r.buf.
n := chunkLen - checksumSize
if n > len(r.decoded) {
r.err = ErrCorrupt
return 0, r.err
}
if !r.readFull(r.decoded[:n], false) {
return 0, r.err
}
if crc(r.decoded[:n]) != checksum {
r.err = ErrCorrupt
return 0, r.err
}
r.i, r.j = 0, n
continue
case chunkTypeStreamIdentifier:
// Section 4.1. Stream identifier (chunk type 0xff).
if chunkLen != len(magicBody) {
r.err = ErrCorrupt
return 0, r.err
}
if !r.readFull(r.buf[:len(magicBody)], false) {
return 0, r.err
}
for i := 0; i < len(magicBody); i++ {
if r.buf[i] != magicBody[i] {
r.err = ErrCorrupt
return 0, r.err
}
}
continue
}
if chunkType <= 0x7f {
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
r.err = ErrUnsupported
return 0, r.err
}
// Section 4.4 Padding (chunk type 0xfe).
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
if !r.readFull(r.buf[:chunkLen], false) {
return 0, r.err
}
}
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
// +build gc
// +build !noasm
package snappy
// decode has the same semantics as in decode_other.go.
//
//go:noescape
func decode(dst, src []byte) int
+490
View File
File diff suppressed because it is too large Load Diff
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !amd64 appengine !gc noasm
package snappy
// decode writes the decoding of src to dst. It assumes that the varint-encoded
// length of the decompressed bytes has already been read, and that len(dst)
// equals that length.
//
// It returns 0 on success or a decodeErrCodeXxx error code on failure.
func decode(dst, src []byte) int {
var d, s, offset, length int
for s < len(src) {
switch src[s] & 0x03 {
case tagLiteral:
x := uint32(src[s] >> 2)
switch {
case x < 60:
s++
case x == 60:
s += 2
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-1])
case x == 61:
s += 3
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-2]) | uint32(src[s-1])<<8
case x == 62:
s += 4
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
case x == 63:
s += 5
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
}
length = int(x) + 1
if length <= 0 {
return decodeErrCodeUnsupportedLiteralLength
}
if length > len(dst)-d || length > len(src)-s {
return decodeErrCodeCorrupt
}
copy(dst[d:], src[s:s+length])
d += length
s += length
continue
case tagCopy1:
s += 2
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
length = 4 + int(src[s-2])>>2&0x7
offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
case tagCopy2:
s += 3
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
length = 1 + int(src[s-3])>>2
offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
case tagCopy4:
s += 5
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
length = 1 + int(src[s-5])>>2
offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
}
if offset <= 0 || d < offset || length > len(dst)-d {
return decodeErrCodeCorrupt
}
// Copy from an earlier sub-slice of dst to a later sub-slice. Unlike
// the built-in copy function, this byte-by-byte copy always runs
// forwards, even if the slices overlap. Conceptually, this is:
//
// d += forwardCopy(dst[d:d+length], dst[d-offset:])
for end := d + length; d != end; d++ {
dst[d] = dst[d-offset]
}
}
if d != len(dst) {
return decodeErrCodeCorrupt
}
return 0
}
+285
View File
@@ -0,0 +1,285 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package snappy
import (
"encoding/binary"
"errors"
"io"
)
// Encode returns the encoded form of src. The returned slice may be a sub-
// slice of dst if dst was large enough to hold the entire encoded block.
// Otherwise, a newly allocated slice will be returned.
//
// The dst and src must not overlap. It is valid to pass a nil dst.
func Encode(dst, src []byte) []byte {
if n := MaxEncodedLen(len(src)); n < 0 {
panic(ErrTooLarge)
} else if len(dst) < n {
dst = make([]byte, n)
}
// The block starts with the varint-encoded length of the decompressed bytes.
d := binary.PutUvarint(dst, uint64(len(src)))
for len(src) > 0 {
p := src
src = nil
if len(p) > maxBlockSize {
p, src = p[:maxBlockSize], p[maxBlockSize:]
}
if len(p) < minNonLiteralBlockSize {
d += emitLiteral(dst[d:], p)
} else {
d += encodeBlock(dst[d:], p)
}
}
return dst[:d]
}
// inputMargin is the minimum number of extra input bytes to keep, inside
// encodeBlock's inner loop. On some architectures, this margin lets us
// implement a fast path for emitLiteral, where the copy of short (<= 16 byte)
// literals can be implemented as a single load to and store from a 16-byte
// register. That literal's actual length can be as short as 1 byte, so this
// can copy up to 15 bytes too much, but that's OK as subsequent iterations of
// the encoding loop will fix up the copy overrun, and this inputMargin ensures
// that we don't overrun the dst and src buffers.
const inputMargin = 16 - 1
// minNonLiteralBlockSize is the minimum size of the input to encodeBlock that
// could be encoded with a copy tag. This is the minimum with respect to the
// algorithm used by encodeBlock, not a minimum enforced by the file format.
//
// The encoded output must start with at least a 1 byte literal, as there are
// no previous bytes to copy. A minimal (1 byte) copy after that, generated
// from an emitCopy call in encodeBlock's main loop, would require at least
// another inputMargin bytes, for the reason above: we want any emitLiteral
// calls inside encodeBlock's main loop to use the fast path if possible, which
// requires being able to overrun by inputMargin bytes. Thus,
// minNonLiteralBlockSize equals 1 + 1 + inputMargin.
//
// The C++ code doesn't use this exact threshold, but it could, as discussed at
// https://groups.google.com/d/topic/snappy-compression/oGbhsdIJSJ8/discussion
// The difference between Go (2+inputMargin) and C++ (inputMargin) is purely an
// optimization. It should not affect the encoded form. This is tested by
// TestSameEncodingAsCppShortCopies.
const minNonLiteralBlockSize = 1 + 1 + inputMargin
// MaxEncodedLen returns the maximum length of a snappy block, given its
// uncompressed length.
//
// It will return a negative value if srcLen is too large to encode.
func MaxEncodedLen(srcLen int) int {
n := uint64(srcLen)
if n > 0xffffffff {
return -1
}
// Compressed data can be defined as:
// compressed := item* literal*
// item := literal* copy
//
// The trailing literal sequence has a space blowup of at most 62/60
// since a literal of length 60 needs one tag byte + one extra byte
// for length information.
//
// Item blowup is trickier to measure. Suppose the "copy" op copies
// 4 bytes of data. Because of a special check in the encoding code,
// we produce a 4-byte copy only if the offset is < 65536. Therefore
// the copy op takes 3 bytes to encode, and this type of item leads
// to at most the 62/60 blowup for representing literals.
//
// Suppose the "copy" op copies 5 bytes of data. If the offset is big
// enough, it will take 5 bytes to encode the copy op. Therefore the
// worst case here is a one-byte literal followed by a five-byte copy.
// That is, 6 bytes of input turn into 7 bytes of "compressed" data.
//
// This last factor dominates the blowup, so the final estimate is:
n = 32 + n + n/6
if n > 0xffffffff {
return -1
}
return int(n)
}
var errClosed = errors.New("snappy: Writer is closed")
// NewWriter returns a new Writer that compresses to w.
//
// The Writer returned does not buffer writes. There is no need to Flush or
// Close such a Writer.
//
// Deprecated: the Writer returned is not suitable for many small writes, only
// for few large writes. Use NewBufferedWriter instead, which is efficient
// regardless of the frequency and shape of the writes, and remember to Close
// that Writer when done.
func NewWriter(w io.Writer) *Writer {
return &Writer{
w: w,
obuf: make([]byte, obufLen),
}
}
// NewBufferedWriter returns a new Writer that compresses to w, using the
// framing format described at
// https://github.com/google/snappy/blob/master/framing_format.txt
//
// The Writer returned buffers writes. Users must call Close to guarantee all
// data has been forwarded to the underlying io.Writer. They may also call
// Flush zero or more times before calling Close.
func NewBufferedWriter(w io.Writer) *Writer {
return &Writer{
w: w,
ibuf: make([]byte, 0, maxBlockSize),
obuf: make([]byte, obufLen),
}
}
// Writer is an io.Writer that can write Snappy-compressed bytes.
type Writer struct {
w io.Writer
err error
// ibuf is a buffer for the incoming (uncompressed) bytes.
//
// Its use is optional. For backwards compatibility, Writers created by the
// NewWriter function have ibuf == nil, do not buffer incoming bytes, and
// therefore do not need to be Flush'ed or Close'd.
ibuf []byte
// obuf is a buffer for the outgoing (compressed) bytes.
obuf []byte
// wroteStreamHeader is whether we have written the stream header.
wroteStreamHeader bool
}
// Reset discards the writer's state and switches the Snappy writer to write to
// w. This permits reusing a Writer rather than allocating a new one.
func (w *Writer) Reset(writer io.Writer) {
w.w = writer
w.err = nil
if w.ibuf != nil {
w.ibuf = w.ibuf[:0]
}
w.wroteStreamHeader = false
}
// Write satisfies the io.Writer interface.
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
if w.ibuf == nil {
// Do not buffer incoming bytes. This does not perform or compress well
// if the caller of Writer.Write writes many small slices. This
// behavior is therefore deprecated, but still supported for backwards
// compatibility with code that doesn't explicitly Flush or Close.
return w.write(p)
}
// The remainder of this method is based on bufio.Writer.Write from the
// standard library.
for len(p) > (cap(w.ibuf)-len(w.ibuf)) && w.err == nil {
var n int
if len(w.ibuf) == 0 {
// Large write, empty buffer.
// Write directly from p to avoid copy.
n, _ = w.write(p)
} else {
n = copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
w.ibuf = w.ibuf[:len(w.ibuf)+n]
w.Flush()
}
nRet += n
p = p[n:]
}
if w.err != nil {
return nRet, w.err
}
n := copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
w.ibuf = w.ibuf[:len(w.ibuf)+n]
nRet += n
return nRet, nil
}
func (w *Writer) write(p []byte) (nRet int, errRet error) {
if w.err != nil {
return 0, w.err
}
for len(p) > 0 {
obufStart := len(magicChunk)
if !w.wroteStreamHeader {
w.wroteStreamHeader = true
copy(w.obuf, magicChunk)
obufStart = 0
}
var uncompressed []byte
if len(p) > maxBlockSize {
uncompressed, p = p[:maxBlockSize], p[maxBlockSize:]
} else {
uncompressed, p = p, nil
}
checksum := crc(uncompressed)
// Compress the buffer, discarding the result if the improvement
// isn't at least 12.5%.
compressed := Encode(w.obuf[obufHeaderLen:], uncompressed)
chunkType := uint8(chunkTypeCompressedData)
chunkLen := 4 + len(compressed)
obufEnd := obufHeaderLen + len(compressed)
if len(compressed) >= len(uncompressed)-len(uncompressed)/8 {
chunkType = chunkTypeUncompressedData
chunkLen = 4 + len(uncompressed)
obufEnd = obufHeaderLen
}
// Fill in the per-chunk header that comes before the body.
w.obuf[len(magicChunk)+0] = chunkType
w.obuf[len(magicChunk)+1] = uint8(chunkLen >> 0)
w.obuf[len(magicChunk)+2] = uint8(chunkLen >> 8)
w.obuf[len(magicChunk)+3] = uint8(chunkLen >> 16)
w.obuf[len(magicChunk)+4] = uint8(checksum >> 0)
w.obuf[len(magicChunk)+5] = uint8(checksum >> 8)
w.obuf[len(magicChunk)+6] = uint8(checksum >> 16)
w.obuf[len(magicChunk)+7] = uint8(checksum >> 24)
if _, err := w.w.Write(w.obuf[obufStart:obufEnd]); err != nil {
w.err = err
return nRet, err
}
if chunkType == chunkTypeUncompressedData {
if _, err := w.w.Write(uncompressed); err != nil {
w.err = err
return nRet, err
}
}
nRet += len(uncompressed)
}
return nRet, nil
}
// Flush flushes the Writer to its underlying io.Writer.
func (w *Writer) Flush() error {
if w.err != nil {
return w.err
}
if len(w.ibuf) == 0 {
return nil
}
w.write(w.ibuf)
w.ibuf = w.ibuf[:0]
return w.err
}
// Close calls Flush and then closes the Writer.
func (w *Writer) Close() error {
w.Flush()
ret := w.err
if w.err == nil {
w.err = errClosed
}
return ret
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
// +build gc
// +build !noasm
package snappy
// emitLiteral has the same semantics as in encode_other.go.
//
//go:noescape
func emitLiteral(dst, lit []byte) int
// emitCopy has the same semantics as in encode_other.go.
//
//go:noescape
func emitCopy(dst []byte, offset, length int) int
// extendMatch has the same semantics as in encode_other.go.
//
//go:noescape
func extendMatch(src []byte, i, j int) int
// encodeBlock has the same semantics as in encode_other.go.
//
//go:noescape
func encodeBlock(dst, src []byte) (d int)
+730
View File
File diff suppressed because it is too large Load Diff
+238
View File
@@ -0,0 +1,238 @@
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !amd64 appengine !gc noasm
package snappy
func load32(b []byte, i int) uint32 {
b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line.
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
func load64(b []byte, i int) uint64 {
b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line.
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
}
// emitLiteral writes a literal chunk and returns the number of bytes written.
//
// It assumes that:
// dst is long enough to hold the encoded bytes
// 1 <= len(lit) && len(lit) <= 65536
func emitLiteral(dst, lit []byte) int {
i, n := 0, uint(len(lit)-1)
switch {
case n < 60:
dst[0] = uint8(n)<<2 | tagLiteral
i = 1
case n < 1<<8:
dst[0] = 60<<2 | tagLiteral
dst[1] = uint8(n)
i = 2
default:
dst[0] = 61<<2 | tagLiteral
dst[1] = uint8(n)
dst[2] = uint8(n >> 8)
i = 3
}
return i + copy(dst[i:], lit)
}
// emitCopy writes a copy chunk and returns the number of bytes written.
//
// It assumes that:
// dst is long enough to hold the encoded bytes
// 1 <= offset && offset <= 65535
// 4 <= length && length <= 65535
func emitCopy(dst []byte, offset, length int) int {
i := 0
// The maximum length for a single tagCopy1 or tagCopy2 op is 64 bytes. The
// threshold for this loop is a little higher (at 68 = 64 + 4), and the
// length emitted down below is is a little lower (at 60 = 64 - 4), because
// it's shorter to encode a length 67 copy as a length 60 tagCopy2 followed
// by a length 7 tagCopy1 (which encodes as 3+2 bytes) than to encode it as
// a length 64 tagCopy2 followed by a length 3 tagCopy2 (which encodes as
// 3+3 bytes). The magic 4 in the 64±4 is because the minimum length for a
// tagCopy1 op is 4 bytes, which is why a length 3 copy has to be an
// encodes-as-3-bytes tagCopy2 instead of an encodes-as-2-bytes tagCopy1.
for length >= 68 {
// Emit a length 64 copy, encoded as 3 bytes.
dst[i+0] = 63<<2 | tagCopy2
dst[i+1] = uint8(offset)
dst[i+2] = uint8(offset >> 8)
i += 3
length -= 64
}
if length > 64 {
// Emit a length 60 copy, encoded as 3 bytes.
dst[i+0] = 59<<2 | tagCopy2
dst[i+1] = uint8(offset)
dst[i+2] = uint8(offset >> 8)
i += 3
length -= 60
}
if length >= 12 || offset >= 2048 {
// Emit the remaining copy, encoded as 3 bytes.
dst[i+0] = uint8(length-1)<<2 | tagCopy2
dst[i+1] = uint8(offset)
dst[i+2] = uint8(offset >> 8)
return i + 3
}
// Emit the remaining copy, encoded as 2 bytes.
dst[i+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
dst[i+1] = uint8(offset)
return i + 2
}
// extendMatch returns the largest k such that k <= len(src) and that
// src[i:i+k-j] and src[j:k] have the same contents.
//
// It assumes that:
// 0 <= i && i < j && j <= len(src)
func extendMatch(src []byte, i, j int) int {
for ; j < len(src) && src[i] == src[j]; i, j = i+1, j+1 {
}
return j
}
func hash(u, shift uint32) uint32 {
return (u * 0x1e35a7bd) >> shift
}
// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It
// assumes that the varint-encoded length of the decompressed bytes has already
// been written.
//
// It also assumes that:
// len(dst) >= MaxEncodedLen(len(src)) &&
// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
func encodeBlock(dst, src []byte) (d int) {
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
// The table element type is uint16, as s < sLimit and sLimit < len(src)
// and len(src) <= maxBlockSize and maxBlockSize == 65536.
const (
maxTableSize = 1 << 14
// tableMask is redundant, but helps the compiler eliminate bounds
// checks.
tableMask = maxTableSize - 1
)
shift := uint32(32 - 8)
for tableSize := 1 << 8; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
shift--
}
// In Go, all array elements are zero-initialized, so there is no advantage
// to a smaller tableSize per se. However, it matches the C++ algorithm,
// and in the asm versions of this code, we can get away with zeroing only
// the first tableSize elements.
var table [maxTableSize]uint16
// sLimit is when to stop looking for offset/length copies. The inputMargin
// lets us use a fast path for emitLiteral in the main loop, while we are
// looking for copies.
sLimit := len(src) - inputMargin
// nextEmit is where in src the next emitLiteral should start from.
nextEmit := 0
// The encoded form must start with a literal, as there are no previous
// bytes to copy, so we start looking for hash matches at s == 1.
s := 1
nextHash := hash(load32(src, s), shift)
for {
// Copied from the C++ snappy implementation:
//
// Heuristic match skipping: If 32 bytes are scanned with no matches
// found, start looking only at every other byte. If 32 more bytes are
// scanned (or skipped), look at every third byte, etc.. When a match
// is found, immediately go back to looking at every byte. This is a
// small loss (~5% performance, ~0.1% density) for compressible data
// due to more bookkeeping, but for non-compressible data (such as
// JPEG) it's a huge win since the compressor quickly "realizes" the
// data is incompressible and doesn't bother looking for matches
// everywhere.
//
// The "skip" variable keeps track of how many bytes there are since
// the last match; dividing it by 32 (ie. right-shifting by five) gives
// the number of bytes to move ahead for each iteration.
skip := 32
nextS := s
candidate := 0
for {
s = nextS
bytesBetweenHashLookups := skip >> 5
nextS = s + bytesBetweenHashLookups
skip += bytesBetweenHashLookups
if nextS > sLimit {
goto emitRemainder
}
candidate = int(table[nextHash&tableMask])
table[nextHash&tableMask] = uint16(s)
nextHash = hash(load32(src, nextS), shift)
if load32(src, s) == load32(src, candidate) {
break
}
}
// A 4-byte match has been found. We'll later see if more than 4 bytes
// match. But, prior to the match, src[nextEmit:s] are unmatched. Emit
// them as literal bytes.
d += emitLiteral(dst[d:], src[nextEmit:s])
// Call emitCopy, and then see if another emitCopy could be our next
// move. Repeat until we find no match for the input immediately after
// what was consumed by the last emitCopy call.
//
// If we exit this loop normally then we need to call emitLiteral next,
// though we don't yet know how big the literal will be. We handle that
// by proceeding to the next iteration of the main loop. We also can
// exit this loop via goto if we get close to exhausting the input.
for {
// Invariant: we have a 4-byte match at s, and no need to emit any
// literal bytes prior to s.
base := s
// Extend the 4-byte match as long as possible.
//
// This is an inlined version of:
// s = extendMatch(src, candidate+4, s+4)
s += 4
for i := candidate + 4; s < len(src) && src[i] == src[s]; i, s = i+1, s+1 {
}
d += emitCopy(dst[d:], base-candidate, s-base)
nextEmit = s
if s >= sLimit {
goto emitRemainder
}
// We could immediately start working at s now, but to improve
// compression we first update the hash table at s-1 and at s. If
// another emitCopy is not our next move, also calculate nextHash
// at s+1. At least on GOARCH=amd64, these three hash calculations
// are faster as one load64 call (with some shifts) instead of
// three load32 calls.
x := load64(src, s-1)
prevHash := hash(uint32(x>>0), shift)
table[prevHash&tableMask] = uint16(s - 1)
currHash := hash(uint32(x>>8), shift)
candidate = int(table[currHash&tableMask])
table[currHash&tableMask] = uint16(s)
if uint32(x>>8) != load32(src, candidate) {
nextHash = hash(uint32(x>>16), shift)
s++
break
}
}
}
emitRemainder:
if nextEmit < len(src) {
d += emitLiteral(dst[d:], src[nextEmit:])
}
return d
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package snappy implements the Snappy compression format. It aims for very
// high speeds and reasonable compression.
//
// There are actually two Snappy formats: block and stream. They are related,
// but different: trying to decompress block-compressed data as a Snappy stream
// will fail, and vice versa. The block format is the Decode and Encode
// functions and the stream format is the Reader and Writer types.
//
// The block format, the more common case, is used when the complete size (the
// number of bytes) of the original data is known upfront, at the time
// compression starts. The stream format, also known as the framing format, is
// for when that isn't always true.
//
// The canonical, C++ implementation is at https://github.com/google/snappy and
// it only implements the block format.
package snappy // import "github.com/golang/snappy"
import (
"hash/crc32"
)
/*
Each encoded block begins with the varint-encoded length of the decoded data,
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
first byte of each chunk is broken into its 2 least and 6 most significant bits
called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag.
Zero means a literal tag. All other values mean a copy tag.
For literal tags:
- If m < 60, the next 1 + m bytes are literal bytes.
- Otherwise, let n be the little-endian unsigned integer denoted by the next
m - 59 bytes. The next 1 + n bytes after that are literal bytes.
For copy tags, length bytes are copied from offset bytes ago, in the style of
Lempel-Ziv compression algorithms. In particular:
- For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12).
The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10
of the offset. The next byte is bits 0-7 of the offset.
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
The length is 1 + m. The offset is the little-endian unsigned integer
denoted by the next 2 bytes.
- For l == 3, this tag is a legacy format that is no longer issued by most
encoders. Nonetheless, the offset ranges in [0, 1<<32) and the length in
[1, 65). The length is 1 + m. The offset is the little-endian unsigned
integer denoted by the next 4 bytes.
*/
const (
tagLiteral = 0x00
tagCopy1 = 0x01
tagCopy2 = 0x02
tagCopy4 = 0x03
)
const (
checksumSize = 4
chunkHeaderSize = 4
magicChunk = "\xff\x06\x00\x00" + magicBody
magicBody = "sNaPpY"
// maxBlockSize is the maximum size of the input to encodeBlock. It is not
// part of the wire format per se, but some parts of the encoder assume
// that an offset fits into a uint16.
//
// Also, for the framing format (Writer type instead of Encode function),
// https://github.com/google/snappy/blob/master/framing_format.txt says
// that "the uncompressed data in a chunk must be no longer than 65536
// bytes".
maxBlockSize = 65536
// maxEncodedLenOfMaxBlockSize equals MaxEncodedLen(maxBlockSize), but is
// hard coded to be a const instead of a variable, so that obufLen can also
// be a const. Their equivalence is confirmed by
// TestMaxEncodedLenOfMaxBlockSize.
maxEncodedLenOfMaxBlockSize = 76490
obufHeaderLen = len(magicChunk) + checksumSize + chunkHeaderSize
obufLen = obufHeaderLen + maxEncodedLenOfMaxBlockSize
)
const (
chunkTypeCompressedData = 0x00
chunkTypeUncompressedData = 0x01
chunkTypePadding = 0xfe
chunkTypeStreamIdentifier = 0xff
)
var crcTable = crc32.MakeTable(crc32.Castagnoli)
// crc implements the checksum specified in section 3 of
// https://github.com/google/snappy/blob/master/framing_format.txt
func crc(b []byte) uint32 {
c := crc32.Update(0, crcTable, b)
return uint32(c>>15|c<<17) + 0xa282ead8
}
+8 -5
View File
@@ -14,13 +14,16 @@ makes `retryablehttp` very easy to drop into existing programs.
`retryablehttp` performs automatic retries under certain conditions. Mainly, if
an error is returned by the client (connection errors, etc.), or if a 500-range
response code is received, then a retry is invoked after a wait period.
Otherwise, the response is returned and left to the caller to interpret.
response code is received (except 501), then a retry is invoked after a wait
period. Otherwise, the response is returned and left to the caller to
interpret.
The main difference from `net/http` is that requests which take a request body
(POST/PUT et. al) require an `io.ReadSeeker` to be provided. This enables the
request body to be "rewound" if the initial request fails so that the full
request can be attempted again.
(POST/PUT et. al) can have the body provided in a number of ways (some more or
less efficient) that allow "rewinding" the request body if the initial request
fails so that the full request can be attempted again. See the
[godoc](http://godoc.org/github.com/hashicorp/go-retryablehttp) for more
details.
Example Use
===========
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
TOOLS= golang.org/x/tools/cover
GOCOVER_TMPFILE?= $(GOCOVER_FILE).tmp
GOCOVER_FILE?= .cover.out
GOCOVERHTML?= coverage.html
FIND=`/usr/bin/which 2> /dev/null gfind find | /usr/bin/grep -v ^no | /usr/bin/head -n 1`
XARGS=`/usr/bin/which 2> /dev/null gxargs xargs | /usr/bin/grep -v ^no | /usr/bin/head -n 1`
test:: $(GOCOVER_FILE)
@$(MAKE) -C cmd/sockaddr test
cover:: coverage_report
$(GOCOVER_FILE)::
@${FIND} . -type d ! -path '*cmd*' ! -path '*.git*' -print0 | ${XARGS} -0 -I % sh -ec "cd % && rm -f $(GOCOVER_TMPFILE) && go test -coverprofile=$(GOCOVER_TMPFILE)"
@echo 'mode: set' > $(GOCOVER_FILE)
@${FIND} . -type f ! -path '*cmd*' ! -path '*.git*' -name "$(GOCOVER_TMPFILE)" -print0 | ${XARGS} -0 -n1 cat $(GOCOVER_TMPFILE) | grep -v '^mode: ' >> ${PWD}/$(GOCOVER_FILE)
$(GOCOVERHTML): $(GOCOVER_FILE)
go tool cover -html=$(GOCOVER_FILE) -o $(GOCOVERHTML)
coverage_report:: $(GOCOVER_FILE)
go tool cover -html=$(GOCOVER_FILE)
audit_tools::
@go get -u github.com/golang/lint/golint && echo "Installed golint:"
@go get -u github.com/fzipp/gocyclo && echo "Installed gocyclo:"
@go get -u github.com/remyoudompheng/go-misc/deadcode && echo "Installed deadcode:"
@go get -u github.com/client9/misspell/cmd/misspell && echo "Installed misspell:"
@go get -u github.com/gordonklaus/ineffassign && echo "Installed ineffassign:"
audit::
deadcode
go tool vet -all *.go
go tool vet -shadow=true *.go
golint *.go
ineffassign .
gocyclo -over 65 *.go
misspell *.go
clean::
rm -f $(GOCOVER_FILE) $(GOCOVERHTML)
dev::
@go build
@$(MAKE) -B -C cmd/sockaddr sockaddr
install::
@go install
@$(MAKE) -C cmd/sockaddr install
doc::
@echo Visit: http://127.0.0.1:6161/pkg/github.com/hashicorp/go-sockaddr/
godoc -http=:6161 -goroot $GOROOT
world::
@set -e; \
for os in solaris darwin freebsd linux windows; do \
for arch in amd64; do \
printf "Building on %s-%s\n" "$${os}" "$${arch}" ; \
env GOOS="$${os}" GOARCH="$${arch}" go build -o /dev/null; \
done; \
done
$(MAKE) -C cmd/sockaddr world
+373
View File
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
# go-sockaddr
## `sockaddr` Library
Socket address convenience functions for Go. `go-sockaddr` is a convenience
library that makes doing the right thing with IP addresses easy. `go-sockaddr`
is loosely modeled after the UNIX `sockaddr_t` and creates a union of the family
of `sockaddr_t` types (see below for an ascii diagram). Library documentation
is available
at
[https://godoc.org/github.com/hashicorp/go-sockaddr](https://godoc.org/github.com/hashicorp/go-sockaddr).
The primary intent of the library was to make it possible to define heuristics
for selecting the correct IP addresses when a configuration is evaluated at
runtime. See
the
[docs](https://godoc.org/github.com/hashicorp/go-sockaddr),
[`template` package](https://godoc.org/github.com/hashicorp/go-sockaddr/template),
tests,
and
[CLI utility](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr)
for details and hints as to how to use this library.
For example, with this library it is possible to find an IP address that:
* is attached to a default route
([`GetDefaultInterfaces()`](https://godoc.org/github.com/hashicorp/go-sockaddr#GetDefaultInterfaces))
* is contained within a CIDR block ([`IfByNetwork()`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByNetwork))
* is an RFC1918 address
([`IfByRFC("1918")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC))
* is ordered
([`OrderedIfAddrBy(args)`](https://godoc.org/github.com/hashicorp/go-sockaddr#OrderedIfAddrBy) where
`args` includes, but is not limited
to,
[`AscIfType`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscIfType),
[`AscNetworkSize`](https://godoc.org/github.com/hashicorp/go-sockaddr#AscNetworkSize))
* excludes all IPv6 addresses
([`IfByType("^(IPv4)$")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByType))
* is larger than a `/32`
([`IfByMaskSize(32)`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByMaskSize))
* is not on a `down` interface
([`ExcludeIfs("flags", "down")`](https://godoc.org/github.com/hashicorp/go-sockaddr#ExcludeIfs))
* preferences an IPv6 address over an IPv4 address
([`SortIfByType()`](https://godoc.org/github.com/hashicorp/go-sockaddr#SortIfByType) +
[`ReverseIfAddrs()`](https://godoc.org/github.com/hashicorp/go-sockaddr#ReverseIfAddrs)); and
* excludes any IP in RFC6890 address
([`IfByRFC("6890")`](https://godoc.org/github.com/hashicorp/go-sockaddr#IfByRFC))
Or any combination or variation therein.
There are also a few simple helper functions such as `GetPublicIP` and
`GetPrivateIP` which both return strings and select the first public or private
IP address on the default interface, respectively. Similarly, there is also a
helper function called `GetInterfaceIP` which returns the first usable IP
address on the named interface.
## `sockaddr` CLI
Given the possible complexity of the `sockaddr` library, there is a CLI utility
that accompanies the library, also
called
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr).
The
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr)
utility exposes nearly all of the functionality of the library and can be used
either as an administrative tool or testing tool. To install
the
[`sockaddr`](https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr),
run:
```text
$ go get -u github.com/hashicorp/go-sockaddr/cmd/sockaddr
```
If you're familiar with UNIX's `sockaddr` struct's, the following diagram
mapping the C `sockaddr` (top) to `go-sockaddr` structs (bottom) and
interfaces will be helpful:
```
+-------------------------------------------------------+
| |
| sockaddr |
| SockAddr |
| |
| +--------------+ +----------------------------------+ |
| | sockaddr_un | | | |
| | SockAddrUnix | | sockaddr_in{,6} | |
| +--------------+ | IPAddr | |
| | | |
| | +-------------+ +--------------+ | |
| | | sockaddr_in | | sockaddr_in6 | | |
| | | IPv4Addr | | IPv6Addr | | |
| | +-------------+ +--------------+ | |
| | | |
| +----------------------------------+ |
| |
+-------------------------------------------------------+
```
## Inspiration and Design
There were many subtle inspirations that led to this design, but the most direct
inspiration for the filtering syntax was
OpenBSD's
[`pf.conf(5)`](https://www.freebsd.org/cgi/man.cgi?query=pf.conf&apropos=0&sektion=0&arch=default&format=html#PARAMETERS) firewall
syntax that lets you select the first IP address on a given named interface.
The original problem stemmed from:
* needing to create immutable images using [Packer](https://www.packer.io) that
ran the [Consul](https://www.consul.io) process (Consul can only use one IP
address at a time);
* images that may or may not have multiple interfaces or IP addresses at
runtime; and
* we didn't want to rely on configuration management to render out the correct
IP address if the VM image was being used in an auto-scaling group.
Instead we needed some way to codify a heuristic that would correctly select the
right IP address but the input parameters were not known when the image was
created.
+5
View File
@@ -0,0 +1,5 @@
/*
Package sockaddr is a Go implementation of the UNIX socket family data types and
related helper functions.
*/
package sockaddr
+254
View File
@@ -0,0 +1,254 @@
package sockaddr
import "strings"
// 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
}
// GetPrivateIPs returns a string with all IP addresses that are part of RFC
// 6890 (regardless of whether or not there is a default route, unlike
// GetPublicIP). If the system can't find any RFC 6890 IP addresses, an empty
// string will be returned instead. This function is the `eval` equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | include "RFC" "6890" | join "address" " "}}'
/// ```
func GetPrivateIPs() (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
} else if len(ifAddrs) < 1 {
return "", nil
}
ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP)
if len(ifAddrs) == 0 {
return "", nil
}
OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs)
ifAddrs, _, err = IfByRFC("6890", ifAddrs)
if err != nil {
return "", err
} else if len(ifAddrs) == 0 {
return "", nil
}
_, ifAddrs, err = IfByRFC(ForwardingBlacklistRFC, ifAddrs)
if err != nil {
return "", err
} else if len(ifAddrs) == 0 {
return "", nil
}
ips := make([]string, 0, len(ifAddrs))
for _, ifAddr := range ifAddrs {
ip := *ToIPAddr(ifAddr.SockAddr)
s := ip.NetIP().String()
ips = append(ips, s)
}
return strings.Join(ips, " "), nil
}
// GetPublicIP returns a string with a single IP address that is NOT part of RFC
// 6890 and has a default route. If the system can't determine its IP address
// or find a non RFC 6890 IP address, an empty string will be returned instead.
// 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
}
// GetPublicIPs returns a string with all IP addresses that are NOT part of RFC
// 6890 (regardless of whether or not there is a default route, unlike
// GetPublicIP). If the system can't find any non RFC 6890 IP addresses, an
// empty string will be returned instead. This function is the `eval`
// equivalent of:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | exclude "RFC" "6890" | join "address" " "}}'
/// ```
func GetPublicIPs() (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
} else if len(ifAddrs) < 1 {
return "", nil
}
ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP)
if len(ifAddrs) == 0 {
return "", nil
}
OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs)
_, ifAddrs, err = IfByRFC("6890", ifAddrs)
if err != nil {
return "", err
} else if len(ifAddrs) == 0 {
return "", nil
}
ips := make([]string, 0, len(ifAddrs))
for _, ifAddr := range ifAddrs {
ip := *ToIPAddr(ifAddr.SockAddr)
s := ip.NetIP().String()
ips = append(ips, s)
}
return strings.Join(ips, " "), nil
}
// GetInterfaceIP returns a string with a single IP address sorted by the size
// of the network (i.e. IP addresses with a smaller netmask, larger network
// size, are sorted first). This function is the `eval` equivalent of:
//
// ```
// $ 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
}
// GetInterfaceIPs returns a string with all IPs, sorted by the size of the
// network (i.e. IP addresses with a smaller netmask, larger network size, are
// sorted first), on a named interface. This function is the `eval` equivalent
// of:
//
// ```
// $ sockaddr eval -r '{{GetAllInterfaces | include "name" <<ARG>> | sort "type,size" | join "address" " "}}'
/// ```
func GetInterfaceIPs(namedIfRE string) (string, error) {
ifAddrs, err := GetAllInterfaces()
if err != nil {
return "", err
}
ifAddrs, _, err = IfByName(namedIfRE, ifAddrs)
if err != nil {
return "", err
}
ifAddrs, err = SortIfBy("+type,+size", ifAddrs)
if err != nil {
return "", err
}
if len(ifAddrs) == 0 {
return "", err
}
ips := make([]string, 0, len(ifAddrs))
for _, ifAddr := range ifAddrs {
ip := *ToIPAddr(ifAddr.SockAddr)
s := ip.NetIP().String()
ips = append(ips, s)
}
return strings.Join(ips, " "), nil
}
// IfAddrAttrs returns a list of attributes supported by the IfAddr type
func IfAddrAttrs() []AttrName {
return ifAddrAttrs
}
// 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
},
}
}
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
package sockaddr
import (
"fmt"
"net"
)
// IfAddr is a union of a SockAddr and a net.Interface.
type IfAddr struct {
SockAddr
net.Interface
}
// Attr returns the named attribute as a string
func (ifAddr IfAddr) Attr(attrName AttrName) (string, error) {
val := IfAddrAttr(ifAddr, attrName)
if val != "" {
return val, nil
}
return Attr(ifAddr.SockAddr, attrName)
}
// Attr returns the named attribute as a string
func Attr(sa SockAddr, attrName AttrName) (string, error) {
switch sockType := sa.Type(); {
case sockType&TypeIP != 0:
ip := *ToIPAddr(sa)
attrVal := IPAddrAttr(ip, attrName)
if attrVal != "" {
return attrVal, nil
}
if sockType == TypeIPv4 {
ipv4 := *ToIPv4Addr(sa)
attrVal := IPv4AddrAttr(ipv4, attrName)
if attrVal != "" {
return attrVal, nil
}
} else if sockType == TypeIPv6 {
ipv6 := *ToIPv6Addr(sa)
attrVal := IPv6AddrAttr(ipv6, attrName)
if attrVal != "" {
return attrVal, nil
}
}
case sockType == TypeUnix:
us := *ToUnixSock(sa)
attrVal := UnixSockAttr(us, attrName)
if attrVal != "" {
return attrVal, nil
}
}
// Non type-specific attributes
switch attrName {
case "string":
return sa.String(), nil
case "type":
return sa.Type().String(), nil
}
return "", fmt.Errorf("unsupported attribute name %q", attrName)
}
+169
View File
@@ -0,0 +1,169 @@
package sockaddr
import (
"fmt"
"math/big"
"net"
"strings"
)
// Constants for the sizes of IPv3, IPv4, and IPv6 address types.
const (
IPv3len = 6
IPv4len = 4
IPv6len = 16
)
// IPAddr is a generic IP address interface for IPv4 and IPv6 addresses,
// networks, and socket endpoints.
type IPAddr interface {
SockAddr
AddressBinString() string
AddressHexString() string
Cmp(SockAddr) int
CmpAddress(SockAddr) int
CmpPort(SockAddr) int
FirstUsable() IPAddr
Host() IPAddr
IPPort() IPPort
LastUsable() IPAddr
Maskbits() int
NetIP() *net.IP
NetIPMask() *net.IPMask
NetIPNet() *net.IPNet
Network() IPAddr
Octets() []int
}
// IPPort is the type for an IP port number for the TCP and UDP IP transports.
type IPPort uint16
// IPPrefixLen is a typed integer representing the prefix length for a given
// IPAddr.
type IPPrefixLen byte
// ipAddrAttrMap is a map of the IPAddr type-specific attributes.
var ipAddrAttrMap map[AttrName]func(IPAddr) string
var ipAddrAttrs []AttrName
func init() {
ipAddrInit()
}
// NewIPAddr creates a new IPAddr from a string. Returns nil if the string is
// not an IPv4 or an IPv6 address.
func NewIPAddr(addr string) (IPAddr, error) {
ipv4Addr, err := NewIPv4Addr(addr)
if err == nil {
return ipv4Addr, nil
}
ipv6Addr, err := NewIPv6Addr(addr)
if err == nil {
return ipv6Addr, nil
}
return nil, fmt.Errorf("invalid IPAddr %v", addr)
}
// IPAddrAttr returns a string representation of an attribute for the given
// IPAddr.
func IPAddrAttr(ip IPAddr, selector AttrName) string {
fn, found := ipAddrAttrMap[selector]
if !found {
return ""
}
return fn(ip)
}
// IPAttrs returns a list of attributes supported by the IPAddr type
func IPAttrs() []AttrName {
return ipAddrAttrs
}
// MustIPAddr is a helper method that must return an IPAddr or panic on invalid
// input.
func MustIPAddr(addr string) IPAddr {
ip, err := NewIPAddr(addr)
if err != nil {
panic(fmt.Sprintf("Unable to create an IPAddr from %+q: %v", addr, err))
}
return ip
}
// ipAddrInit is called once at init()
func ipAddrInit() {
// Sorted for human readability
ipAddrAttrs = []AttrName{
"host",
"address",
"port",
"netmask",
"network",
"mask_bits",
"binary",
"hex",
"first_usable",
"last_usable",
"octets",
}
ipAddrAttrMap = map[AttrName]func(ip IPAddr) string{
"address": func(ip IPAddr) string {
return ip.NetIP().String()
},
"binary": func(ip IPAddr) string {
return ip.AddressBinString()
},
"first_usable": func(ip IPAddr) string {
return ip.FirstUsable().String()
},
"hex": func(ip IPAddr) string {
return ip.AddressHexString()
},
"host": func(ip IPAddr) string {
return ip.Host().String()
},
"last_usable": func(ip IPAddr) string {
return ip.LastUsable().String()
},
"mask_bits": func(ip IPAddr) string {
return fmt.Sprintf("%d", ip.Maskbits())
},
"netmask": func(ip IPAddr) string {
switch v := ip.(type) {
case IPv4Addr:
ipv4Mask := IPv4Addr{
Address: IPv4Address(v.Mask),
Mask: IPv4HostMask,
}
return ipv4Mask.String()
case IPv6Addr:
ipv6Mask := new(big.Int)
ipv6Mask.Set(v.Mask)
ipv6MaskAddr := IPv6Addr{
Address: IPv6Address(ipv6Mask),
Mask: ipv6HostMask,
}
return ipv6MaskAddr.String()
default:
return fmt.Sprintf("<unsupported type: %T>", ip)
}
},
"network": func(ip IPAddr) string {
return ip.Network().NetIP().String()
},
"octets": func(ip IPAddr) string {
octets := ip.Octets()
octetStrs := make([]string, 0, len(octets))
for _, octet := range octets {
octetStrs = append(octetStrs, fmt.Sprintf("%d", octet))
}
return strings.Join(octetStrs, " ")
},
"port": func(ip IPAddr) string {
return fmt.Sprintf("%d", ip.IPPort())
},
}
}
+98
View File
@@ -0,0 +1,98 @@
package sockaddr
import "bytes"
type IPAddrs []IPAddr
func (s IPAddrs) Len() int { return len(s) }
func (s IPAddrs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// // SortIPAddrsByCmp is a type that satisfies sort.Interface and can be used
// // by the routines in this package. The SortIPAddrsByCmp type is used to
// // sort IPAddrs by Cmp()
// type SortIPAddrsByCmp struct{ IPAddrs }
// // Less reports whether the element with index i should sort before the
// // element with index j.
// func (s SortIPAddrsByCmp) Less(i, j int) bool {
// // Sort by Type, then address, then port number.
// return Less(s.IPAddrs[i], s.IPAddrs[j])
// }
// SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and
// can be used by the routines in this package. The
// SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest
// network (most specific to largest network).
type SortIPAddrsByNetworkSize struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsByNetworkSize) Less(i, j int) bool {
// Sort masks with a larger binary value (i.e. fewer hosts per network
// prefix) after masks with a smaller value (larger number of hosts per
// prefix).
switch bytes.Compare([]byte(*s.IPAddrs[i].NetIPMask()), []byte(*s.IPAddrs[j].NetIPMask())) {
case 0:
// Fall through to the second test if the net.IPMasks are the
// same.
break
case 1:
return true
case -1:
return false
default:
panic("bad, m'kay?")
}
// Sort IPs based on the length (i.e. prefer IPv4 over IPv6).
iLen := len(*s.IPAddrs[i].NetIP())
jLen := len(*s.IPAddrs[j].NetIP())
if iLen != jLen {
return iLen > jLen
}
// Sort IPs based on their network address from lowest to highest.
switch bytes.Compare(s.IPAddrs[i].NetIPNet().IP, s.IPAddrs[j].NetIPNet().IP) {
case 0:
break
case 1:
return false
case -1:
return true
default:
panic("lol wut?")
}
// If a host does not have a port set, it always sorts after hosts
// that have a port (e.g. a host with a /32 and port number is more
// specific and should sort first over a host with a /32 but no port
// set).
if s.IPAddrs[i].IPPort() == 0 || s.IPAddrs[j].IPPort() == 0 {
return false
}
return s.IPAddrs[i].IPPort() < s.IPAddrs[j].IPPort()
}
// SortIPAddrsBySpecificMaskLen is a type that satisfies sort.Interface and
// can be used by the routines in this package. The
// SortIPAddrsBySpecificMaskLen type is used to sort IPAddrs by smallest
// network (most specific to largest network).
type SortIPAddrsBySpecificMaskLen struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsBySpecificMaskLen) Less(i, j int) bool {
return s.IPAddrs[i].Maskbits() > s.IPAddrs[j].Maskbits()
}
// SortIPAddrsByBroadMaskLen is a type that satisfies sort.Interface and can
// be used by the routines in this package. The SortIPAddrsByBroadMaskLen
// type is used to sort IPAddrs by largest network (i.e. largest subnets
// first).
type SortIPAddrsByBroadMaskLen struct{ IPAddrs }
// Less reports whether the element with index i should sort before the
// element with index j.
func (s SortIPAddrsByBroadMaskLen) Less(i, j int) bool {
return s.IPAddrs[i].Maskbits() < s.IPAddrs[j].Maskbits()
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+948
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
package sockaddr
// RouteInterface specifies an interface for obtaining memoized route table and
// network information from a given OS.
type RouteInterface interface {
// GetDefaultInterfaceName returns the name of the interface that has a
// default route or an error and an empty string if a problem was
// encountered.
GetDefaultInterfaceName() (string, error)
}
// VisitCommands visits each command used by the platform-specific RouteInfo
// implementation.
func (ri routeInfo) VisitCommands(fn func(name string, cmd []string)) {
for k, v := range ri.cmds {
cmds := append([]string(nil), v...)
fn(k, cmds)
}
}
+36
View File
@@ -0,0 +1,36 @@
// +build darwin dragonfly freebsd netbsd openbsd
package sockaddr
import "os/exec"
var cmds map[string][]string = map[string][]string{
"route": {"/sbin/route", "-n", "get", "default"},
}
type routeInfo struct {
cmds map[string][]string
}
// NewRouteInfo returns a BSD-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
return routeInfo{
cmds: cmds,
}, nil
}
// GetDefaultInterfaceName returns the interface name attached to the default
// route on the default interface.
func (ri routeInfo) GetDefaultInterfaceName() (string, error) {
out, err := exec.Command(cmds["route"][0], cmds["route"][1:]...).Output()
if err != nil {
return "", err
}
var ifName string
if ifName, err = parseDefaultIfNameFromRoute(string(out)); err != nil {
return "", err
}
return ifName, nil
}

Some files were not shown because too many files have changed in this diff Show More