Update dependencies
This commit is contained in:
202
vendor/github.com/google/btree/LICENSE
generated
vendored
Normal file
202
vendor/github.com/google/btree/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
10
vendor/github.com/google/btree/README.md
generated
vendored
Normal file
10
vendor/github.com/google/btree/README.md
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# BTree implementation for Go
|
||||
|
||||
This package provides an in-memory B-Tree implementation for Go, useful as
|
||||
an ordered, mutable data structure.
|
||||
|
||||
The API is based off of the wonderful
|
||||
http://godoc.org/github.com/petar/GoLLRB/llrb, and is meant to allow btree to
|
||||
act as a drop-in replacement for gollrb trees.
|
||||
|
||||
See http://godoc.org/github.com/google/btree for documentation.
|
||||
893
vendor/github.com/google/btree/btree.go
generated
vendored
Normal file
893
vendor/github.com/google/btree/btree.go
generated
vendored
Normal file
@@ -0,0 +1,893 @@
|
||||
// Copyright 2014 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
|
||||
// Package btree implements in-memory B-Trees of arbitrary degree.
|
||||
//
|
||||
// btree implements an in-memory B-Tree for use as an ordered data structure.
|
||||
// It is not meant for persistent storage solutions.
|
||||
//
|
||||
// It has a flatter structure than an equivalent red-black or other binary tree,
|
||||
// which in some cases yields better memory usage and/or performance.
|
||||
// See some discussion on the matter here:
|
||||
// http://google-opensource.blogspot.com/2013/01/c-containers-that-save-memory-and-time.html
|
||||
// Note, though, that this project is in no way related to the C++ B-Tree
|
||||
// implementation written about there.
|
||||
//
|
||||
// Within this tree, each node contains a slice of items and a (possibly nil)
|
||||
// slice of children. For basic numeric values or raw structs, this can cause
|
||||
// efficiency differences when compared to equivalent C++ template code that
|
||||
// stores values in arrays within the node:
|
||||
// * Due to the overhead of storing values as interfaces (each
|
||||
// value needs to be stored as the value itself, then 2 words for the
|
||||
// interface pointing to that value and its type), resulting in higher
|
||||
// memory use.
|
||||
// * Since interfaces can point to values anywhere in memory, values are
|
||||
// most likely not stored in contiguous blocks, resulting in a higher
|
||||
// number of cache misses.
|
||||
// These issues don't tend to matter, though, when working with strings or other
|
||||
// heap-allocated structures, since C++-equivalent structures also must store
|
||||
// pointers and also distribute their values across the heap.
|
||||
//
|
||||
// This implementation is designed to be a drop-in replacement to gollrb.LLRB
|
||||
// trees, (http://github.com/petar/gollrb), an excellent and probably the most
|
||||
// widely used ordered tree implementation in the Go ecosystem currently.
|
||||
// Its functions, therefore, exactly mirror those of
|
||||
// llrb.LLRB where possible. Unlike gollrb, though, we currently don't
|
||||
// support storing multiple equivalent values.
|
||||
package btree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Item represents a single object in the tree.
|
||||
type Item interface {
|
||||
// Less tests whether the current item is less than the given argument.
|
||||
//
|
||||
// This must provide a strict weak ordering.
|
||||
// If !a.Less(b) && !b.Less(a), we treat this to mean a == b (i.e. we can only
|
||||
// hold one of either a or b in the tree).
|
||||
Less(than Item) bool
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultFreeListSize = 32
|
||||
)
|
||||
|
||||
var (
|
||||
nilItems = make(items, 16)
|
||||
nilChildren = make(children, 16)
|
||||
)
|
||||
|
||||
// FreeList represents a free list of btree nodes. By default each
|
||||
// BTree has its own FreeList, but multiple BTrees can share the same
|
||||
// FreeList.
|
||||
// Two Btrees using the same freelist are safe for concurrent write access.
|
||||
type FreeList struct {
|
||||
mu sync.Mutex
|
||||
freelist []*node
|
||||
}
|
||||
|
||||
// NewFreeList creates a new free list.
|
||||
// size is the maximum size of the returned free list.
|
||||
func NewFreeList(size int) *FreeList {
|
||||
return &FreeList{freelist: make([]*node, 0, size)}
|
||||
}
|
||||
|
||||
func (f *FreeList) newNode() (n *node) {
|
||||
f.mu.Lock()
|
||||
index := len(f.freelist) - 1
|
||||
if index < 0 {
|
||||
f.mu.Unlock()
|
||||
return new(node)
|
||||
}
|
||||
n = f.freelist[index]
|
||||
f.freelist[index] = nil
|
||||
f.freelist = f.freelist[:index]
|
||||
f.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// freeNode adds the given node to the list, returning true if it was added
|
||||
// and false if it was discarded.
|
||||
func (f *FreeList) freeNode(n *node) (out bool) {
|
||||
f.mu.Lock()
|
||||
if len(f.freelist) < cap(f.freelist) {
|
||||
f.freelist = append(f.freelist, n)
|
||||
out = true
|
||||
}
|
||||
f.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// ItemIterator allows callers of Ascend* to iterate in-order over portions of
|
||||
// the tree. When this function returns false, iteration will stop and the
|
||||
// associated Ascend* function will immediately return.
|
||||
type ItemIterator func(i Item) bool
|
||||
|
||||
// New creates a new B-Tree with the given degree.
|
||||
//
|
||||
// New(2), for example, will create a 2-3-4 tree (each node contains 1-3 items
|
||||
// and 2-4 children).
|
||||
func New(degree int) *BTree {
|
||||
return NewWithFreeList(degree, NewFreeList(DefaultFreeListSize))
|
||||
}
|
||||
|
||||
// NewWithFreeList creates a new B-Tree that uses the given node free list.
|
||||
func NewWithFreeList(degree int, f *FreeList) *BTree {
|
||||
if degree <= 1 {
|
||||
panic("bad degree")
|
||||
}
|
||||
return &BTree{
|
||||
degree: degree,
|
||||
cow: ©OnWriteContext{freelist: f},
|
||||
}
|
||||
}
|
||||
|
||||
// items stores items in a node.
|
||||
type items []Item
|
||||
|
||||
// insertAt inserts a value into the given index, pushing all subsequent values
|
||||
// forward.
|
||||
func (s *items) insertAt(index int, item Item) {
|
||||
*s = append(*s, nil)
|
||||
if index < len(*s) {
|
||||
copy((*s)[index+1:], (*s)[index:])
|
||||
}
|
||||
(*s)[index] = item
|
||||
}
|
||||
|
||||
// removeAt removes a value at a given index, pulling all subsequent values
|
||||
// back.
|
||||
func (s *items) removeAt(index int) Item {
|
||||
item := (*s)[index]
|
||||
copy((*s)[index:], (*s)[index+1:])
|
||||
(*s)[len(*s)-1] = nil
|
||||
*s = (*s)[:len(*s)-1]
|
||||
return item
|
||||
}
|
||||
|
||||
// pop removes and returns the last element in the list.
|
||||
func (s *items) pop() (out Item) {
|
||||
index := len(*s) - 1
|
||||
out = (*s)[index]
|
||||
(*s)[index] = nil
|
||||
*s = (*s)[:index]
|
||||
return
|
||||
}
|
||||
|
||||
// truncate truncates this instance at index so that it contains only the
|
||||
// first index items. index must be less than or equal to length.
|
||||
func (s *items) truncate(index int) {
|
||||
var toClear items
|
||||
*s, toClear = (*s)[:index], (*s)[index:]
|
||||
for len(toClear) > 0 {
|
||||
toClear = toClear[copy(toClear, nilItems):]
|
||||
}
|
||||
}
|
||||
|
||||
// find returns the index where the given item should be inserted into this
|
||||
// list. 'found' is true if the item already exists in the list at the given
|
||||
// index.
|
||||
func (s items) find(item Item) (index int, found bool) {
|
||||
i := sort.Search(len(s), func(i int) bool {
|
||||
return item.Less(s[i])
|
||||
})
|
||||
if i > 0 && !s[i-1].Less(item) {
|
||||
return i - 1, true
|
||||
}
|
||||
return i, false
|
||||
}
|
||||
|
||||
// children stores child nodes in a node.
|
||||
type children []*node
|
||||
|
||||
// insertAt inserts a value into the given index, pushing all subsequent values
|
||||
// forward.
|
||||
func (s *children) insertAt(index int, n *node) {
|
||||
*s = append(*s, nil)
|
||||
if index < len(*s) {
|
||||
copy((*s)[index+1:], (*s)[index:])
|
||||
}
|
||||
(*s)[index] = n
|
||||
}
|
||||
|
||||
// removeAt removes a value at a given index, pulling all subsequent values
|
||||
// back.
|
||||
func (s *children) removeAt(index int) *node {
|
||||
n := (*s)[index]
|
||||
copy((*s)[index:], (*s)[index+1:])
|
||||
(*s)[len(*s)-1] = nil
|
||||
*s = (*s)[:len(*s)-1]
|
||||
return n
|
||||
}
|
||||
|
||||
// pop removes and returns the last element in the list.
|
||||
func (s *children) pop() (out *node) {
|
||||
index := len(*s) - 1
|
||||
out = (*s)[index]
|
||||
(*s)[index] = nil
|
||||
*s = (*s)[:index]
|
||||
return
|
||||
}
|
||||
|
||||
// truncate truncates this instance at index so that it contains only the
|
||||
// first index children. index must be less than or equal to length.
|
||||
func (s *children) truncate(index int) {
|
||||
var toClear children
|
||||
*s, toClear = (*s)[:index], (*s)[index:]
|
||||
for len(toClear) > 0 {
|
||||
toClear = toClear[copy(toClear, nilChildren):]
|
||||
}
|
||||
}
|
||||
|
||||
// node is an internal node in a tree.
|
||||
//
|
||||
// It must at all times maintain the invariant that either
|
||||
// * len(children) == 0, len(items) unconstrained
|
||||
// * len(children) == len(items) + 1
|
||||
type node struct {
|
||||
items items
|
||||
children children
|
||||
cow *copyOnWriteContext
|
||||
}
|
||||
|
||||
func (n *node) mutableFor(cow *copyOnWriteContext) *node {
|
||||
if n.cow == cow {
|
||||
return n
|
||||
}
|
||||
out := cow.newNode()
|
||||
if cap(out.items) >= len(n.items) {
|
||||
out.items = out.items[:len(n.items)]
|
||||
} else {
|
||||
out.items = make(items, len(n.items), cap(n.items))
|
||||
}
|
||||
copy(out.items, n.items)
|
||||
// Copy children
|
||||
if cap(out.children) >= len(n.children) {
|
||||
out.children = out.children[:len(n.children)]
|
||||
} else {
|
||||
out.children = make(children, len(n.children), cap(n.children))
|
||||
}
|
||||
copy(out.children, n.children)
|
||||
return out
|
||||
}
|
||||
|
||||
func (n *node) mutableChild(i int) *node {
|
||||
c := n.children[i].mutableFor(n.cow)
|
||||
n.children[i] = c
|
||||
return c
|
||||
}
|
||||
|
||||
// split splits the given node at the given index. The current node shrinks,
|
||||
// and this function returns the item that existed at that index and a new node
|
||||
// containing all items/children after it.
|
||||
func (n *node) split(i int) (Item, *node) {
|
||||
item := n.items[i]
|
||||
next := n.cow.newNode()
|
||||
next.items = append(next.items, n.items[i+1:]...)
|
||||
n.items.truncate(i)
|
||||
if len(n.children) > 0 {
|
||||
next.children = append(next.children, n.children[i+1:]...)
|
||||
n.children.truncate(i + 1)
|
||||
}
|
||||
return item, next
|
||||
}
|
||||
|
||||
// maybeSplitChild checks if a child should be split, and if so splits it.
|
||||
// Returns whether or not a split occurred.
|
||||
func (n *node) maybeSplitChild(i, maxItems int) bool {
|
||||
if len(n.children[i].items) < maxItems {
|
||||
return false
|
||||
}
|
||||
first := n.mutableChild(i)
|
||||
item, second := first.split(maxItems / 2)
|
||||
n.items.insertAt(i, item)
|
||||
n.children.insertAt(i+1, second)
|
||||
return true
|
||||
}
|
||||
|
||||
// insert inserts an item into the subtree rooted at this node, making sure
|
||||
// no nodes in the subtree exceed maxItems items. Should an equivalent item be
|
||||
// be found/replaced by insert, it will be returned.
|
||||
func (n *node) insert(item Item, maxItems int) Item {
|
||||
i, found := n.items.find(item)
|
||||
if found {
|
||||
out := n.items[i]
|
||||
n.items[i] = item
|
||||
return out
|
||||
}
|
||||
if len(n.children) == 0 {
|
||||
n.items.insertAt(i, item)
|
||||
return nil
|
||||
}
|
||||
if n.maybeSplitChild(i, maxItems) {
|
||||
inTree := n.items[i]
|
||||
switch {
|
||||
case item.Less(inTree):
|
||||
// no change, we want first split node
|
||||
case inTree.Less(item):
|
||||
i++ // we want second split node
|
||||
default:
|
||||
out := n.items[i]
|
||||
n.items[i] = item
|
||||
return out
|
||||
}
|
||||
}
|
||||
return n.mutableChild(i).insert(item, maxItems)
|
||||
}
|
||||
|
||||
// get finds the given key in the subtree and returns it.
|
||||
func (n *node) get(key Item) Item {
|
||||
i, found := n.items.find(key)
|
||||
if found {
|
||||
return n.items[i]
|
||||
} else if len(n.children) > 0 {
|
||||
return n.children[i].get(key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// min returns the first item in the subtree.
|
||||
func min(n *node) Item {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
for len(n.children) > 0 {
|
||||
n = n.children[0]
|
||||
}
|
||||
if len(n.items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return n.items[0]
|
||||
}
|
||||
|
||||
// max returns the last item in the subtree.
|
||||
func max(n *node) Item {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
for len(n.children) > 0 {
|
||||
n = n.children[len(n.children)-1]
|
||||
}
|
||||
if len(n.items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return n.items[len(n.items)-1]
|
||||
}
|
||||
|
||||
// toRemove details what item to remove in a node.remove call.
|
||||
type toRemove int
|
||||
|
||||
const (
|
||||
removeItem toRemove = iota // removes the given item
|
||||
removeMin // removes smallest item in the subtree
|
||||
removeMax // removes largest item in the subtree
|
||||
)
|
||||
|
||||
// remove removes an item from the subtree rooted at this node.
|
||||
func (n *node) remove(item Item, minItems int, typ toRemove) Item {
|
||||
var i int
|
||||
var found bool
|
||||
switch typ {
|
||||
case removeMax:
|
||||
if len(n.children) == 0 {
|
||||
return n.items.pop()
|
||||
}
|
||||
i = len(n.items)
|
||||
case removeMin:
|
||||
if len(n.children) == 0 {
|
||||
return n.items.removeAt(0)
|
||||
}
|
||||
i = 0
|
||||
case removeItem:
|
||||
i, found = n.items.find(item)
|
||||
if len(n.children) == 0 {
|
||||
if found {
|
||||
return n.items.removeAt(i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
panic("invalid type")
|
||||
}
|
||||
// If we get to here, we have children.
|
||||
if len(n.children[i].items) <= minItems {
|
||||
return n.growChildAndRemove(i, item, minItems, typ)
|
||||
}
|
||||
child := n.mutableChild(i)
|
||||
// Either we had enough items to begin with, or we've done some
|
||||
// merging/stealing, because we've got enough now and we're ready to return
|
||||
// stuff.
|
||||
if found {
|
||||
// The item exists at index 'i', and the child we've selected can give us a
|
||||
// predecessor, since if we've gotten here it's got > minItems items in it.
|
||||
out := n.items[i]
|
||||
// We use our special-case 'remove' call with typ=maxItem to pull the
|
||||
// predecessor of item i (the rightmost leaf of our immediate left child)
|
||||
// and set it into where we pulled the item from.
|
||||
n.items[i] = child.remove(nil, minItems, removeMax)
|
||||
return out
|
||||
}
|
||||
// Final recursive call. Once we're here, we know that the item isn't in this
|
||||
// node and that the child is big enough to remove from.
|
||||
return child.remove(item, minItems, typ)
|
||||
}
|
||||
|
||||
// growChildAndRemove grows child 'i' to make sure it's possible to remove an
|
||||
// item from it while keeping it at minItems, then calls remove to actually
|
||||
// remove it.
|
||||
//
|
||||
// Most documentation says we have to do two sets of special casing:
|
||||
// 1) item is in this node
|
||||
// 2) item is in child
|
||||
// In both cases, we need to handle the two subcases:
|
||||
// A) node has enough values that it can spare one
|
||||
// B) node doesn't have enough values
|
||||
// For the latter, we have to check:
|
||||
// a) left sibling has node to spare
|
||||
// b) right sibling has node to spare
|
||||
// c) we must merge
|
||||
// To simplify our code here, we handle cases #1 and #2 the same:
|
||||
// If a node doesn't have enough items, we make sure it does (using a,b,c).
|
||||
// We then simply redo our remove call, and the second time (regardless of
|
||||
// whether we're in case 1 or 2), we'll have enough items and can guarantee
|
||||
// that we hit case A.
|
||||
func (n *node) growChildAndRemove(i int, item Item, minItems int, typ toRemove) Item {
|
||||
if i > 0 && len(n.children[i-1].items) > minItems {
|
||||
// Steal from left child
|
||||
child := n.mutableChild(i)
|
||||
stealFrom := n.mutableChild(i - 1)
|
||||
stolenItem := stealFrom.items.pop()
|
||||
child.items.insertAt(0, n.items[i-1])
|
||||
n.items[i-1] = stolenItem
|
||||
if len(stealFrom.children) > 0 {
|
||||
child.children.insertAt(0, stealFrom.children.pop())
|
||||
}
|
||||
} else if i < len(n.items) && len(n.children[i+1].items) > minItems {
|
||||
// steal from right child
|
||||
child := n.mutableChild(i)
|
||||
stealFrom := n.mutableChild(i + 1)
|
||||
stolenItem := stealFrom.items.removeAt(0)
|
||||
child.items = append(child.items, n.items[i])
|
||||
n.items[i] = stolenItem
|
||||
if len(stealFrom.children) > 0 {
|
||||
child.children = append(child.children, stealFrom.children.removeAt(0))
|
||||
}
|
||||
} else {
|
||||
if i >= len(n.items) {
|
||||
i--
|
||||
}
|
||||
child := n.mutableChild(i)
|
||||
// merge with right child
|
||||
mergeItem := n.items.removeAt(i)
|
||||
mergeChild := n.children.removeAt(i + 1)
|
||||
child.items = append(child.items, mergeItem)
|
||||
child.items = append(child.items, mergeChild.items...)
|
||||
child.children = append(child.children, mergeChild.children...)
|
||||
n.cow.freeNode(mergeChild)
|
||||
}
|
||||
return n.remove(item, minItems, typ)
|
||||
}
|
||||
|
||||
type direction int
|
||||
|
||||
const (
|
||||
descend = direction(-1)
|
||||
ascend = direction(+1)
|
||||
)
|
||||
|
||||
// iterate provides a simple method for iterating over elements in the tree.
|
||||
//
|
||||
// When ascending, the 'start' should be less than 'stop' and when descending,
|
||||
// the 'start' should be greater than 'stop'. Setting 'includeStart' to true
|
||||
// will force the iterator to include the first item when it equals 'start',
|
||||
// thus creating a "greaterOrEqual" or "lessThanEqual" rather than just a
|
||||
// "greaterThan" or "lessThan" queries.
|
||||
func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit bool, iter ItemIterator) (bool, bool) {
|
||||
var ok, found bool
|
||||
var index int
|
||||
switch dir {
|
||||
case ascend:
|
||||
if start != nil {
|
||||
index, _ = n.items.find(start)
|
||||
}
|
||||
for i := index; i < len(n.items); i++ {
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[i].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if !includeStart && !hit && start != nil && !start.Less(n.items[i]) {
|
||||
hit = true
|
||||
continue
|
||||
}
|
||||
hit = true
|
||||
if stop != nil && !n.items[i].Less(stop) {
|
||||
return hit, false
|
||||
}
|
||||
if !iter(n.items[i]) {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[len(n.children)-1].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
case descend:
|
||||
if start != nil {
|
||||
index, found = n.items.find(start)
|
||||
if !found {
|
||||
index = index - 1
|
||||
}
|
||||
} else {
|
||||
index = len(n.items) - 1
|
||||
}
|
||||
for i := index; i >= 0; i-- {
|
||||
if start != nil && !n.items[i].Less(start) {
|
||||
if !includeStart || hit || start.Less(n.items[i]) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[i+1].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if stop != nil && !stop.Less(n.items[i]) {
|
||||
return hit, false // continue
|
||||
}
|
||||
hit = true
|
||||
if !iter(n.items[i]) {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
if len(n.children) > 0 {
|
||||
if hit, ok = n.children[0].iterate(dir, start, stop, includeStart, hit, iter); !ok {
|
||||
return hit, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return hit, true
|
||||
}
|
||||
|
||||
// Used for testing/debugging purposes.
|
||||
func (n *node) print(w io.Writer, level int) {
|
||||
fmt.Fprintf(w, "%sNODE:%v\n", strings.Repeat(" ", level), n.items)
|
||||
for _, c := range n.children {
|
||||
c.print(w, level+1)
|
||||
}
|
||||
}
|
||||
|
||||
// BTree is an implementation of a B-Tree.
|
||||
//
|
||||
// BTree stores Item instances in an ordered structure, allowing easy insertion,
|
||||
// removal, and iteration.
|
||||
//
|
||||
// Write operations are not safe for concurrent mutation by multiple
|
||||
// goroutines, but Read operations are.
|
||||
type BTree struct {
|
||||
degree int
|
||||
length int
|
||||
root *node
|
||||
cow *copyOnWriteContext
|
||||
}
|
||||
|
||||
// copyOnWriteContext pointers determine node ownership... a tree with a write
|
||||
// context equivalent to a node's write context is allowed to modify that node.
|
||||
// A tree whose write context does not match a node's is not allowed to modify
|
||||
// it, and must create a new, writable copy (IE: it's a Clone).
|
||||
//
|
||||
// When doing any write operation, we maintain the invariant that the current
|
||||
// node's context is equal to the context of the tree that requested the write.
|
||||
// We do this by, before we descend into any node, creating a copy with the
|
||||
// correct context if the contexts don't match.
|
||||
//
|
||||
// Since the node we're currently visiting on any write has the requesting
|
||||
// tree's context, that node is modifiable in place. Children of that node may
|
||||
// not share context, but before we descend into them, we'll make a mutable
|
||||
// copy.
|
||||
type copyOnWriteContext struct {
|
||||
freelist *FreeList
|
||||
}
|
||||
|
||||
// Clone clones the btree, lazily. Clone should not be called concurrently,
|
||||
// but the original tree (t) and the new tree (t2) can be used concurrently
|
||||
// once the Clone call completes.
|
||||
//
|
||||
// The internal tree structure of b is marked read-only and shared between t and
|
||||
// t2. Writes to both t and t2 use copy-on-write logic, creating new nodes
|
||||
// whenever one of b's original nodes would have been modified. Read operations
|
||||
// should have no performance degredation. Write operations for both t and t2
|
||||
// will initially experience minor slow-downs caused by additional allocs and
|
||||
// copies due to the aforementioned copy-on-write logic, but should converge to
|
||||
// the original performance characteristics of the original tree.
|
||||
func (t *BTree) Clone() (t2 *BTree) {
|
||||
// Create two entirely new copy-on-write contexts.
|
||||
// This operation effectively creates three trees:
|
||||
// the original, shared nodes (old b.cow)
|
||||
// the new b.cow nodes
|
||||
// the new out.cow nodes
|
||||
cow1, cow2 := *t.cow, *t.cow
|
||||
out := *t
|
||||
t.cow = &cow1
|
||||
out.cow = &cow2
|
||||
return &out
|
||||
}
|
||||
|
||||
// maxItems returns the max number of items to allow per node.
|
||||
func (t *BTree) maxItems() int {
|
||||
return t.degree*2 - 1
|
||||
}
|
||||
|
||||
// minItems returns the min number of items to allow per node (ignored for the
|
||||
// root node).
|
||||
func (t *BTree) minItems() int {
|
||||
return t.degree - 1
|
||||
}
|
||||
|
||||
func (c *copyOnWriteContext) newNode() (n *node) {
|
||||
n = c.freelist.newNode()
|
||||
n.cow = c
|
||||
return
|
||||
}
|
||||
|
||||
type freeType int
|
||||
|
||||
const (
|
||||
ftFreelistFull freeType = iota // node was freed (available for GC, not stored in freelist)
|
||||
ftStored // node was stored in the freelist for later use
|
||||
ftNotOwned // node was ignored by COW, since it's owned by another one
|
||||
)
|
||||
|
||||
// freeNode frees a node within a given COW context, if it's owned by that
|
||||
// context. It returns what happened to the node (see freeType const
|
||||
// documentation).
|
||||
func (c *copyOnWriteContext) freeNode(n *node) freeType {
|
||||
if n.cow == c {
|
||||
// clear to allow GC
|
||||
n.items.truncate(0)
|
||||
n.children.truncate(0)
|
||||
n.cow = nil
|
||||
if c.freelist.freeNode(n) {
|
||||
return ftStored
|
||||
} else {
|
||||
return ftFreelistFull
|
||||
}
|
||||
} else {
|
||||
return ftNotOwned
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceOrInsert adds the given item to the tree. If an item in the tree
|
||||
// already equals the given one, it is removed from the tree and returned.
|
||||
// Otherwise, nil is returned.
|
||||
//
|
||||
// nil cannot be added to the tree (will panic).
|
||||
func (t *BTree) ReplaceOrInsert(item Item) Item {
|
||||
if item == nil {
|
||||
panic("nil item being added to BTree")
|
||||
}
|
||||
if t.root == nil {
|
||||
t.root = t.cow.newNode()
|
||||
t.root.items = append(t.root.items, item)
|
||||
t.length++
|
||||
return nil
|
||||
} else {
|
||||
t.root = t.root.mutableFor(t.cow)
|
||||
if len(t.root.items) >= t.maxItems() {
|
||||
item2, second := t.root.split(t.maxItems() / 2)
|
||||
oldroot := t.root
|
||||
t.root = t.cow.newNode()
|
||||
t.root.items = append(t.root.items, item2)
|
||||
t.root.children = append(t.root.children, oldroot, second)
|
||||
}
|
||||
}
|
||||
out := t.root.insert(item, t.maxItems())
|
||||
if out == nil {
|
||||
t.length++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Delete removes an item equal to the passed in item from the tree, returning
|
||||
// it. If no such item exists, returns nil.
|
||||
func (t *BTree) Delete(item Item) Item {
|
||||
return t.deleteItem(item, removeItem)
|
||||
}
|
||||
|
||||
// DeleteMin removes the smallest item in the tree and returns it.
|
||||
// If no such item exists, returns nil.
|
||||
func (t *BTree) DeleteMin() Item {
|
||||
return t.deleteItem(nil, removeMin)
|
||||
}
|
||||
|
||||
// DeleteMax removes the largest item in the tree and returns it.
|
||||
// If no such item exists, returns nil.
|
||||
func (t *BTree) DeleteMax() Item {
|
||||
return t.deleteItem(nil, removeMax)
|
||||
}
|
||||
|
||||
func (t *BTree) deleteItem(item Item, typ toRemove) Item {
|
||||
if t.root == nil || len(t.root.items) == 0 {
|
||||
return nil
|
||||
}
|
||||
t.root = t.root.mutableFor(t.cow)
|
||||
out := t.root.remove(item, t.minItems(), typ)
|
||||
if len(t.root.items) == 0 && len(t.root.children) > 0 {
|
||||
oldroot := t.root
|
||||
t.root = t.root.children[0]
|
||||
t.cow.freeNode(oldroot)
|
||||
}
|
||||
if out != nil {
|
||||
t.length--
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AscendRange calls the iterator for every value in the tree within the range
|
||||
// [greaterOrEqual, lessThan), until iterator returns false.
|
||||
func (t *BTree) AscendRange(greaterOrEqual, lessThan Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, greaterOrEqual, lessThan, true, false, iterator)
|
||||
}
|
||||
|
||||
// AscendLessThan calls the iterator for every value in the tree within the range
|
||||
// [first, pivot), until iterator returns false.
|
||||
func (t *BTree) AscendLessThan(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, nil, pivot, false, false, iterator)
|
||||
}
|
||||
|
||||
// AscendGreaterOrEqual calls the iterator for every value in the tree within
|
||||
// the range [pivot, last], until iterator returns false.
|
||||
func (t *BTree) AscendGreaterOrEqual(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, pivot, nil, true, false, iterator)
|
||||
}
|
||||
|
||||
// Ascend calls the iterator for every value in the tree within the range
|
||||
// [first, last], until iterator returns false.
|
||||
func (t *BTree) Ascend(iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(ascend, nil, nil, false, false, iterator)
|
||||
}
|
||||
|
||||
// DescendRange calls the iterator for every value in the tree within the range
|
||||
// [lessOrEqual, greaterThan), until iterator returns false.
|
||||
func (t *BTree) DescendRange(lessOrEqual, greaterThan Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, lessOrEqual, greaterThan, true, false, iterator)
|
||||
}
|
||||
|
||||
// DescendLessOrEqual calls the iterator for every value in the tree within the range
|
||||
// [pivot, first], until iterator returns false.
|
||||
func (t *BTree) DescendLessOrEqual(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, pivot, nil, true, false, iterator)
|
||||
}
|
||||
|
||||
// DescendGreaterThan calls the iterator for every value in the tree within
|
||||
// the range [last, pivot), until iterator returns false.
|
||||
func (t *BTree) DescendGreaterThan(pivot Item, iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, nil, pivot, false, false, iterator)
|
||||
}
|
||||
|
||||
// Descend calls the iterator for every value in the tree within the range
|
||||
// [last, first], until iterator returns false.
|
||||
func (t *BTree) Descend(iterator ItemIterator) {
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
t.root.iterate(descend, nil, nil, false, false, iterator)
|
||||
}
|
||||
|
||||
// Get looks for the key item in the tree, returning it. It returns nil if
|
||||
// unable to find that item.
|
||||
func (t *BTree) Get(key Item) Item {
|
||||
if t.root == nil {
|
||||
return nil
|
||||
}
|
||||
return t.root.get(key)
|
||||
}
|
||||
|
||||
// Min returns the smallest item in the tree, or nil if the tree is empty.
|
||||
func (t *BTree) Min() Item {
|
||||
return min(t.root)
|
||||
}
|
||||
|
||||
// Max returns the largest item in the tree, or nil if the tree is empty.
|
||||
func (t *BTree) Max() Item {
|
||||
return max(t.root)
|
||||
}
|
||||
|
||||
// Has returns true if the given key is in the tree.
|
||||
func (t *BTree) Has(key Item) bool {
|
||||
return t.Get(key) != nil
|
||||
}
|
||||
|
||||
// Len returns the number of items currently in the tree.
|
||||
func (t *BTree) Len() int {
|
||||
return t.length
|
||||
}
|
||||
|
||||
// Clear removes all items from the btree. If addNodesToFreelist is true,
|
||||
// t's nodes are added to its freelist as part of this call, until the freelist
|
||||
// is full. Otherwise, the root node is simply dereferenced and the subtree
|
||||
// left to Go's normal GC processes.
|
||||
//
|
||||
// This can be much faster
|
||||
// than calling Delete on all elements, because that requires finding/removing
|
||||
// each element in the tree and updating the tree accordingly. It also is
|
||||
// somewhat faster than creating a new tree to replace the old one, because
|
||||
// nodes from the old tree are reclaimed into the freelist for use by the new
|
||||
// one, instead of being lost to the garbage collector.
|
||||
//
|
||||
// This call takes:
|
||||
// O(1): when addNodesToFreelist is false, this is a single operation.
|
||||
// O(1): when the freelist is already full, it breaks out immediately
|
||||
// O(freelist size): when the freelist is empty and the nodes are all owned
|
||||
// by this tree, nodes are added to the freelist until full.
|
||||
// O(tree size): when all nodes are owned by another tree, all nodes are
|
||||
// iterated over looking for nodes to add to the freelist, and due to
|
||||
// ownership, none are.
|
||||
func (t *BTree) Clear(addNodesToFreelist bool) {
|
||||
if t.root != nil && addNodesToFreelist {
|
||||
t.root.reset(t.cow)
|
||||
}
|
||||
t.root, t.length = nil, 0
|
||||
}
|
||||
|
||||
// reset returns a subtree to the freelist. It breaks out immediately if the
|
||||
// freelist is full, since the only benefit of iterating is to fill that
|
||||
// freelist up. Returns true if parent reset call should continue.
|
||||
func (n *node) reset(c *copyOnWriteContext) bool {
|
||||
for _, child := range n.children {
|
||||
if !child.reset(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return c.freeNode(n) != ftFreelistFull
|
||||
}
|
||||
|
||||
// Int implements the Item interface for integers.
|
||||
type Int int
|
||||
|
||||
// Less returns true if int(a) < int(b).
|
||||
func (a Int) Less(b Item) bool {
|
||||
return a < b.(Int)
|
||||
}
|
||||
1083
vendor/github.com/google/btree/btree_generic.go
generated
vendored
Normal file
1083
vendor/github.com/google/btree/btree_generic.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2017 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.
|
||||
671
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
671
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
@@ -0,0 +1,671 @@
|
||||
// Copyright 2017, The 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 cmp determines equality of values.
|
||||
//
|
||||
// This package is intended to be a more powerful and safer alternative to
|
||||
// [reflect.DeepEqual] for comparing whether two values are semantically equal.
|
||||
// It is intended to only be used in tests, as performance is not a goal and
|
||||
// it may panic if it cannot compare the values. Its propensity towards
|
||||
// panicking means that its unsuitable for production environments where a
|
||||
// spurious panic may be fatal.
|
||||
//
|
||||
// The primary features of cmp are:
|
||||
//
|
||||
// - When the default behavior of equality does not suit the test's needs,
|
||||
// custom equality functions can override the equality operation.
|
||||
// For example, an equality function may report floats as equal so long as
|
||||
// they are within some tolerance of each other.
|
||||
//
|
||||
// - Types with an Equal method (e.g., [time.Time.Equal]) may use that method
|
||||
// to determine equality. This allows package authors to determine
|
||||
// the equality operation for the types that they define.
|
||||
//
|
||||
// - If no custom equality functions are used and no Equal method is defined,
|
||||
// equality is determined by recursively comparing the primitive kinds on
|
||||
// both values, much like [reflect.DeepEqual]. Unlike [reflect.DeepEqual],
|
||||
// unexported fields are not compared by default; they result in panics
|
||||
// unless suppressed by using an [Ignore] option
|
||||
// (see [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported])
|
||||
// or explicitly compared using the [Exporter] option.
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/diff"
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// TODO(≥go1.18): Use any instead of interface{}.
|
||||
|
||||
// Equal reports whether x and y are equal by recursively applying the
|
||||
// following rules in the given order to x and y and all of their sub-values:
|
||||
//
|
||||
// - Let S be the set of all [Ignore], [Transformer], and [Comparer] options that
|
||||
// remain after applying all path filters, value filters, and type filters.
|
||||
// If at least one [Ignore] exists in S, then the comparison is ignored.
|
||||
// If the number of [Transformer] and [Comparer] options in S is non-zero,
|
||||
// then Equal panics because it is ambiguous which option to use.
|
||||
// If S contains a single [Transformer], then use that to transform
|
||||
// the current values and recursively call Equal on the output values.
|
||||
// If S contains a single [Comparer], then use that to compare the current values.
|
||||
// Otherwise, evaluation proceeds to the next rule.
|
||||
//
|
||||
// - If the values have an Equal method of the form "(T) Equal(T) bool" or
|
||||
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
|
||||
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
|
||||
// evaluation proceeds to the next rule.
|
||||
//
|
||||
// - Lastly, try to compare x and y based on their basic kinds.
|
||||
// Simple kinds like booleans, integers, floats, complex numbers, strings,
|
||||
// and channels are compared using the equivalent of the == operator in Go.
|
||||
// Functions are only equal if they are both nil, otherwise they are unequal.
|
||||
//
|
||||
// Structs are equal if recursively calling Equal on all fields report equal.
|
||||
// If a struct contains unexported fields, Equal panics unless an [Ignore] option
|
||||
// (e.g., [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) ignores that field
|
||||
// or the [Exporter] option explicitly permits comparing the unexported field.
|
||||
//
|
||||
// Slices are equal if they are both nil or both non-nil, where recursively
|
||||
// calling Equal on all non-ignored slice or array elements report equal.
|
||||
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
|
||||
// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
|
||||
//
|
||||
// Maps are equal if they are both nil or both non-nil, where recursively
|
||||
// calling Equal on all non-ignored map entries report equal.
|
||||
// Map keys are equal according to the == operator.
|
||||
// To use custom comparisons for map keys, consider using
|
||||
// [github.com/google/go-cmp/cmp/cmpopts.SortMaps].
|
||||
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
|
||||
// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
|
||||
//
|
||||
// Pointers and interfaces are equal if they are both nil or both non-nil,
|
||||
// where they have the same underlying concrete type and recursively
|
||||
// calling Equal on the underlying values reports equal.
|
||||
//
|
||||
// Before recursing into a pointer, slice element, or map, the current path
|
||||
// is checked to detect whether the address has already been visited.
|
||||
// If there is a cycle, then the pointed at values are considered equal
|
||||
// only if both addresses were previously visited in the same path step.
|
||||
func Equal(x, y interface{}, opts ...Option) bool {
|
||||
s := newState(opts)
|
||||
s.compareAny(rootStep(x, y))
|
||||
return s.result.Equal()
|
||||
}
|
||||
|
||||
// Diff returns a human-readable report of the differences between two values:
|
||||
// y - x. It returns an empty string if and only if Equal returns true for the
|
||||
// same input values and options.
|
||||
//
|
||||
// The output is displayed as a literal in pseudo-Go syntax.
|
||||
// At the start of each line, a "-" prefix indicates an element removed from x,
|
||||
// a "+" prefix to indicates an element added from y, and the lack of a prefix
|
||||
// indicates an element common to both x and y. If possible, the output
|
||||
// uses fmt.Stringer.String or error.Error methods to produce more humanly
|
||||
// readable outputs. In such cases, the string is prefixed with either an
|
||||
// 's' or 'e' character, respectively, to indicate that the method was called.
|
||||
//
|
||||
// Do not depend on this output being stable. If you need the ability to
|
||||
// programmatically interpret the difference, consider using a custom Reporter.
|
||||
func Diff(x, y interface{}, opts ...Option) string {
|
||||
s := newState(opts)
|
||||
|
||||
// Optimization: If there are no other reporters, we can optimize for the
|
||||
// common case where the result is equal (and thus no reported difference).
|
||||
// This avoids the expensive construction of a difference tree.
|
||||
if len(s.reporters) == 0 {
|
||||
s.compareAny(rootStep(x, y))
|
||||
if s.result.Equal() {
|
||||
return ""
|
||||
}
|
||||
s.result = diff.Result{} // Reset results
|
||||
}
|
||||
|
||||
r := new(defaultReporter)
|
||||
s.reporters = append(s.reporters, reporter{r})
|
||||
s.compareAny(rootStep(x, y))
|
||||
d := r.String()
|
||||
if (d == "") != s.result.Equal() {
|
||||
panic("inconsistent difference and equality results")
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// rootStep constructs the first path step. If x and y have differing types,
|
||||
// then they are stored within an empty interface type.
|
||||
func rootStep(x, y interface{}) PathStep {
|
||||
vx := reflect.ValueOf(x)
|
||||
vy := reflect.ValueOf(y)
|
||||
|
||||
// If the inputs are different types, auto-wrap them in an empty interface
|
||||
// so that they have the same parent type.
|
||||
var t reflect.Type
|
||||
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
|
||||
t = anyType
|
||||
if vx.IsValid() {
|
||||
vvx := reflect.New(t).Elem()
|
||||
vvx.Set(vx)
|
||||
vx = vvx
|
||||
}
|
||||
if vy.IsValid() {
|
||||
vvy := reflect.New(t).Elem()
|
||||
vvy.Set(vy)
|
||||
vy = vvy
|
||||
}
|
||||
} else {
|
||||
t = vx.Type()
|
||||
}
|
||||
|
||||
return &pathStep{t, vx, vy}
|
||||
}
|
||||
|
||||
type state struct {
|
||||
// These fields represent the "comparison state".
|
||||
// Calling statelessCompare must not result in observable changes to these.
|
||||
result diff.Result // The current result of comparison
|
||||
curPath Path // The current path in the value tree
|
||||
curPtrs pointerPath // The current set of visited pointers
|
||||
reporters []reporter // Optional reporters
|
||||
|
||||
// recChecker checks for infinite cycles applying the same set of
|
||||
// transformers upon the output of itself.
|
||||
recChecker recChecker
|
||||
|
||||
// dynChecker triggers pseudo-random checks for option correctness.
|
||||
// It is safe for statelessCompare to mutate this value.
|
||||
dynChecker dynChecker
|
||||
|
||||
// These fields, once set by processOption, will not change.
|
||||
exporters []exporter // List of exporters for structs with unexported fields
|
||||
opts Options // List of all fundamental and filter options
|
||||
}
|
||||
|
||||
func newState(opts []Option) *state {
|
||||
// Always ensure a validator option exists to validate the inputs.
|
||||
s := &state{opts: Options{validator{}}}
|
||||
s.curPtrs.Init()
|
||||
s.processOption(Options(opts))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *state) processOption(opt Option) {
|
||||
switch opt := opt.(type) {
|
||||
case nil:
|
||||
case Options:
|
||||
for _, o := range opt {
|
||||
s.processOption(o)
|
||||
}
|
||||
case coreOption:
|
||||
type filtered interface {
|
||||
isFiltered() bool
|
||||
}
|
||||
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
|
||||
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
||||
}
|
||||
s.opts = append(s.opts, opt)
|
||||
case exporter:
|
||||
s.exporters = append(s.exporters, opt)
|
||||
case reporter:
|
||||
s.reporters = append(s.reporters, opt)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option %T", opt))
|
||||
}
|
||||
}
|
||||
|
||||
// statelessCompare compares two values and returns the result.
|
||||
// This function is stateless in that it does not alter the current result,
|
||||
// or output to any registered reporters.
|
||||
func (s *state) statelessCompare(step PathStep) diff.Result {
|
||||
// We do not save and restore curPath and curPtrs because all of the
|
||||
// compareX methods should properly push and pop from them.
|
||||
// It is an implementation bug if the contents of the paths differ from
|
||||
// when calling this function to when returning from it.
|
||||
|
||||
oldResult, oldReporters := s.result, s.reporters
|
||||
s.result = diff.Result{} // Reset result
|
||||
s.reporters = nil // Remove reporters to avoid spurious printouts
|
||||
s.compareAny(step)
|
||||
res := s.result
|
||||
s.result, s.reporters = oldResult, oldReporters
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *state) compareAny(step PathStep) {
|
||||
// Update the path stack.
|
||||
s.curPath.push(step)
|
||||
defer s.curPath.pop()
|
||||
for _, r := range s.reporters {
|
||||
r.PushStep(step)
|
||||
defer r.PopStep()
|
||||
}
|
||||
s.recChecker.Check(s.curPath)
|
||||
|
||||
// Cycle-detection for slice elements (see NOTE in compareSlice).
|
||||
t := step.Type()
|
||||
vx, vy := step.Values()
|
||||
if si, ok := step.(SliceIndex); ok && si.isSlice && vx.IsValid() && vy.IsValid() {
|
||||
px, py := vx.Addr(), vy.Addr()
|
||||
if eq, visited := s.curPtrs.Push(px, py); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(px, py)
|
||||
}
|
||||
|
||||
// Rule 1: Check whether an option applies on this node in the value tree.
|
||||
if s.tryOptions(t, vx, vy) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 2: Check whether the type has a valid Equal method.
|
||||
if s.tryMethod(t, vx, vy) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 3: Compare based on the underlying kind.
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
s.report(vx.Bool() == vy.Bool(), 0)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
s.report(vx.Int() == vy.Int(), 0)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
s.report(vx.Uint() == vy.Uint(), 0)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
s.report(vx.Float() == vy.Float(), 0)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
s.report(vx.Complex() == vy.Complex(), 0)
|
||||
case reflect.String:
|
||||
s.report(vx.String() == vy.String(), 0)
|
||||
case reflect.Chan, reflect.UnsafePointer:
|
||||
s.report(vx.Pointer() == vy.Pointer(), 0)
|
||||
case reflect.Func:
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
case reflect.Struct:
|
||||
s.compareStruct(t, vx, vy)
|
||||
case reflect.Slice, reflect.Array:
|
||||
s.compareSlice(t, vx, vy)
|
||||
case reflect.Map:
|
||||
s.compareMap(t, vx, vy)
|
||||
case reflect.Ptr:
|
||||
s.comparePtr(t, vx, vy)
|
||||
case reflect.Interface:
|
||||
s.compareInterface(t, vx, vy)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool {
|
||||
// Evaluate all filters and apply the remaining options.
|
||||
if opt := s.opts.filter(s, t, vx, vy); opt != nil {
|
||||
opt.apply(s, vx, vy)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
|
||||
// Check if this type even has an Equal method.
|
||||
m, ok := t.MethodByName("Equal")
|
||||
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
|
||||
return false
|
||||
}
|
||||
|
||||
eq := s.callTTBFunc(m.Func, vx, vy)
|
||||
s.report(eq, reportByMethod)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{v})[0]
|
||||
}
|
||||
|
||||
// Run the function twice and ensure that we get the same results back.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, v)
|
||||
got := <-c
|
||||
want := f.Call([]reflect.Value{v})[0]
|
||||
if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() {
|
||||
// To avoid false-positives with non-reflexive equality operations,
|
||||
// we sanity check whether a value is equal to itself.
|
||||
if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() {
|
||||
return want
|
||||
}
|
||||
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
}
|
||||
|
||||
// Swapping the input arguments is sufficient to check that
|
||||
// f is symmetric and deterministic.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, y, x)
|
||||
got := <-c
|
||||
want := f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
if !got.IsValid() || got.Bool() != want {
|
||||
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
|
||||
var ret reflect.Value
|
||||
defer func() {
|
||||
recover() // Ignore panics, let the other call to f panic instead
|
||||
c <- ret
|
||||
}()
|
||||
ret = f.Call(vs)[0]
|
||||
}
|
||||
|
||||
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
||||
var addr bool
|
||||
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||
|
||||
var mayForce, mayForceInit bool
|
||||
step := StructField{&structField{}}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
step.typ = t.Field(i).Type
|
||||
step.vx = vx.Field(i)
|
||||
step.vy = vy.Field(i)
|
||||
step.name = t.Field(i).Name
|
||||
step.idx = i
|
||||
step.unexported = !isExported(step.name)
|
||||
if step.unexported {
|
||||
if step.name == "_" {
|
||||
continue
|
||||
}
|
||||
// Defer checking of unexported fields until later to give an
|
||||
// Ignore a chance to ignore the field.
|
||||
if !vax.IsValid() || !vay.IsValid() {
|
||||
// For retrieveUnexportedField to work, the parent struct must
|
||||
// be addressable. Create a new copy of the values if
|
||||
// necessary to make them addressable.
|
||||
addr = vx.CanAddr() || vy.CanAddr()
|
||||
vax = makeAddressable(vx)
|
||||
vay = makeAddressable(vy)
|
||||
}
|
||||
if !mayForceInit {
|
||||
for _, xf := range s.exporters {
|
||||
mayForce = mayForce || xf(t)
|
||||
}
|
||||
mayForceInit = true
|
||||
}
|
||||
step.mayForce = mayForce
|
||||
step.paddr = addr
|
||||
step.pvx = vax
|
||||
step.pvy = vay
|
||||
step.field = t.Field(i)
|
||||
}
|
||||
s.compareAny(step)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
|
||||
isSlice := t.Kind() == reflect.Slice
|
||||
if isSlice && (vx.IsNil() || vy.IsNil()) {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: It is incorrect to call curPtrs.Push on the slice header pointer
|
||||
// since slices represents a list of pointers, rather than a single pointer.
|
||||
// The pointer checking logic must be handled on a per-element basis
|
||||
// in compareAny.
|
||||
//
|
||||
// A slice header (see reflect.SliceHeader) in Go is a tuple of a starting
|
||||
// pointer P, a length N, and a capacity C. Supposing each slice element has
|
||||
// a memory size of M, then the slice is equivalent to the list of pointers:
|
||||
// [P+i*M for i in range(N)]
|
||||
//
|
||||
// For example, v[:0] and v[:1] are slices with the same starting pointer,
|
||||
// but they are clearly different values. Using the slice pointer alone
|
||||
// violates the assumption that equal pointers implies equal values.
|
||||
|
||||
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}, isSlice: isSlice}}
|
||||
withIndexes := func(ix, iy int) SliceIndex {
|
||||
if ix >= 0 {
|
||||
step.vx, step.xkey = vx.Index(ix), ix
|
||||
} else {
|
||||
step.vx, step.xkey = reflect.Value{}, -1
|
||||
}
|
||||
if iy >= 0 {
|
||||
step.vy, step.ykey = vy.Index(iy), iy
|
||||
} else {
|
||||
step.vy, step.ykey = reflect.Value{}, -1
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
// Ignore options are able to ignore missing elements in a slice.
|
||||
// However, detecting these reliably requires an optimal differencing
|
||||
// algorithm, for which diff.Difference is not.
|
||||
//
|
||||
// Instead, we first iterate through both slices to detect which elements
|
||||
// would be ignored if standing alone. The index of non-discarded elements
|
||||
// are stored in a separate slice, which diffing is then performed on.
|
||||
var indexesX, indexesY []int
|
||||
var ignoredX, ignoredY []bool
|
||||
for ix := 0; ix < vx.Len(); ix++ {
|
||||
ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0
|
||||
if !ignored {
|
||||
indexesX = append(indexesX, ix)
|
||||
}
|
||||
ignoredX = append(ignoredX, ignored)
|
||||
}
|
||||
for iy := 0; iy < vy.Len(); iy++ {
|
||||
ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0
|
||||
if !ignored {
|
||||
indexesY = append(indexesY, iy)
|
||||
}
|
||||
ignoredY = append(ignoredY, ignored)
|
||||
}
|
||||
|
||||
// Compute an edit-script for slices vx and vy (excluding ignored elements).
|
||||
edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result {
|
||||
return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy]))
|
||||
})
|
||||
|
||||
// Replay the ignore-scripts and the edit-script.
|
||||
var ix, iy int
|
||||
for ix < vx.Len() || iy < vy.Len() {
|
||||
var e diff.EditType
|
||||
switch {
|
||||
case ix < len(ignoredX) && ignoredX[ix]:
|
||||
e = diff.UniqueX
|
||||
case iy < len(ignoredY) && ignoredY[iy]:
|
||||
e = diff.UniqueY
|
||||
default:
|
||||
e, edits = edits[0], edits[1:]
|
||||
}
|
||||
switch e {
|
||||
case diff.UniqueX:
|
||||
s.compareAny(withIndexes(ix, -1))
|
||||
ix++
|
||||
case diff.UniqueY:
|
||||
s.compareAny(withIndexes(-1, iy))
|
||||
iy++
|
||||
default:
|
||||
s.compareAny(withIndexes(ix, iy))
|
||||
ix++
|
||||
iy++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Cycle-detection for maps.
|
||||
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(vx, vy)
|
||||
|
||||
// We combine and sort the two map keys so that we can perform the
|
||||
// comparisons in a deterministic order.
|
||||
step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}}
|
||||
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
|
||||
step.vx = vx.MapIndex(k)
|
||||
step.vy = vy.MapIndex(k)
|
||||
step.key = k
|
||||
if !step.vx.IsValid() && !step.vy.IsValid() {
|
||||
// It is possible for both vx and vy to be invalid if the
|
||||
// key contained a NaN value in it.
|
||||
//
|
||||
// Even with the ability to retrieve NaN keys in Go 1.12,
|
||||
// there still isn't a sensible way to compare the values since
|
||||
// a NaN key may map to multiple unordered values.
|
||||
// The most reasonable way to compare NaNs would be to compare the
|
||||
// set of values. However, this is impossible to do efficiently
|
||||
// since set equality is provably an O(n^2) operation given only
|
||||
// an Equal function. If we had a Less function or Hash function,
|
||||
// this could be done in O(n*log(n)) or O(n), respectively.
|
||||
//
|
||||
// Rather than adding complex logic to deal with NaNs, make it
|
||||
// the user's responsibility to compare such obscure maps.
|
||||
const help = "consider providing a Comparer to compare the map"
|
||||
panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help))
|
||||
}
|
||||
s.compareAny(step)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Cycle-detection for pointers.
|
||||
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(vx, vy)
|
||||
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
|
||||
}
|
||||
|
||||
func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
if vx.Type() != vy.Type() {
|
||||
s.report(false, 0)
|
||||
return
|
||||
}
|
||||
s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}})
|
||||
}
|
||||
|
||||
func (s *state) report(eq bool, rf resultFlags) {
|
||||
if rf&reportByIgnore == 0 {
|
||||
if eq {
|
||||
s.result.NumSame++
|
||||
rf |= reportEqual
|
||||
} else {
|
||||
s.result.NumDiff++
|
||||
rf |= reportUnequal
|
||||
}
|
||||
}
|
||||
for _, r := range s.reporters {
|
||||
r.Report(Result{flags: rf})
|
||||
}
|
||||
}
|
||||
|
||||
// recChecker tracks the state needed to periodically perform checks that
|
||||
// user provided transformers are not stuck in an infinitely recursive cycle.
|
||||
type recChecker struct{ next int }
|
||||
|
||||
// Check scans the Path for any recursive transformers and panics when any
|
||||
// recursive transformers are detected. Note that the presence of a
|
||||
// recursive Transformer does not necessarily imply an infinite cycle.
|
||||
// As such, this check only activates after some minimal number of path steps.
|
||||
func (rc *recChecker) Check(p Path) {
|
||||
const minLen = 1 << 16
|
||||
if rc.next == 0 {
|
||||
rc.next = minLen
|
||||
}
|
||||
if len(p) < rc.next {
|
||||
return
|
||||
}
|
||||
rc.next <<= 1
|
||||
|
||||
// Check whether the same transformer has appeared at least twice.
|
||||
var ss []string
|
||||
m := map[Option]int{}
|
||||
for _, ps := range p {
|
||||
if t, ok := ps.(Transform); ok {
|
||||
t := t.Option()
|
||||
if m[t] == 1 { // Transformer was used exactly once before
|
||||
tf := t.(*transformer).fnc.Type()
|
||||
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
|
||||
}
|
||||
m[t]++
|
||||
}
|
||||
}
|
||||
if len(ss) > 0 {
|
||||
const warning = "recursive set of Transformers detected"
|
||||
const help = "consider using cmpopts.AcyclicTransformer"
|
||||
set := strings.Join(ss, "\n\t")
|
||||
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
|
||||
}
|
||||
}
|
||||
|
||||
// dynChecker tracks the state needed to periodically perform checks that
|
||||
// user provided functions are symmetric and deterministic.
|
||||
// The zero value is safe for immediate use.
|
||||
type dynChecker struct{ curr, next int }
|
||||
|
||||
// Next increments the state and reports whether a check should be performed.
|
||||
//
|
||||
// Checks occur every Nth function call, where N is a triangular number:
|
||||
//
|
||||
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Triangular_number
|
||||
//
|
||||
// This sequence ensures that the cost of checks drops significantly as
|
||||
// the number of functions calls grows larger.
|
||||
func (dc *dynChecker) Next() bool {
|
||||
ok := dc.curr == dc.next
|
||||
if ok {
|
||||
dc.curr = 0
|
||||
dc.next++
|
||||
}
|
||||
dc.curr++
|
||||
return ok
|
||||
}
|
||||
|
||||
// makeAddressable returns a value that is always addressable.
|
||||
// It returns the input verbatim if it is already addressable,
|
||||
// otherwise it creates a new value and returns an addressable copy.
|
||||
func makeAddressable(v reflect.Value) reflect.Value {
|
||||
if v.CanAddr() {
|
||||
return v
|
||||
}
|
||||
vc := reflect.New(v.Type()).Elem()
|
||||
vc.Set(v)
|
||||
return vc
|
||||
}
|
||||
31
vendor/github.com/google/go-cmp/cmp/export.go
generated
vendored
Normal file
31
vendor/github.com/google/go-cmp/cmp/export.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017, The 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 cmp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
|
||||
// a struct such that the value has read-write permissions.
|
||||
//
|
||||
// The parent struct, v, must be addressable, while f must be a StructField
|
||||
// describing the field to retrieve. If addr is false,
|
||||
// then the returned value will be shallowed copied to be non-addressable.
|
||||
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
|
||||
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||
if !addr {
|
||||
// A field is addressable if and only if the struct is addressable.
|
||||
// If the original parent value was not addressable, shallow copy the
|
||||
// value to make it non-addressable to avoid leaking an implementation
|
||||
// detail of how forcibly exporting a field works.
|
||||
if ve.Kind() == reflect.Interface && ve.IsNil() {
|
||||
return reflect.Zero(f.Type)
|
||||
}
|
||||
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
|
||||
}
|
||||
return ve
|
||||
}
|
||||
18
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
18
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017, The 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.
|
||||
|
||||
//go:build !cmp_debug
|
||||
// +build !cmp_debug
|
||||
|
||||
package diff
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct{}
|
||||
|
||||
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
|
||||
return f
|
||||
}
|
||||
func (debugger) Update() {}
|
||||
func (debugger) Finish() {}
|
||||
123
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
123
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2017, The 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.
|
||||
|
||||
//go:build cmp_debug
|
||||
// +build cmp_debug
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The algorithm can be seen running in real-time by enabling debugging:
|
||||
// go test -tags=cmp_debug -v
|
||||
//
|
||||
// Example output:
|
||||
// === RUN TestDifference/#34
|
||||
// ┌───────────────────────────────┐
|
||||
// │ \ · · · · · · · · · · · · · · │
|
||||
// │ · # · · · · · · · · · · · · · │
|
||||
// │ · \ · · · · · · · · · · · · · │
|
||||
// │ · · \ · · · · · · · · · · · · │
|
||||
// │ · · · X # · · · · · · · · · · │
|
||||
// │ · · · # \ · · · · · · · · · · │
|
||||
// │ · · · · · # # · · · · · · · · │
|
||||
// │ · · · · · # \ · · · · · · · · │
|
||||
// │ · · · · · · · \ · · · · · · · │
|
||||
// │ · · · · · · · · \ · · · · · · │
|
||||
// │ · · · · · · · · · \ · · · · · │
|
||||
// │ · · · · · · · · · · \ · · # · │
|
||||
// │ · · · · · · · · · · · \ # # · │
|
||||
// │ · · · · · · · · · · · # # # · │
|
||||
// │ · · · · · · · · · · # # # # · │
|
||||
// │ · · · · · · · · · # # # # # · │
|
||||
// │ · · · · · · · · · · · · · · \ │
|
||||
// └───────────────────────────────┘
|
||||
// [.Y..M.XY......YXYXY.|]
|
||||
//
|
||||
// The grid represents the edit-graph where the horizontal axis represents
|
||||
// list X and the vertical axis represents list Y. The start of the two lists
|
||||
// is the top-left, while the ends are the bottom-right. The '·' represents
|
||||
// an unexplored node in the graph. The '\' indicates that the two symbols
|
||||
// from list X and Y are equal. The 'X' indicates that two symbols are similar
|
||||
// (but not exactly equal) to each other. The '#' indicates that the two symbols
|
||||
// are different (and not similar). The algorithm traverses this graph trying to
|
||||
// make the paths starting in the top-left and the bottom-right connect.
|
||||
//
|
||||
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
|
||||
// the currently established path from the forward and reverse searches,
|
||||
// separated by a '|' character.
|
||||
|
||||
const (
|
||||
updateDelay = 100 * time.Millisecond
|
||||
finishDelay = 500 * time.Millisecond
|
||||
ansiTerminal = true // ANSI escape codes used to move terminal cursor
|
||||
)
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct {
|
||||
sync.Mutex
|
||||
p1, p2 EditScript
|
||||
fwdPath, revPath *EditScript
|
||||
grid []byte
|
||||
lines int
|
||||
}
|
||||
|
||||
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
|
||||
dbg.Lock()
|
||||
dbg.fwdPath, dbg.revPath = p1, p2
|
||||
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
|
||||
row := "│ " + strings.Repeat("· ", nx) + "│\n"
|
||||
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
|
||||
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
|
||||
dbg.lines = strings.Count(dbg.String(), "\n")
|
||||
fmt.Print(dbg)
|
||||
|
||||
// Wrap the EqualFunc so that we can intercept each result.
|
||||
return func(ix, iy int) (r Result) {
|
||||
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
|
||||
for i := range cell {
|
||||
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
|
||||
}
|
||||
switch r = f(ix, iy); {
|
||||
case r.Equal():
|
||||
cell[0] = '\\'
|
||||
case r.Similar():
|
||||
cell[0] = 'X'
|
||||
default:
|
||||
cell[0] = '#'
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (dbg *debugger) Update() {
|
||||
dbg.print(updateDelay)
|
||||
}
|
||||
|
||||
func (dbg *debugger) Finish() {
|
||||
dbg.print(finishDelay)
|
||||
dbg.Unlock()
|
||||
}
|
||||
|
||||
func (dbg *debugger) String() string {
|
||||
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
|
||||
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
|
||||
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
|
||||
}
|
||||
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
|
||||
}
|
||||
|
||||
func (dbg *debugger) print(d time.Duration) {
|
||||
if ansiTerminal {
|
||||
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
|
||||
}
|
||||
fmt.Print(dbg)
|
||||
time.Sleep(d)
|
||||
}
|
||||
402
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
402
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
@@ -0,0 +1,402 @@
|
||||
// Copyright 2017, The 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 diff implements an algorithm for producing edit-scripts.
|
||||
// The edit-script is a sequence of operations needed to transform one list
|
||||
// of symbols into another (or vice-versa). The edits allowed are insertions,
|
||||
// deletions, and modifications. The summation of all edits is called the
|
||||
// Levenshtein distance as this problem is well-known in computer science.
|
||||
//
|
||||
// This package prioritizes performance over accuracy. That is, the run time
|
||||
// is more important than obtaining a minimal Levenshtein distance.
|
||||
package diff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
)
|
||||
|
||||
// EditType represents a single operation within an edit-script.
|
||||
type EditType uint8
|
||||
|
||||
const (
|
||||
// Identity indicates that a symbol pair is identical in both list X and Y.
|
||||
Identity EditType = iota
|
||||
// UniqueX indicates that a symbol only exists in X and not Y.
|
||||
UniqueX
|
||||
// UniqueY indicates that a symbol only exists in Y and not X.
|
||||
UniqueY
|
||||
// Modified indicates that a symbol pair is a modification of each other.
|
||||
Modified
|
||||
)
|
||||
|
||||
// EditScript represents the series of differences between two lists.
|
||||
type EditScript []EditType
|
||||
|
||||
// String returns a human-readable string representing the edit-script where
|
||||
// Identity, UniqueX, UniqueY, and Modified are represented by the
|
||||
// '.', 'X', 'Y', and 'M' characters, respectively.
|
||||
func (es EditScript) String() string {
|
||||
b := make([]byte, len(es))
|
||||
for i, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
b[i] = '.'
|
||||
case UniqueX:
|
||||
b[i] = 'X'
|
||||
case UniqueY:
|
||||
b[i] = 'Y'
|
||||
case Modified:
|
||||
b[i] = 'M'
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// stats returns a histogram of the number of each type of edit operation.
|
||||
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
s.NI++
|
||||
case UniqueX:
|
||||
s.NX++
|
||||
case UniqueY:
|
||||
s.NY++
|
||||
case Modified:
|
||||
s.NM++
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
|
||||
// lists X and Y are equal.
|
||||
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
|
||||
|
||||
// LenX is the length of the X list.
|
||||
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
|
||||
|
||||
// LenY is the length of the Y list.
|
||||
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
|
||||
|
||||
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
|
||||
// When called by Difference, the index is guaranteed to be within nx and ny.
|
||||
type EqualFunc func(ix int, iy int) Result
|
||||
|
||||
// Result is the result of comparison.
|
||||
// NumSame is the number of sub-elements that are equal.
|
||||
// NumDiff is the number of sub-elements that are not equal.
|
||||
type Result struct{ NumSame, NumDiff int }
|
||||
|
||||
// BoolResult returns a Result that is either Equal or not Equal.
|
||||
func BoolResult(b bool) Result {
|
||||
if b {
|
||||
return Result{NumSame: 1} // Equal, Similar
|
||||
} else {
|
||||
return Result{NumDiff: 2} // Not Equal, not Similar
|
||||
}
|
||||
}
|
||||
|
||||
// Equal indicates whether the symbols are equal. Two symbols are equal
|
||||
// if and only if NumDiff == 0. If Equal, then they are also Similar.
|
||||
func (r Result) Equal() bool { return r.NumDiff == 0 }
|
||||
|
||||
// Similar indicates whether two symbols are similar and may be represented
|
||||
// by using the Modified type. As a special case, we consider binary comparisons
|
||||
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
|
||||
//
|
||||
// The exact ratio of NumSame to NumDiff to determine similarity may change.
|
||||
func (r Result) Similar() bool {
|
||||
// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
|
||||
return r.NumSame+1 >= r.NumDiff
|
||||
}
|
||||
|
||||
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||
|
||||
// Difference reports whether two lists of lengths nx and ny are equal
|
||||
// given the definition of equality provided as f.
|
||||
//
|
||||
// This function returns an edit-script, which is a sequence of operations
|
||||
// needed to convert one list into the other. The following invariants for
|
||||
// the edit-script are maintained:
|
||||
// - eq == (es.Dist()==0)
|
||||
// - nx == es.LenX()
|
||||
// - ny == es.LenY()
|
||||
//
|
||||
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
|
||||
// produces an edit-script with a minimal Levenshtein distance). This algorithm
|
||||
// favors performance over optimality. The exact output is not guaranteed to
|
||||
// be stable and may change over time.
|
||||
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
||||
// This algorithm is based on traversing what is known as an "edit-graph".
|
||||
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
|
||||
// by Eugene W. Myers. Since D can be as large as N itself, this is
|
||||
// effectively O(N^2). Unlike the algorithm from that paper, we are not
|
||||
// interested in the optimal path, but at least some "decent" path.
|
||||
//
|
||||
// For example, let X and Y be lists of symbols:
|
||||
// X = [A B C A B B A]
|
||||
// Y = [C B A B A C]
|
||||
//
|
||||
// The edit-graph can be drawn as the following:
|
||||
// A B C A B B A
|
||||
// ┌─────────────┐
|
||||
// C │_|_|\|_|_|_|_│ 0
|
||||
// B │_|\|_|_|\|\|_│ 1
|
||||
// A │\|_|_|\|_|_|\│ 2
|
||||
// B │_|\|_|_|\|\|_│ 3
|
||||
// A │\|_|_|\|_|_|\│ 4
|
||||
// C │ | |\| | | | │ 5
|
||||
// └─────────────┘ 6
|
||||
// 0 1 2 3 4 5 6 7
|
||||
//
|
||||
// List X is written along the horizontal axis, while list Y is written
|
||||
// along the vertical axis. At any point on this grid, if the symbol in
|
||||
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
|
||||
// The goal of any minimal edit-script algorithm is to find a path from the
|
||||
// top-left corner to the bottom-right corner, while traveling through the
|
||||
// fewest horizontal or vertical edges.
|
||||
// A horizontal edge is equivalent to inserting a symbol from list X.
|
||||
// A vertical edge is equivalent to inserting a symbol from list Y.
|
||||
// A diagonal edge is equivalent to a matching symbol between both X and Y.
|
||||
|
||||
// Invariants:
|
||||
// - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
|
||||
// - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
|
||||
//
|
||||
// In general:
|
||||
// - fwdFrontier.X < revFrontier.X
|
||||
// - fwdFrontier.Y < revFrontier.Y
|
||||
//
|
||||
// Unless, it is time for the algorithm to terminate.
|
||||
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
|
||||
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
|
||||
fwdFrontier := fwdPath.point // Forward search frontier
|
||||
revFrontier := revPath.point // Reverse search frontier
|
||||
|
||||
// Search budget bounds the cost of searching for better paths.
|
||||
// The longest sequence of non-matching symbols that can be tolerated is
|
||||
// approximately the square-root of the search budget.
|
||||
searchBudget := 4 * (nx + ny) // O(n)
|
||||
|
||||
// Running the tests with the "cmp_debug" build tag prints a visualization
|
||||
// of the algorithm running in real-time. This is educational for
|
||||
// understanding how the algorithm works. See debug_enable.go.
|
||||
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
|
||||
|
||||
// The algorithm below is a greedy, meet-in-the-middle algorithm for
|
||||
// computing sub-optimal edit-scripts between two lists.
|
||||
//
|
||||
// The algorithm is approximately as follows:
|
||||
// - Searching for differences switches back-and-forth between
|
||||
// a search that starts at the beginning (the top-left corner), and
|
||||
// a search that starts at the end (the bottom-right corner).
|
||||
// The goal of the search is connect with the search
|
||||
// from the opposite corner.
|
||||
// - As we search, we build a path in a greedy manner,
|
||||
// where the first match seen is added to the path (this is sub-optimal,
|
||||
// but provides a decent result in practice). When matches are found,
|
||||
// we try the next pair of symbols in the lists and follow all matches
|
||||
// as far as possible.
|
||||
// - When searching for matches, we search along a diagonal going through
|
||||
// through the "frontier" point. If no matches are found,
|
||||
// we advance the frontier towards the opposite corner.
|
||||
// - This algorithm terminates when either the X coordinates or the
|
||||
// Y coordinates of the forward and reverse frontier points ever intersect.
|
||||
|
||||
// This algorithm is correct even if searching only in the forward direction
|
||||
// or in the reverse direction. We do both because it is commonly observed
|
||||
// that two lists commonly differ because elements were added to the front
|
||||
// or end of the other list.
|
||||
//
|
||||
// Non-deterministically start with either the forward or reverse direction
|
||||
// to introduce some deliberate instability so that we have the flexibility
|
||||
// to change this algorithm in the future.
|
||||
if flags.Deterministic || randBool {
|
||||
goto forwardSearch
|
||||
} else {
|
||||
goto reverseSearch
|
||||
}
|
||||
|
||||
forwardSearch:
|
||||
{
|
||||
// Forward search from the beginning.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
goto finishSearch
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
|
||||
switch {
|
||||
case p.X >= revPath.X || p.Y < fwdPath.Y:
|
||||
stop1 = true // Hit top-right corner
|
||||
case p.Y >= revPath.Y || p.X < fwdPath.X:
|
||||
stop2 = true // Hit bottom-left corner
|
||||
case f(p.X, p.Y).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
fwdPath.connect(p, f)
|
||||
fwdPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(fwdPath.X, fwdPath.Y).Equal() {
|
||||
break
|
||||
}
|
||||
fwdPath.append(Identity)
|
||||
}
|
||||
fwdFrontier = fwdPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards reverse point.
|
||||
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
|
||||
fwdFrontier.X++
|
||||
} else {
|
||||
fwdFrontier.Y++
|
||||
}
|
||||
goto reverseSearch
|
||||
}
|
||||
|
||||
reverseSearch:
|
||||
{
|
||||
// Reverse search from the end.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
goto finishSearch
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{revFrontier.X - z, revFrontier.Y + z}
|
||||
switch {
|
||||
case fwdPath.X >= p.X || revPath.Y < p.Y:
|
||||
stop1 = true // Hit bottom-left corner
|
||||
case fwdPath.Y >= p.Y || revPath.X < p.X:
|
||||
stop2 = true // Hit top-right corner
|
||||
case f(p.X-1, p.Y-1).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
revPath.connect(p, f)
|
||||
revPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(revPath.X-1, revPath.Y-1).Equal() {
|
||||
break
|
||||
}
|
||||
revPath.append(Identity)
|
||||
}
|
||||
revFrontier = revPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards forward point.
|
||||
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
|
||||
revFrontier.X--
|
||||
} else {
|
||||
revFrontier.Y--
|
||||
}
|
||||
goto forwardSearch
|
||||
}
|
||||
|
||||
finishSearch:
|
||||
// Join the forward and reverse paths and then append the reverse path.
|
||||
fwdPath.connect(revPath.point, f)
|
||||
for i := len(revPath.es) - 1; i >= 0; i-- {
|
||||
t := revPath.es[i]
|
||||
revPath.es = revPath.es[:i]
|
||||
fwdPath.append(t)
|
||||
}
|
||||
debug.Finish()
|
||||
return fwdPath.es
|
||||
}
|
||||
|
||||
type path struct {
|
||||
dir int // +1 if forward, -1 if reverse
|
||||
point // Leading point of the EditScript path
|
||||
es EditScript
|
||||
}
|
||||
|
||||
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
|
||||
// to the edit-script to connect p.point to dst.
|
||||
func (p *path) connect(dst point, f EqualFunc) {
|
||||
if p.dir > 0 {
|
||||
// Connect in forward direction.
|
||||
for dst.X > p.X && dst.Y > p.Y {
|
||||
switch r := f(p.X, p.Y); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case dst.X-p.X >= dst.Y-p.Y:
|
||||
p.append(UniqueX)
|
||||
default:
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
for dst.X > p.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for dst.Y > p.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
} else {
|
||||
// Connect in reverse direction.
|
||||
for p.X > dst.X && p.Y > dst.Y {
|
||||
switch r := f(p.X-1, p.Y-1); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case p.Y-dst.Y >= p.X-dst.X:
|
||||
p.append(UniqueY)
|
||||
default:
|
||||
p.append(UniqueX)
|
||||
}
|
||||
}
|
||||
for p.X > dst.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for p.Y > dst.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *path) append(t EditType) {
|
||||
p.es = append(p.es, t)
|
||||
switch t {
|
||||
case Identity, Modified:
|
||||
p.add(p.dir, p.dir)
|
||||
case UniqueX:
|
||||
p.add(p.dir, 0)
|
||||
case UniqueY:
|
||||
p.add(0, p.dir)
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
|
||||
type point struct{ X, Y int }
|
||||
|
||||
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
|
||||
|
||||
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
|
||||
//
|
||||
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
|
||||
func zigzag(x int) int {
|
||||
if x&1 != 0 {
|
||||
x = ^x
|
||||
}
|
||||
return x >> 1
|
||||
}
|
||||
9
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
Normal file
9
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2019, The 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 flags
|
||||
|
||||
// Deterministic controls whether the output of Diff should be deterministic.
|
||||
// This is only used for testing.
|
||||
var Deterministic bool
|
||||
99
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
99
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2017, The 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 function provides functionality for identifying function types.
|
||||
package function
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type funcType int
|
||||
|
||||
const (
|
||||
_ funcType = iota
|
||||
|
||||
tbFunc // func(T) bool
|
||||
ttbFunc // func(T, T) bool
|
||||
trbFunc // func(T, R) bool
|
||||
tibFunc // func(T, I) bool
|
||||
trFunc // func(T) R
|
||||
|
||||
Equal = ttbFunc // func(T, T) bool
|
||||
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
|
||||
Transformer = trFunc // func(T) R
|
||||
ValueFilter = ttbFunc // func(T, T) bool
|
||||
Less = ttbFunc // func(T, T) bool
|
||||
ValuePredicate = tbFunc // func(T) bool
|
||||
KeyValuePredicate = trbFunc // func(T, R) bool
|
||||
)
|
||||
|
||||
var boolType = reflect.TypeOf(true)
|
||||
|
||||
// IsType reports whether the reflect.Type is of the specified function type.
|
||||
func IsType(t reflect.Type, ft funcType) bool {
|
||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||
return false
|
||||
}
|
||||
ni, no := t.NumIn(), t.NumOut()
|
||||
switch ft {
|
||||
case tbFunc: // func(T) bool
|
||||
if ni == 1 && no == 1 && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case ttbFunc: // func(T, T) bool
|
||||
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case trbFunc: // func(T, R) bool
|
||||
if ni == 2 && no == 1 && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case tibFunc: // func(T, I) bool
|
||||
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case trFunc: // func(T) R
|
||||
if ni == 1 && no == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
|
||||
|
||||
// NameOf returns the name of the function value.
|
||||
func NameOf(v reflect.Value) string {
|
||||
fnc := runtime.FuncForPC(v.Pointer())
|
||||
if fnc == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
|
||||
|
||||
// Method closures have a "-fm" suffix.
|
||||
fullName = strings.TrimSuffix(fullName, "-fm")
|
||||
|
||||
var name string
|
||||
for len(fullName) > 0 {
|
||||
inParen := strings.HasSuffix(fullName, ")")
|
||||
fullName = strings.TrimSuffix(fullName, ")")
|
||||
|
||||
s := lastIdentRx.FindString(fullName)
|
||||
if s == "" {
|
||||
break
|
||||
}
|
||||
name = s + "." + name
|
||||
fullName = strings.TrimSuffix(fullName, s)
|
||||
|
||||
if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
|
||||
fullName = fullName[:i]
|
||||
}
|
||||
fullName = strings.TrimSuffix(fullName, ".")
|
||||
}
|
||||
return strings.TrimSuffix(name, ".")
|
||||
}
|
||||
164
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
164
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright 2020, The 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 value
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var anyType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
|
||||
// TypeString is nearly identical to reflect.Type.String,
|
||||
// but has an additional option to specify that full type names be used.
|
||||
func TypeString(t reflect.Type, qualified bool) string {
|
||||
return string(appendTypeName(nil, t, qualified, false))
|
||||
}
|
||||
|
||||
func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte {
|
||||
// BUG: Go reflection provides no way to disambiguate two named types
|
||||
// of the same name and within the same package,
|
||||
// but declared within the namespace of different functions.
|
||||
|
||||
// Use the "any" alias instead of "interface{}" for better readability.
|
||||
if t == anyType {
|
||||
return append(b, "any"...)
|
||||
}
|
||||
|
||||
// Named type.
|
||||
if t.Name() != "" {
|
||||
if qualified && t.PkgPath() != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, t.PkgPath()...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
b = append(b, t.Name()...)
|
||||
} else {
|
||||
b = append(b, t.String()...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Unnamed type.
|
||||
switch k := t.Kind(); k {
|
||||
case reflect.Bool, reflect.String, reflect.UnsafePointer,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
b = append(b, k.String()...)
|
||||
case reflect.Chan:
|
||||
if t.ChanDir() == reflect.RecvDir {
|
||||
b = append(b, "<-"...)
|
||||
}
|
||||
b = append(b, "chan"...)
|
||||
if t.ChanDir() == reflect.SendDir {
|
||||
b = append(b, "<-"...)
|
||||
}
|
||||
b = append(b, ' ')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Func:
|
||||
if !elideFunc {
|
||||
b = append(b, "func"...)
|
||||
}
|
||||
b = append(b, '(')
|
||||
for i := 0; i < t.NumIn(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
if i == t.NumIn()-1 && t.IsVariadic() {
|
||||
b = append(b, "..."...)
|
||||
b = appendTypeName(b, t.In(i).Elem(), qualified, false)
|
||||
} else {
|
||||
b = appendTypeName(b, t.In(i), qualified, false)
|
||||
}
|
||||
}
|
||||
b = append(b, ')')
|
||||
switch t.NumOut() {
|
||||
case 0:
|
||||
// Do nothing
|
||||
case 1:
|
||||
b = append(b, ' ')
|
||||
b = appendTypeName(b, t.Out(0), qualified, false)
|
||||
default:
|
||||
b = append(b, " ("...)
|
||||
for i := 0; i < t.NumOut(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
b = appendTypeName(b, t.Out(i), qualified, false)
|
||||
}
|
||||
b = append(b, ')')
|
||||
}
|
||||
case reflect.Struct:
|
||||
b = append(b, "struct{ "...)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, "; "...)
|
||||
}
|
||||
sf := t.Field(i)
|
||||
if !sf.Anonymous {
|
||||
if qualified && sf.PkgPath != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, sf.PkgPath...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
}
|
||||
b = append(b, sf.Name...)
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = appendTypeName(b, sf.Type, qualified, false)
|
||||
if sf.Tag != "" {
|
||||
b = append(b, ' ')
|
||||
b = strconv.AppendQuote(b, string(sf.Tag))
|
||||
}
|
||||
}
|
||||
if b[len(b)-1] == ' ' {
|
||||
b = b[:len(b)-1]
|
||||
} else {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = append(b, '}')
|
||||
case reflect.Slice, reflect.Array:
|
||||
b = append(b, '[')
|
||||
if k == reflect.Array {
|
||||
b = strconv.AppendUint(b, uint64(t.Len()), 10)
|
||||
}
|
||||
b = append(b, ']')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Map:
|
||||
b = append(b, "map["...)
|
||||
b = appendTypeName(b, t.Key(), qualified, false)
|
||||
b = append(b, ']')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Ptr:
|
||||
b = append(b, '*')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Interface:
|
||||
b = append(b, "interface{ "...)
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, "; "...)
|
||||
}
|
||||
m := t.Method(i)
|
||||
if qualified && m.PkgPath != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, m.PkgPath...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
}
|
||||
b = append(b, m.Name...)
|
||||
b = appendTypeName(b, m.Type, qualified, true)
|
||||
}
|
||||
if b[len(b)-1] == ' ' {
|
||||
b = b[:len(b)-1]
|
||||
} else {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = append(b, '}')
|
||||
default:
|
||||
panic("invalid kind: " + k.String())
|
||||
}
|
||||
return b
|
||||
}
|
||||
34
vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go
generated
vendored
Normal file
34
vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2018, The 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 value
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
|
||||
type Pointer struct {
|
||||
p unsafe.Pointer
|
||||
t reflect.Type
|
||||
}
|
||||
|
||||
// PointerOf returns a Pointer from v, which must be a
|
||||
// reflect.Ptr, reflect.Slice, or reflect.Map.
|
||||
func PointerOf(v reflect.Value) Pointer {
|
||||
// The proper representation of a pointer is unsafe.Pointer,
|
||||
// which is necessary if the GC ever uses a moving collector.
|
||||
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
|
||||
}
|
||||
|
||||
// IsNil reports whether the pointer is nil.
|
||||
func (p Pointer) IsNil() bool {
|
||||
return p.p == nil
|
||||
}
|
||||
|
||||
// Uintptr returns the pointer as a uintptr.
|
||||
func (p Pointer) Uintptr() uintptr {
|
||||
return uintptr(p.p)
|
||||
}
|
||||
106
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
106
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2017, The 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 value
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SortKeys sorts a list of map keys, deduplicating keys if necessary.
|
||||
// The type of each value must be comparable.
|
||||
func SortKeys(vs []reflect.Value) []reflect.Value {
|
||||
if len(vs) == 0 {
|
||||
return vs
|
||||
}
|
||||
|
||||
// Sort the map keys.
|
||||
sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) })
|
||||
|
||||
// Deduplicate keys (fails for NaNs).
|
||||
vs2 := vs[:1]
|
||||
for _, v := range vs[1:] {
|
||||
if isLess(vs2[len(vs2)-1], v) {
|
||||
vs2 = append(vs2, v)
|
||||
}
|
||||
}
|
||||
return vs2
|
||||
}
|
||||
|
||||
// isLess is a generic function for sorting arbitrary map keys.
|
||||
// The inputs must be of the same type and must be comparable.
|
||||
func isLess(x, y reflect.Value) bool {
|
||||
switch x.Type().Kind() {
|
||||
case reflect.Bool:
|
||||
return !x.Bool() && y.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return x.Int() < y.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return x.Uint() < y.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
// NOTE: This does not sort -0 as less than +0
|
||||
// since Go maps treat -0 and +0 as equal keys.
|
||||
fx, fy := x.Float(), y.Float()
|
||||
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
cx, cy := x.Complex(), y.Complex()
|
||||
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
|
||||
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
|
||||
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
|
||||
}
|
||||
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
|
||||
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
|
||||
return x.Pointer() < y.Pointer()
|
||||
case reflect.String:
|
||||
return x.String() < y.String()
|
||||
case reflect.Array:
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
if isLess(x.Index(i), y.Index(i)) {
|
||||
return true
|
||||
}
|
||||
if isLess(y.Index(i), x.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Struct:
|
||||
for i := 0; i < x.NumField(); i++ {
|
||||
if isLess(x.Field(i), y.Field(i)) {
|
||||
return true
|
||||
}
|
||||
if isLess(y.Field(i), x.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Interface:
|
||||
vx, vy := x.Elem(), y.Elem()
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
return !vx.IsValid() && vy.IsValid()
|
||||
}
|
||||
tx, ty := vx.Type(), vy.Type()
|
||||
if tx == ty {
|
||||
return isLess(x.Elem(), y.Elem())
|
||||
}
|
||||
if tx.Kind() != ty.Kind() {
|
||||
return vx.Kind() < vy.Kind()
|
||||
}
|
||||
if tx.String() != ty.String() {
|
||||
return tx.String() < ty.String()
|
||||
}
|
||||
if tx.PkgPath() != ty.PkgPath() {
|
||||
return tx.PkgPath() < ty.PkgPath()
|
||||
}
|
||||
// This can happen in rare situations, so we fallback to just comparing
|
||||
// the unique pointer for a reflect.Type. This guarantees deterministic
|
||||
// ordering within a program, but it is obviously not stable.
|
||||
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
|
||||
default:
|
||||
// Must be Func, Map, or Slice; which are not comparable.
|
||||
panic(fmt.Sprintf("%T is not comparable", x.Type()))
|
||||
}
|
||||
}
|
||||
554
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
554
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
@@ -0,0 +1,554 @@
|
||||
// Copyright 2017, The 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
)
|
||||
|
||||
// Option configures for specific behavior of [Equal] and [Diff]. In particular,
|
||||
// the fundamental Option functions ([Ignore], [Transformer], and [Comparer]),
|
||||
// configure how equality is determined.
|
||||
//
|
||||
// The fundamental options may be composed with filters ([FilterPath] and
|
||||
// [FilterValues]) to control the scope over which they are applied.
|
||||
//
|
||||
// The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions
|
||||
// for creating options that may be used with [Equal] and [Diff].
|
||||
type Option interface {
|
||||
// filter applies all filters and returns the option that remains.
|
||||
// Each option may only read s.curPath and call s.callTTBFunc.
|
||||
//
|
||||
// An Options is returned only if multiple comparers or transformers
|
||||
// can apply simultaneously and will only contain values of those types
|
||||
// or sub-Options containing values of those types.
|
||||
filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption
|
||||
}
|
||||
|
||||
// applicableOption represents the following types:
|
||||
//
|
||||
// Fundamental: ignore | validator | *comparer | *transformer
|
||||
// Grouping: Options
|
||||
type applicableOption interface {
|
||||
Option
|
||||
|
||||
// apply executes the option, which may mutate s or panic.
|
||||
apply(s *state, vx, vy reflect.Value)
|
||||
}
|
||||
|
||||
// coreOption represents the following types:
|
||||
//
|
||||
// Fundamental: ignore | validator | *comparer | *transformer
|
||||
// Filters: *pathFilter | *valuesFilter
|
||||
type coreOption interface {
|
||||
Option
|
||||
isCore()
|
||||
}
|
||||
|
||||
type core struct{}
|
||||
|
||||
func (core) isCore() {}
|
||||
|
||||
// Options is a list of [Option] values that also satisfies the [Option] interface.
|
||||
// Helper comparison packages may return an Options value when packing multiple
|
||||
// [Option] values into a single [Option]. When this package processes an Options,
|
||||
// it will be implicitly expanded into a flat list.
|
||||
//
|
||||
// Applying a filter on an Options is equivalent to applying that same filter
|
||||
// on all individual options held within.
|
||||
type Options []Option
|
||||
|
||||
func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) {
|
||||
for _, opt := range opts {
|
||||
switch opt := opt.filter(s, t, vx, vy); opt.(type) {
|
||||
case ignore:
|
||||
return ignore{} // Only ignore can short-circuit evaluation
|
||||
case validator:
|
||||
out = validator{} // Takes precedence over comparer or transformer
|
||||
case *comparer, *transformer, Options:
|
||||
switch out.(type) {
|
||||
case nil:
|
||||
out = opt
|
||||
case validator:
|
||||
// Keep validator
|
||||
case *comparer, *transformer, Options:
|
||||
out = Options{out, opt} // Conflicting comparers or transformers
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (opts Options) apply(s *state, _, _ reflect.Value) {
|
||||
const warning = "ambiguous set of applicable options"
|
||||
const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
|
||||
var ss []string
|
||||
for _, opt := range flattenOptions(nil, opts) {
|
||||
ss = append(ss, fmt.Sprint(opt))
|
||||
}
|
||||
set := strings.Join(ss, "\n\t")
|
||||
panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
|
||||
}
|
||||
|
||||
func (opts Options) String() string {
|
||||
var ss []string
|
||||
for _, opt := range opts {
|
||||
ss = append(ss, fmt.Sprint(opt))
|
||||
}
|
||||
return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
|
||||
}
|
||||
|
||||
// FilterPath returns a new [Option] where opt is only evaluated if filter f
|
||||
// returns true for the current [Path] in the value tree.
|
||||
//
|
||||
// This filter is called even if a slice element or map entry is missing and
|
||||
// provides an opportunity to ignore such cases. The filter function must be
|
||||
// symmetric such that the filter result is identical regardless of whether the
|
||||
// missing value is from x or y.
|
||||
//
|
||||
// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
|
||||
// a previously filtered [Option].
|
||||
func FilterPath(f func(Path) bool, opt Option) Option {
|
||||
if f == nil {
|
||||
panic("invalid path filter function")
|
||||
}
|
||||
if opt := normalizeOption(opt); opt != nil {
|
||||
return &pathFilter{fnc: f, opt: opt}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type pathFilter struct {
|
||||
core
|
||||
fnc func(Path) bool
|
||||
opt Option
|
||||
}
|
||||
|
||||
func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||
if f.fnc(s.curPath) {
|
||||
return f.opt.filter(s, t, vx, vy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f pathFilter) String() string {
|
||||
return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)
|
||||
}
|
||||
|
||||
// FilterValues returns a new [Option] where opt is only evaluated if filter f,
|
||||
// which is a function of the form "func(T, T) bool", returns true for the
|
||||
// current pair of values being compared. If either value is invalid or
|
||||
// the type of the values is not assignable to T, then this filter implicitly
|
||||
// returns false.
|
||||
//
|
||||
// The filter function must be
|
||||
// symmetric (i.e., agnostic to the order of the inputs) and
|
||||
// deterministic (i.e., produces the same result when given the same inputs).
|
||||
// If T is an interface, it is possible that f is called with two values with
|
||||
// different concrete types that both implement T.
|
||||
//
|
||||
// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
|
||||
// a previously filtered [Option].
|
||||
func FilterValues(f interface{}, opt Option) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid values filter function: %T", f))
|
||||
}
|
||||
if opt := normalizeOption(opt); opt != nil {
|
||||
vf := &valuesFilter{fnc: v, opt: opt}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
vf.typ = ti
|
||||
}
|
||||
return vf
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type valuesFilter struct {
|
||||
core
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
opt Option
|
||||
}
|
||||
|
||||
func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||
if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() {
|
||||
return nil
|
||||
}
|
||||
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
|
||||
return f.opt.filter(s, t, vx, vy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f valuesFilter) String() string {
|
||||
return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)
|
||||
}
|
||||
|
||||
// Ignore is an [Option] that causes all comparisons to be ignored.
|
||||
// This value is intended to be combined with [FilterPath] or [FilterValues].
|
||||
// It is an error to pass an unfiltered Ignore option to [Equal].
|
||||
func Ignore() Option { return ignore{} }
|
||||
|
||||
type ignore struct{ core }
|
||||
|
||||
func (ignore) isFiltered() bool { return false }
|
||||
func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }
|
||||
func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) }
|
||||
func (ignore) String() string { return "Ignore()" }
|
||||
|
||||
// validator is a sentinel Option type to indicate that some options could not
|
||||
// be evaluated due to unexported fields, missing slice elements, or
|
||||
// missing map entries. Both values are validator only for unexported fields.
|
||||
type validator struct{ core }
|
||||
|
||||
func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
return validator{}
|
||||
}
|
||||
if !vx.CanInterface() || !vy.CanInterface() {
|
||||
return validator{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (validator) apply(s *state, vx, vy reflect.Value) {
|
||||
// Implies missing slice element or map entry.
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
s.report(vx.IsValid() == vy.IsValid(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Unable to Interface implies unexported field without visibility access.
|
||||
if !vx.CanInterface() || !vy.CanInterface() {
|
||||
help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
|
||||
var name string
|
||||
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
|
||||
// Named type with unexported fields.
|
||||
name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
|
||||
if _, ok := reflect.New(t).Interface().(error); ok {
|
||||
help = "consider using cmpopts.EquateErrors to compare error values"
|
||||
} else if t.Comparable() {
|
||||
help = "consider using cmpopts.EquateComparable to compare comparable Go types"
|
||||
}
|
||||
} else {
|
||||
// Unnamed type with unexported fields. Derive PkgPath from field.
|
||||
var pkgPath string
|
||||
for i := 0; i < t.NumField() && pkgPath == ""; i++ {
|
||||
pkgPath = t.Field(i).PkgPath
|
||||
}
|
||||
name = fmt.Sprintf("%q.(%v)", pkgPath, t.String()) // e.g., "path/to/package".(struct { a int })
|
||||
}
|
||||
panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help))
|
||||
}
|
||||
|
||||
panic("not reachable")
|
||||
}
|
||||
|
||||
// identRx represents a valid identifier according to the Go specification.
|
||||
const identRx = `[_\p{L}][_\p{L}\p{N}]*`
|
||||
|
||||
var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)
|
||||
|
||||
// Transformer returns an [Option] that applies a transformation function that
|
||||
// converts values of a certain type into that of another.
|
||||
//
|
||||
// The transformer f must be a function "func(T) R" that converts values of
|
||||
// type T to those of type R and is implicitly filtered to input values
|
||||
// assignable to T. The transformer must not mutate T in any way.
|
||||
//
|
||||
// To help prevent some cases of infinite recursive cycles applying the
|
||||
// same transform to the output of itself (e.g., in the case where the
|
||||
// input and output types are the same), an implicit filter is added such that
|
||||
// a transformer is applicable only if that exact transformer is not already
|
||||
// in the tail of the [Path] since the last non-[Transform] step.
|
||||
// For situations where the implicit filter is still insufficient,
|
||||
// consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer],
|
||||
// which adds a filter to prevent the transformer from
|
||||
// being recursively applied upon itself.
|
||||
//
|
||||
// The name is a user provided label that is used as the [Transform.Name] in the
|
||||
// transformation [PathStep] (and eventually shown in the [Diff] output).
|
||||
// The name must be a valid identifier or qualified identifier in Go syntax.
|
||||
// If empty, an arbitrary name is used.
|
||||
func Transformer(name string, f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid transformer function: %T", f))
|
||||
}
|
||||
if name == "" {
|
||||
name = function.NameOf(v)
|
||||
if !identsRx.MatchString(name) {
|
||||
name = "λ" // Lambda-symbol as placeholder name
|
||||
}
|
||||
} else if !identsRx.MatchString(name) {
|
||||
panic(fmt.Sprintf("invalid name: %q", name))
|
||||
}
|
||||
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
tr.typ = ti
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
type transformer struct {
|
||||
core
|
||||
name string
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T) R
|
||||
}
|
||||
|
||||
func (tr *transformer) isFiltered() bool { return tr.typ != nil }
|
||||
|
||||
func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
for i := len(s.curPath) - 1; i >= 0; i-- {
|
||||
if t, ok := s.curPath[i].(Transform); !ok {
|
||||
break // Hit most recent non-Transform step
|
||||
} else if tr == t.trans {
|
||||
return nil // Cannot directly use same Transform
|
||||
}
|
||||
}
|
||||
if tr.typ == nil || t.AssignableTo(tr.typ) {
|
||||
return tr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
|
||||
step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}}
|
||||
vvx := s.callTRFunc(tr.fnc, vx, step)
|
||||
vvy := s.callTRFunc(tr.fnc, vy, step)
|
||||
step.vx, step.vy = vvx, vvy
|
||||
s.compareAny(step)
|
||||
}
|
||||
|
||||
func (tr transformer) String() string {
|
||||
return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))
|
||||
}
|
||||
|
||||
// Comparer returns an [Option] that determines whether two values are equal
|
||||
// to each other.
|
||||
//
|
||||
// The comparer f must be a function "func(T, T) bool" and is implicitly
|
||||
// filtered to input values assignable to T. If T is an interface, it is
|
||||
// possible that f is called with two values of different concrete types that
|
||||
// both implement T.
|
||||
//
|
||||
// The equality function must be:
|
||||
// - Symmetric: equal(x, y) == equal(y, x)
|
||||
// - Deterministic: equal(x, y) == equal(x, y)
|
||||
// - Pure: equal(x, y) does not modify x or y
|
||||
func Comparer(f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid comparer function: %T", f))
|
||||
}
|
||||
cm := &comparer{fnc: v}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
cm.typ = ti
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
type comparer struct {
|
||||
core
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
}
|
||||
|
||||
func (cm *comparer) isFiltered() bool { return cm.typ != nil }
|
||||
|
||||
func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
if cm.typ == nil || t.AssignableTo(cm.typ) {
|
||||
return cm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
|
||||
eq := s.callTTBFunc(cm.fnc, vx, vy)
|
||||
s.report(eq, reportByFunc)
|
||||
}
|
||||
|
||||
func (cm comparer) String() string {
|
||||
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
|
||||
}
|
||||
|
||||
// Exporter returns an [Option] that specifies whether [Equal] is allowed to
|
||||
// introspect into the unexported fields of certain struct types.
|
||||
//
|
||||
// Users of this option must understand that comparing on unexported fields
|
||||
// from external packages is not safe since changes in the internal
|
||||
// implementation of some external package may cause the result of [Equal]
|
||||
// to unexpectedly change. However, it may be valid to use this option on types
|
||||
// defined in an internal package where the semantic meaning of an unexported
|
||||
// field is in the control of the user.
|
||||
//
|
||||
// In many cases, a custom [Comparer] should be used instead that defines
|
||||
// equality as a function of the public API of a type rather than the underlying
|
||||
// unexported implementation.
|
||||
//
|
||||
// For example, the [reflect.Type] documentation defines equality to be determined
|
||||
// by the == operator on the interface (essentially performing a shallow pointer
|
||||
// comparison) and most attempts to compare *[regexp.Regexp] types are interested
|
||||
// in only checking that the regular expression strings are equal.
|
||||
// Both of these are accomplished using [Comparer] options:
|
||||
//
|
||||
// Comparer(func(x, y reflect.Type) bool { return x == y })
|
||||
// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
|
||||
//
|
||||
// In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]
|
||||
// option can be used to ignore all unexported fields on specified struct types.
|
||||
func Exporter(f func(reflect.Type) bool) Option {
|
||||
return exporter(f)
|
||||
}
|
||||
|
||||
type exporter func(reflect.Type) bool
|
||||
|
||||
func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect
|
||||
// unexported fields of the specified struct types.
|
||||
//
|
||||
// See [Exporter] for the proper use of this option.
|
||||
func AllowUnexported(types ...interface{}) Option {
|
||||
m := make(map[reflect.Type]bool)
|
||||
for _, typ := range types {
|
||||
t := reflect.TypeOf(typ)
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid struct type: %T", typ))
|
||||
}
|
||||
m[t] = true
|
||||
}
|
||||
return exporter(func(t reflect.Type) bool { return m[t] })
|
||||
}
|
||||
|
||||
// Result represents the comparison result for a single node and
|
||||
// is provided by cmp when calling Report (see [Reporter]).
|
||||
type Result struct {
|
||||
_ [0]func() // Make Result incomparable
|
||||
flags resultFlags
|
||||
}
|
||||
|
||||
// Equal reports whether the node was determined to be equal or not.
|
||||
// As a special case, ignored nodes are considered equal.
|
||||
func (r Result) Equal() bool {
|
||||
return r.flags&(reportEqual|reportByIgnore) != 0
|
||||
}
|
||||
|
||||
// ByIgnore reports whether the node is equal because it was ignored.
|
||||
// This never reports true if [Result.Equal] reports false.
|
||||
func (r Result) ByIgnore() bool {
|
||||
return r.flags&reportByIgnore != 0
|
||||
}
|
||||
|
||||
// ByMethod reports whether the Equal method determined equality.
|
||||
func (r Result) ByMethod() bool {
|
||||
return r.flags&reportByMethod != 0
|
||||
}
|
||||
|
||||
// ByFunc reports whether a [Comparer] function determined equality.
|
||||
func (r Result) ByFunc() bool {
|
||||
return r.flags&reportByFunc != 0
|
||||
}
|
||||
|
||||
// ByCycle reports whether a reference cycle was detected.
|
||||
func (r Result) ByCycle() bool {
|
||||
return r.flags&reportByCycle != 0
|
||||
}
|
||||
|
||||
type resultFlags uint
|
||||
|
||||
const (
|
||||
_ resultFlags = (1 << iota) / 2
|
||||
|
||||
reportEqual
|
||||
reportUnequal
|
||||
reportByIgnore
|
||||
reportByMethod
|
||||
reportByFunc
|
||||
reportByCycle
|
||||
)
|
||||
|
||||
// Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses
|
||||
// the value trees, it calls PushStep as it descends into each node in the
|
||||
// tree and PopStep as it ascend out of the node. The leaves of the tree are
|
||||
// either compared (determined to be equal or not equal) or ignored and reported
|
||||
// as such by calling the Report method.
|
||||
func Reporter(r interface {
|
||||
// PushStep is called when a tree-traversal operation is performed.
|
||||
// The PathStep itself is only valid until the step is popped.
|
||||
// The PathStep.Values are valid for the duration of the entire traversal
|
||||
// and must not be mutated.
|
||||
//
|
||||
// Equal always calls PushStep at the start to provide an operation-less
|
||||
// PathStep used to report the root values.
|
||||
//
|
||||
// Within a slice, the exact set of inserted, removed, or modified elements
|
||||
// is unspecified and may change in future implementations.
|
||||
// The entries of a map are iterated through in an unspecified order.
|
||||
PushStep(PathStep)
|
||||
|
||||
// Report is called exactly once on leaf nodes to report whether the
|
||||
// comparison identified the node as equal, unequal, or ignored.
|
||||
// A leaf node is one that is immediately preceded by and followed by
|
||||
// a pair of PushStep and PopStep calls.
|
||||
Report(Result)
|
||||
|
||||
// PopStep ascends back up the value tree.
|
||||
// There is always a matching pop call for every push call.
|
||||
PopStep()
|
||||
}) Option {
|
||||
return reporter{r}
|
||||
}
|
||||
|
||||
type reporter struct{ reporterIface }
|
||||
type reporterIface interface {
|
||||
PushStep(PathStep)
|
||||
Report(Result)
|
||||
PopStep()
|
||||
}
|
||||
|
||||
func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// normalizeOption normalizes the input options such that all Options groups
|
||||
// are flattened and groups with a single element are reduced to that element.
|
||||
// Only coreOptions and Options containing coreOptions are allowed.
|
||||
func normalizeOption(src Option) Option {
|
||||
switch opts := flattenOptions(nil, Options{src}); len(opts) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return opts[0]
|
||||
default:
|
||||
return opts
|
||||
}
|
||||
}
|
||||
|
||||
// flattenOptions copies all options in src to dst as a flat list.
|
||||
// Only coreOptions and Options containing coreOptions are allowed.
|
||||
func flattenOptions(dst, src Options) Options {
|
||||
for _, opt := range src {
|
||||
switch opt := opt.(type) {
|
||||
case nil:
|
||||
continue
|
||||
case Options:
|
||||
dst = flattenOptions(dst, opt)
|
||||
case coreOption:
|
||||
dst = append(dst, opt)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid option type: %T", opt))
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
390
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
390
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
@@ -0,0 +1,390 @@
|
||||
// Copyright 2017, The 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// Path is a list of [PathStep] describing the sequence of operations to get
|
||||
// from some root type to the current position in the value tree.
|
||||
// The first Path element is always an operation-less [PathStep] that exists
|
||||
// simply to identify the initial type.
|
||||
//
|
||||
// When traversing structs with embedded structs, the embedded struct will
|
||||
// always be accessed as a field before traversing the fields of the
|
||||
// embedded struct themselves. That is, an exported field from the
|
||||
// embedded struct will never be accessed directly from the parent struct.
|
||||
type Path []PathStep
|
||||
|
||||
// PathStep is a union-type for specific operations to traverse
|
||||
// a value's tree structure. Users of this package never need to implement
|
||||
// these types as values of this type will be returned by this package.
|
||||
//
|
||||
// Implementations of this interface:
|
||||
// - [StructField]
|
||||
// - [SliceIndex]
|
||||
// - [MapIndex]
|
||||
// - [Indirect]
|
||||
// - [TypeAssertion]
|
||||
// - [Transform]
|
||||
type PathStep interface {
|
||||
String() string
|
||||
|
||||
// Type is the resulting type after performing the path step.
|
||||
Type() reflect.Type
|
||||
|
||||
// Values is the resulting values after performing the path step.
|
||||
// The type of each valid value is guaranteed to be identical to Type.
|
||||
//
|
||||
// In some cases, one or both may be invalid or have restrictions:
|
||||
// - For StructField, both are not interface-able if the current field
|
||||
// is unexported and the struct type is not explicitly permitted by
|
||||
// an Exporter to traverse unexported fields.
|
||||
// - For SliceIndex, one may be invalid if an element is missing from
|
||||
// either the x or y slice.
|
||||
// - For MapIndex, one may be invalid if an entry is missing from
|
||||
// either the x or y map.
|
||||
//
|
||||
// The provided values must not be mutated.
|
||||
Values() (vx, vy reflect.Value)
|
||||
}
|
||||
|
||||
var (
|
||||
_ PathStep = StructField{}
|
||||
_ PathStep = SliceIndex{}
|
||||
_ PathStep = MapIndex{}
|
||||
_ PathStep = Indirect{}
|
||||
_ PathStep = TypeAssertion{}
|
||||
_ PathStep = Transform{}
|
||||
)
|
||||
|
||||
func (pa *Path) push(s PathStep) {
|
||||
*pa = append(*pa, s)
|
||||
}
|
||||
|
||||
func (pa *Path) pop() {
|
||||
*pa = (*pa)[:len(*pa)-1]
|
||||
}
|
||||
|
||||
// Last returns the last [PathStep] in the Path.
|
||||
// If the path is empty, this returns a non-nil [PathStep]
|
||||
// that reports a nil [PathStep.Type].
|
||||
func (pa Path) Last() PathStep {
|
||||
return pa.Index(-1)
|
||||
}
|
||||
|
||||
// Index returns the ith step in the Path and supports negative indexing.
|
||||
// A negative index starts counting from the tail of the Path such that -1
|
||||
// refers to the last step, -2 refers to the second-to-last step, and so on.
|
||||
// If index is invalid, this returns a non-nil [PathStep]
|
||||
// that reports a nil [PathStep.Type].
|
||||
func (pa Path) Index(i int) PathStep {
|
||||
if i < 0 {
|
||||
i = len(pa) + i
|
||||
}
|
||||
if i < 0 || i >= len(pa) {
|
||||
return pathStep{}
|
||||
}
|
||||
return pa[i]
|
||||
}
|
||||
|
||||
// String returns the simplified path to a node.
|
||||
// The simplified path only contains struct field accesses.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// MyMap.MySlices.MyField
|
||||
func (pa Path) String() string {
|
||||
var ss []string
|
||||
for _, s := range pa {
|
||||
if _, ok := s.(StructField); ok {
|
||||
ss = append(ss, s.String())
|
||||
}
|
||||
}
|
||||
return strings.TrimPrefix(strings.Join(ss, ""), ".")
|
||||
}
|
||||
|
||||
// GoString returns the path to a specific node using Go syntax.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
|
||||
func (pa Path) GoString() string {
|
||||
var ssPre, ssPost []string
|
||||
var numIndirect int
|
||||
for i, s := range pa {
|
||||
var nextStep PathStep
|
||||
if i+1 < len(pa) {
|
||||
nextStep = pa[i+1]
|
||||
}
|
||||
switch s := s.(type) {
|
||||
case Indirect:
|
||||
numIndirect++
|
||||
pPre, pPost := "(", ")"
|
||||
switch nextStep.(type) {
|
||||
case Indirect:
|
||||
continue // Next step is indirection, so let them batch up
|
||||
case StructField:
|
||||
numIndirect-- // Automatic indirection on struct fields
|
||||
case nil:
|
||||
pPre, pPost = "", "" // Last step; no need for parenthesis
|
||||
}
|
||||
if numIndirect > 0 {
|
||||
ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
|
||||
ssPost = append(ssPost, pPost)
|
||||
}
|
||||
numIndirect = 0
|
||||
continue
|
||||
case Transform:
|
||||
ssPre = append(ssPre, s.trans.name+"(")
|
||||
ssPost = append(ssPost, ")")
|
||||
continue
|
||||
}
|
||||
ssPost = append(ssPost, s.String())
|
||||
}
|
||||
for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
|
||||
ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
|
||||
}
|
||||
return strings.Join(ssPre, "") + strings.Join(ssPost, "")
|
||||
}
|
||||
|
||||
type pathStep struct {
|
||||
typ reflect.Type
|
||||
vx, vy reflect.Value
|
||||
}
|
||||
|
||||
func (ps pathStep) Type() reflect.Type { return ps.typ }
|
||||
func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy }
|
||||
func (ps pathStep) String() string {
|
||||
if ps.typ == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
s := value.TypeString(ps.typ, false)
|
||||
if s == "" || strings.ContainsAny(s, "{}\n") {
|
||||
return "root" // Type too simple or complex to print
|
||||
}
|
||||
return fmt.Sprintf("{%s}", s)
|
||||
}
|
||||
|
||||
// StructField is a [PathStep] that represents a struct field access
|
||||
// on a field called [StructField.Name].
|
||||
type StructField struct{ *structField }
|
||||
type structField struct {
|
||||
pathStep
|
||||
name string
|
||||
idx int
|
||||
|
||||
// These fields are used for forcibly accessing an unexported field.
|
||||
// pvx, pvy, and field are only valid if unexported is true.
|
||||
unexported bool
|
||||
mayForce bool // Forcibly allow visibility
|
||||
paddr bool // Was parent addressable?
|
||||
pvx, pvy reflect.Value // Parent values (always addressable)
|
||||
field reflect.StructField // Field information
|
||||
}
|
||||
|
||||
func (sf StructField) Type() reflect.Type { return sf.typ }
|
||||
func (sf StructField) Values() (vx, vy reflect.Value) {
|
||||
if !sf.unexported {
|
||||
return sf.vx, sf.vy // CanInterface reports true
|
||||
}
|
||||
|
||||
// Forcibly obtain read-write access to an unexported struct field.
|
||||
if sf.mayForce {
|
||||
vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr)
|
||||
vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr)
|
||||
return vx, vy // CanInterface reports true
|
||||
}
|
||||
return sf.vx, sf.vy // CanInterface reports false
|
||||
}
|
||||
func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) }
|
||||
|
||||
// Name is the field name.
|
||||
func (sf StructField) Name() string { return sf.name }
|
||||
|
||||
// Index is the index of the field in the parent struct type.
|
||||
// See [reflect.Type.Field].
|
||||
func (sf StructField) Index() int { return sf.idx }
|
||||
|
||||
// SliceIndex is a [PathStep] that represents an index operation on
|
||||
// a slice or array at some index [SliceIndex.Key].
|
||||
type SliceIndex struct{ *sliceIndex }
|
||||
type sliceIndex struct {
|
||||
pathStep
|
||||
xkey, ykey int
|
||||
isSlice bool // False for reflect.Array
|
||||
}
|
||||
|
||||
func (si SliceIndex) Type() reflect.Type { return si.typ }
|
||||
func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy }
|
||||
func (si SliceIndex) String() string {
|
||||
switch {
|
||||
case si.xkey == si.ykey:
|
||||
return fmt.Sprintf("[%d]", si.xkey)
|
||||
case si.ykey == -1:
|
||||
// [5->?] means "I don't know where X[5] went"
|
||||
return fmt.Sprintf("[%d->?]", si.xkey)
|
||||
case si.xkey == -1:
|
||||
// [?->3] means "I don't know where Y[3] came from"
|
||||
return fmt.Sprintf("[?->%d]", si.ykey)
|
||||
default:
|
||||
// [5->3] means "X[5] moved to Y[3]"
|
||||
return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
|
||||
}
|
||||
}
|
||||
|
||||
// Key is the index key; it may return -1 if in a split state
|
||||
func (si SliceIndex) Key() int {
|
||||
if si.xkey != si.ykey {
|
||||
return -1
|
||||
}
|
||||
return si.xkey
|
||||
}
|
||||
|
||||
// SplitKeys are the indexes for indexing into slices in the
|
||||
// x and y values, respectively. These indexes may differ due to the
|
||||
// insertion or removal of an element in one of the slices, causing
|
||||
// all of the indexes to be shifted. If an index is -1, then that
|
||||
// indicates that the element does not exist in the associated slice.
|
||||
//
|
||||
// [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes
|
||||
// returned by SplitKeys are not the same. SplitKeys will never return -1 for
|
||||
// both indexes.
|
||||
func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey }
|
||||
|
||||
// MapIndex is a [PathStep] that represents an index operation on a map at some index Key.
|
||||
type MapIndex struct{ *mapIndex }
|
||||
type mapIndex struct {
|
||||
pathStep
|
||||
key reflect.Value
|
||||
}
|
||||
|
||||
func (mi MapIndex) Type() reflect.Type { return mi.typ }
|
||||
func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy }
|
||||
func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
|
||||
|
||||
// Key is the value of the map key.
|
||||
func (mi MapIndex) Key() reflect.Value { return mi.key }
|
||||
|
||||
// Indirect is a [PathStep] that represents pointer indirection on the parent type.
|
||||
type Indirect struct{ *indirect }
|
||||
type indirect struct {
|
||||
pathStep
|
||||
}
|
||||
|
||||
func (in Indirect) Type() reflect.Type { return in.typ }
|
||||
func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy }
|
||||
func (in Indirect) String() string { return "*" }
|
||||
|
||||
// TypeAssertion is a [PathStep] that represents a type assertion on an interface.
|
||||
type TypeAssertion struct{ *typeAssertion }
|
||||
type typeAssertion struct {
|
||||
pathStep
|
||||
}
|
||||
|
||||
func (ta TypeAssertion) Type() reflect.Type { return ta.typ }
|
||||
func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }
|
||||
func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) }
|
||||
|
||||
// Transform is a [PathStep] that represents a transformation
|
||||
// from the parent type to the current type.
|
||||
type Transform struct{ *transform }
|
||||
type transform struct {
|
||||
pathStep
|
||||
trans *transformer
|
||||
}
|
||||
|
||||
func (tf Transform) Type() reflect.Type { return tf.typ }
|
||||
func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy }
|
||||
func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
|
||||
|
||||
// Name is the name of the [Transformer].
|
||||
func (tf Transform) Name() string { return tf.trans.name }
|
||||
|
||||
// Func is the function pointer to the transformer function.
|
||||
func (tf Transform) Func() reflect.Value { return tf.trans.fnc }
|
||||
|
||||
// Option returns the originally constructed [Transformer] option.
|
||||
// The == operator can be used to detect the exact option used.
|
||||
func (tf Transform) Option() Option { return tf.trans }
|
||||
|
||||
// pointerPath represents a dual-stack of pointers encountered when
|
||||
// recursively traversing the x and y values. This data structure supports
|
||||
// detection of cycles and determining whether the cycles are equal.
|
||||
// In Go, cycles can occur via pointers, slices, and maps.
|
||||
//
|
||||
// The pointerPath uses a map to represent a stack; where descension into a
|
||||
// pointer pushes the address onto the stack, and ascension from a pointer
|
||||
// pops the address from the stack. Thus, when traversing into a pointer from
|
||||
// reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
|
||||
// by checking whether the pointer has already been visited. The cycle detection
|
||||
// uses a separate stack for the x and y values.
|
||||
//
|
||||
// If a cycle is detected we need to determine whether the two pointers
|
||||
// should be considered equal. The definition of equality chosen by Equal
|
||||
// requires two graphs to have the same structure. To determine this, both the
|
||||
// x and y values must have a cycle where the previous pointers were also
|
||||
// encountered together as a pair.
|
||||
//
|
||||
// Semantically, this is equivalent to augmenting Indirect, SliceIndex, and
|
||||
// MapIndex with pointer information for the x and y values.
|
||||
// Suppose px and py are two pointers to compare, we then search the
|
||||
// Path for whether px was ever encountered in the Path history of x, and
|
||||
// similarly so with py. If either side has a cycle, the comparison is only
|
||||
// equal if both px and py have a cycle resulting from the same PathStep.
|
||||
//
|
||||
// Using a map as a stack is more performant as we can perform cycle detection
|
||||
// in O(1) instead of O(N) where N is len(Path).
|
||||
type pointerPath struct {
|
||||
// mx is keyed by x pointers, where the value is the associated y pointer.
|
||||
mx map[value.Pointer]value.Pointer
|
||||
// my is keyed by y pointers, where the value is the associated x pointer.
|
||||
my map[value.Pointer]value.Pointer
|
||||
}
|
||||
|
||||
func (p *pointerPath) Init() {
|
||||
p.mx = make(map[value.Pointer]value.Pointer)
|
||||
p.my = make(map[value.Pointer]value.Pointer)
|
||||
}
|
||||
|
||||
// Push indicates intent to descend into pointers vx and vy where
|
||||
// visited reports whether either has been seen before. If visited before,
|
||||
// equal reports whether both pointers were encountered together.
|
||||
// Pop must be called if and only if the pointers were never visited.
|
||||
//
|
||||
// The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map
|
||||
// and be non-nil.
|
||||
func (p pointerPath) Push(vx, vy reflect.Value) (equal, visited bool) {
|
||||
px := value.PointerOf(vx)
|
||||
py := value.PointerOf(vy)
|
||||
_, ok1 := p.mx[px]
|
||||
_, ok2 := p.my[py]
|
||||
if ok1 || ok2 {
|
||||
equal = p.mx[px] == py && p.my[py] == px // Pointers paired together
|
||||
return equal, true
|
||||
}
|
||||
p.mx[px] = py
|
||||
p.my[py] = px
|
||||
return false, false
|
||||
}
|
||||
|
||||
// Pop ascends from pointers vx and vy.
|
||||
func (p pointerPath) Pop(vx, vy reflect.Value) {
|
||||
delete(p.mx, value.PointerOf(vx))
|
||||
delete(p.my, value.PointerOf(vy))
|
||||
}
|
||||
|
||||
// isExported reports whether the identifier is exported.
|
||||
func isExported(id string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(id)
|
||||
return unicode.IsUpper(r)
|
||||
}
|
||||
54
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
Normal file
54
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017, The 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 cmp
|
||||
|
||||
// defaultReporter implements the reporter interface.
|
||||
//
|
||||
// As Equal serially calls the PushStep, Report, and PopStep methods, the
|
||||
// defaultReporter constructs a tree-based representation of the compared value
|
||||
// and the result of each comparison (see valueNode).
|
||||
//
|
||||
// When the String method is called, the FormatDiff method transforms the
|
||||
// valueNode tree into a textNode tree, which is a tree-based representation
|
||||
// of the textual output (see textNode).
|
||||
//
|
||||
// Lastly, the textNode.String method produces the final report as a string.
|
||||
type defaultReporter struct {
|
||||
root *valueNode
|
||||
curr *valueNode
|
||||
}
|
||||
|
||||
func (r *defaultReporter) PushStep(ps PathStep) {
|
||||
r.curr = r.curr.PushStep(ps)
|
||||
if r.root == nil {
|
||||
r.root = r.curr
|
||||
}
|
||||
}
|
||||
func (r *defaultReporter) Report(rs Result) {
|
||||
r.curr.Report(rs)
|
||||
}
|
||||
func (r *defaultReporter) PopStep() {
|
||||
r.curr = r.curr.PopStep()
|
||||
}
|
||||
|
||||
// String provides a full report of the differences detected as a structured
|
||||
// literal in pseudo-Go syntax. String may only be called after the entire tree
|
||||
// has been traversed.
|
||||
func (r *defaultReporter) String() string {
|
||||
assert(r.root != nil && r.curr == nil)
|
||||
if r.root.NumDiff == 0 {
|
||||
return ""
|
||||
}
|
||||
ptrs := new(pointerReferences)
|
||||
text := formatOptions{}.FormatDiff(r.root, ptrs)
|
||||
resolveReferences(text)
|
||||
return text.String()
|
||||
}
|
||||
|
||||
func assert(ok bool) {
|
||||
if !ok {
|
||||
panic("assertion failure")
|
||||
}
|
||||
}
|
||||
433
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
Normal file
433
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
Normal file
@@ -0,0 +1,433 @@
|
||||
// Copyright 2019, The 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// numContextRecords is the number of surrounding equal records to print.
|
||||
const numContextRecords = 2
|
||||
|
||||
type diffMode byte
|
||||
|
||||
const (
|
||||
diffUnknown diffMode = 0
|
||||
diffIdentical diffMode = ' '
|
||||
diffRemoved diffMode = '-'
|
||||
diffInserted diffMode = '+'
|
||||
)
|
||||
|
||||
type typeMode int
|
||||
|
||||
const (
|
||||
// emitType always prints the type.
|
||||
emitType typeMode = iota
|
||||
// elideType never prints the type.
|
||||
elideType
|
||||
// autoType prints the type only for composite kinds
|
||||
// (i.e., structs, slices, arrays, and maps).
|
||||
autoType
|
||||
)
|
||||
|
||||
type formatOptions struct {
|
||||
// DiffMode controls the output mode of FormatDiff.
|
||||
//
|
||||
// If diffUnknown, then produce a diff of the x and y values.
|
||||
// If diffIdentical, then emit values as if they were equal.
|
||||
// If diffRemoved, then only emit x values (ignoring y values).
|
||||
// If diffInserted, then only emit y values (ignoring x values).
|
||||
DiffMode diffMode
|
||||
|
||||
// TypeMode controls whether to print the type for the current node.
|
||||
//
|
||||
// As a general rule of thumb, we always print the type of the next node
|
||||
// after an interface, and always elide the type of the next node after
|
||||
// a slice or map node.
|
||||
TypeMode typeMode
|
||||
|
||||
// formatValueOptions are options specific to printing reflect.Values.
|
||||
formatValueOptions
|
||||
}
|
||||
|
||||
func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
|
||||
opts.DiffMode = d
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
|
||||
opts.TypeMode = t
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) WithVerbosity(level int) formatOptions {
|
||||
opts.VerbosityLevel = level
|
||||
opts.LimitVerbosity = true
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) verbosity() uint {
|
||||
switch {
|
||||
case opts.VerbosityLevel < 0:
|
||||
return 0
|
||||
case opts.VerbosityLevel > 16:
|
||||
return 16 // some reasonable maximum to avoid shift overflow
|
||||
default:
|
||||
return uint(opts.VerbosityLevel)
|
||||
}
|
||||
}
|
||||
|
||||
const maxVerbosityPreset = 6
|
||||
|
||||
// verbosityPreset modifies the verbosity settings given an index
|
||||
// between 0 and maxVerbosityPreset, inclusive.
|
||||
func verbosityPreset(opts formatOptions, i int) formatOptions {
|
||||
opts.VerbosityLevel = int(opts.verbosity()) + 2*i
|
||||
if i > 0 {
|
||||
opts.AvoidStringer = true
|
||||
}
|
||||
if i >= maxVerbosityPreset {
|
||||
opts.PrintAddresses = true
|
||||
opts.QualifiedNames = true
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// FormatDiff converts a valueNode tree into a textNode tree, where the later
|
||||
// is a textual representation of the differences detected in the former.
|
||||
func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
|
||||
if opts.DiffMode == diffIdentical {
|
||||
opts = opts.WithVerbosity(1)
|
||||
} else if opts.verbosity() < 3 {
|
||||
opts = opts.WithVerbosity(3)
|
||||
}
|
||||
|
||||
// Check whether we have specialized formatting for this node.
|
||||
// This is not necessary, but helpful for producing more readable outputs.
|
||||
if opts.CanFormatDiffSlice(v) {
|
||||
return opts.FormatDiffSlice(v)
|
||||
}
|
||||
|
||||
var parentKind reflect.Kind
|
||||
if v.parent != nil && v.parent.TransformerName == "" {
|
||||
parentKind = v.parent.Type.Kind()
|
||||
}
|
||||
|
||||
// For leaf nodes, format the value based on the reflect.Values alone.
|
||||
// As a special case, treat equal []byte as a leaf nodes.
|
||||
isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType
|
||||
isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0
|
||||
if v.MaxDepth == 0 || isEqualBytes {
|
||||
switch opts.DiffMode {
|
||||
case diffUnknown, diffIdentical:
|
||||
// Format Equal.
|
||||
if v.NumDiff == 0 {
|
||||
outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
if v.NumIgnored > 0 && v.NumSame == 0 {
|
||||
return textEllipsis
|
||||
} else if outx.Len() < outy.Len() {
|
||||
return outx
|
||||
} else {
|
||||
return outy
|
||||
}
|
||||
}
|
||||
|
||||
// Format unequal.
|
||||
assert(opts.DiffMode == diffUnknown)
|
||||
var list textList
|
||||
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
|
||||
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||
opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
|
||||
outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
}
|
||||
if outx != nil {
|
||||
list = append(list, textRecord{Diff: '-', Value: outx})
|
||||
}
|
||||
if outy != nil {
|
||||
list = append(list, textRecord{Diff: '+', Value: outy})
|
||||
}
|
||||
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
||||
case diffRemoved:
|
||||
return opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
case diffInserted:
|
||||
return opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
default:
|
||||
panic("invalid diff mode")
|
||||
}
|
||||
}
|
||||
|
||||
// Register slice element to support cycle detection.
|
||||
if parentKind == reflect.Slice {
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
|
||||
defer ptrs.Pop()
|
||||
defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
|
||||
}
|
||||
|
||||
// Descend into the child value node.
|
||||
if v.TransformerName != "" {
|
||||
out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||
out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
|
||||
return opts.FormatType(v.Type, out)
|
||||
} else {
|
||||
switch k := v.Type.Kind(); k {
|
||||
case reflect.Struct, reflect.Array, reflect.Slice:
|
||||
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||
out = opts.FormatType(v.Type, out)
|
||||
case reflect.Map:
|
||||
// Register map to support cycle detection.
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||
defer ptrs.Pop()
|
||||
|
||||
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||
out = wrapTrunkReferences(ptrRefs, out)
|
||||
out = opts.FormatType(v.Type, out)
|
||||
case reflect.Ptr:
|
||||
// Register pointer to support cycle detection.
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||
defer ptrs.Pop()
|
||||
|
||||
out = opts.FormatDiff(v.Value, ptrs)
|
||||
out = wrapTrunkReferences(ptrRefs, out)
|
||||
out = &textWrap{Prefix: "&", Value: out}
|
||||
case reflect.Interface:
|
||||
out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v cannot have children", k))
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
|
||||
// Derive record name based on the data structure kind.
|
||||
var name string
|
||||
var formatKey func(reflect.Value) string
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
name = "field"
|
||||
opts = opts.WithTypeMode(autoType)
|
||||
formatKey = func(v reflect.Value) string { return v.String() }
|
||||
case reflect.Slice, reflect.Array:
|
||||
name = "element"
|
||||
opts = opts.WithTypeMode(elideType)
|
||||
formatKey = func(reflect.Value) string { return "" }
|
||||
case reflect.Map:
|
||||
name = "entry"
|
||||
opts = opts.WithTypeMode(elideType)
|
||||
formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
|
||||
}
|
||||
|
||||
maxLen := -1
|
||||
if opts.LimitVerbosity {
|
||||
if opts.DiffMode == diffIdentical {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
} else {
|
||||
maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
|
||||
}
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
|
||||
// Handle unification.
|
||||
switch opts.DiffMode {
|
||||
case diffIdentical, diffRemoved, diffInserted:
|
||||
var list textList
|
||||
var deferredEllipsis bool // Add final "..." to indicate records were dropped
|
||||
for _, r := range recs {
|
||||
if len(list) == maxLen {
|
||||
deferredEllipsis = true
|
||||
break
|
||||
}
|
||||
|
||||
// Elide struct fields that are zero value.
|
||||
if k == reflect.Struct {
|
||||
var isZero bool
|
||||
switch opts.DiffMode {
|
||||
case diffIdentical:
|
||||
isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero()
|
||||
case diffRemoved:
|
||||
isZero = r.Value.ValueX.IsZero()
|
||||
case diffInserted:
|
||||
isZero = r.Value.ValueY.IsZero()
|
||||
}
|
||||
if isZero {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Elide ignored nodes.
|
||||
if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
|
||||
deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
|
||||
if !deferredEllipsis {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if out := opts.FormatDiff(r.Value, ptrs); out != nil {
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
}
|
||||
}
|
||||
if deferredEllipsis {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
}
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
case diffUnknown:
|
||||
default:
|
||||
panic("invalid diff mode")
|
||||
}
|
||||
|
||||
// Handle differencing.
|
||||
var numDiffs int
|
||||
var list textList
|
||||
var keys []reflect.Value // invariant: len(list) == len(keys)
|
||||
groups := coalesceAdjacentRecords(name, recs)
|
||||
maxGroup := diffStats{Name: name}
|
||||
for i, ds := range groups {
|
||||
if maxLen >= 0 && numDiffs >= maxLen {
|
||||
maxGroup = maxGroup.Append(ds)
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle equal records.
|
||||
if ds.NumDiff() == 0 {
|
||||
// Compute the number of leading and trailing records to print.
|
||||
var numLo, numHi int
|
||||
numEqual := ds.NumIgnored + ds.NumIdentical
|
||||
for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
|
||||
if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
||||
break
|
||||
}
|
||||
numLo++
|
||||
}
|
||||
for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
|
||||
if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
||||
break
|
||||
}
|
||||
numHi++
|
||||
}
|
||||
if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
|
||||
numHi++ // Avoid pointless coalescing of a single equal record
|
||||
}
|
||||
|
||||
// Format the equal values.
|
||||
for _, r := range recs[:numLo] {
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
if numEqual > numLo+numHi {
|
||||
ds.NumIdentical -= numLo + numHi
|
||||
list.AppendEllipsis(ds)
|
||||
for len(keys) < len(list) {
|
||||
keys = append(keys, reflect.Value{})
|
||||
}
|
||||
}
|
||||
for _, r := range recs[numEqual-numHi : numEqual] {
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
recs = recs[numEqual:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle unequal records.
|
||||
for _, r := range recs[:ds.NumDiff()] {
|
||||
switch {
|
||||
case opts.CanFormatDiffSlice(r.Value):
|
||||
out := opts.FormatDiffSlice(r.Value)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
case r.Value.NumChildren == r.Value.MaxDepth:
|
||||
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||
opts2 := verbosityPreset(opts, i)
|
||||
outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||
outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||
}
|
||||
if outx != nil {
|
||||
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
if outy != nil {
|
||||
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
default:
|
||||
out := opts.FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
}
|
||||
recs = recs[ds.NumDiff():]
|
||||
numDiffs += ds.NumDiff()
|
||||
}
|
||||
if maxGroup.IsZero() {
|
||||
assert(len(recs) == 0)
|
||||
} else {
|
||||
list.AppendEllipsis(maxGroup)
|
||||
for len(keys) < len(list) {
|
||||
keys = append(keys, reflect.Value{})
|
||||
}
|
||||
}
|
||||
assert(len(list) == len(keys))
|
||||
|
||||
// For maps, the default formatting logic uses fmt.Stringer which may
|
||||
// produce ambiguous output. Avoid calling String to disambiguate.
|
||||
if k == reflect.Map {
|
||||
var ambiguous bool
|
||||
seenKeys := map[string]reflect.Value{}
|
||||
for i, currKey := range keys {
|
||||
if currKey.IsValid() {
|
||||
strKey := list[i].Key
|
||||
prevKey, seen := seenKeys[strKey]
|
||||
if seen && prevKey.CanInterface() && currKey.CanInterface() {
|
||||
ambiguous = prevKey.Interface() != currKey.Interface()
|
||||
if ambiguous {
|
||||
break
|
||||
}
|
||||
}
|
||||
seenKeys[strKey] = currKey
|
||||
}
|
||||
}
|
||||
if ambiguous {
|
||||
for i, k := range keys {
|
||||
if k.IsValid() {
|
||||
list[i].Key = formatMapKey(k, true, ptrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
}
|
||||
|
||||
// coalesceAdjacentRecords coalesces the list of records into groups of
|
||||
// adjacent equal, or unequal counts.
|
||||
func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
|
||||
var prevCase int // Arbitrary index into which case last occurred
|
||||
lastStats := func(i int) *diffStats {
|
||||
if prevCase != i {
|
||||
groups = append(groups, diffStats{Name: name})
|
||||
prevCase = i
|
||||
}
|
||||
return &groups[len(groups)-1]
|
||||
}
|
||||
for _, r := range recs {
|
||||
switch rv := r.Value; {
|
||||
case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
|
||||
lastStats(1).NumIgnored++
|
||||
case rv.NumDiff == 0:
|
||||
lastStats(1).NumIdentical++
|
||||
case rv.NumDiff > 0 && !rv.ValueY.IsValid():
|
||||
lastStats(2).NumRemoved++
|
||||
case rv.NumDiff > 0 && !rv.ValueX.IsValid():
|
||||
lastStats(2).NumInserted++
|
||||
default:
|
||||
lastStats(2).NumModified++
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
// Copyright 2020, The 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 cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
const (
|
||||
pointerDelimPrefix = "⟪"
|
||||
pointerDelimSuffix = "⟫"
|
||||
)
|
||||
|
||||
// formatPointer prints the address of the pointer.
|
||||
func formatPointer(p value.Pointer, withDelims bool) string {
|
||||
v := p.Uintptr()
|
||||
if flags.Deterministic {
|
||||
v = 0xdeadf00f // Only used for stable testing purposes
|
||||
}
|
||||
if withDelims {
|
||||
return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
|
||||
}
|
||||
return formatHex(uint64(v))
|
||||
}
|
||||
|
||||
// pointerReferences is a stack of pointers visited so far.
|
||||
type pointerReferences [][2]value.Pointer
|
||||
|
||||
func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
|
||||
if deref && vx.IsValid() {
|
||||
vx = vx.Addr()
|
||||
}
|
||||
if deref && vy.IsValid() {
|
||||
vy = vy.Addr()
|
||||
}
|
||||
switch d {
|
||||
case diffUnknown, diffIdentical:
|
||||
pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
|
||||
case diffRemoved:
|
||||
pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
|
||||
case diffInserted:
|
||||
pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
|
||||
}
|
||||
*ps = append(*ps, pp)
|
||||
return pp
|
||||
}
|
||||
|
||||
func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
|
||||
p = value.PointerOf(v)
|
||||
for _, pp := range *ps {
|
||||
if p == pp[0] || p == pp[1] {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
*ps = append(*ps, [2]value.Pointer{p, p})
|
||||
return p, false
|
||||
}
|
||||
|
||||
func (ps *pointerReferences) Pop() {
|
||||
*ps = (*ps)[:len(*ps)-1]
|
||||
}
|
||||
|
||||
// trunkReferences is metadata for a textNode indicating that the sub-tree
|
||||
// represents the value for either pointer in a pair of references.
|
||||
type trunkReferences struct{ pp [2]value.Pointer }
|
||||
|
||||
// trunkReference is metadata for a textNode indicating that the sub-tree
|
||||
// represents the value for the given pointer reference.
|
||||
type trunkReference struct{ p value.Pointer }
|
||||
|
||||
// leafReference is metadata for a textNode indicating that the value is
|
||||
// truncated as it refers to another part of the tree (i.e., a trunk).
|
||||
type leafReference struct{ p value.Pointer }
|
||||
|
||||
func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
|
||||
switch {
|
||||
case pp[0].IsNil():
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
|
||||
case pp[1].IsNil():
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||
case pp[0] == pp[1]:
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||
default:
|
||||
return &textWrap{Value: s, Metadata: trunkReferences{pp}}
|
||||
}
|
||||
}
|
||||
func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
|
||||
var prefix string
|
||||
if printAddress {
|
||||
prefix = formatPointer(p, true)
|
||||
}
|
||||
return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
|
||||
}
|
||||
func makeLeafReference(p value.Pointer, printAddress bool) textNode {
|
||||
out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
|
||||
var prefix string
|
||||
if printAddress {
|
||||
prefix = formatPointer(p, true)
|
||||
}
|
||||
return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
|
||||
}
|
||||
|
||||
// resolveReferences walks the textNode tree searching for any leaf reference
|
||||
// metadata and resolves each against the corresponding trunk references.
|
||||
// Since pointer addresses in memory are not particularly readable to the user,
|
||||
// it replaces each pointer value with an arbitrary and unique reference ID.
|
||||
func resolveReferences(s textNode) {
|
||||
var walkNodes func(textNode, func(textNode))
|
||||
walkNodes = func(s textNode, f func(textNode)) {
|
||||
f(s)
|
||||
switch s := s.(type) {
|
||||
case *textWrap:
|
||||
walkNodes(s.Value, f)
|
||||
case textList:
|
||||
for _, r := range s {
|
||||
walkNodes(r.Value, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all trunks and leaves with reference metadata.
|
||||
var trunks, leaves []*textWrap
|
||||
walkNodes(s, func(s textNode) {
|
||||
if s, ok := s.(*textWrap); ok {
|
||||
switch s.Metadata.(type) {
|
||||
case leafReference:
|
||||
leaves = append(leaves, s)
|
||||
case trunkReference, trunkReferences:
|
||||
trunks = append(trunks, s)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// No leaf references to resolve.
|
||||
if len(leaves) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Collect the set of all leaf references to resolve.
|
||||
leafPtrs := make(map[value.Pointer]bool)
|
||||
for _, leaf := range leaves {
|
||||
leafPtrs[leaf.Metadata.(leafReference).p] = true
|
||||
}
|
||||
|
||||
// Collect the set of trunk pointers that are always paired together.
|
||||
// This allows us to assign a single ID to both pointers for brevity.
|
||||
// If a pointer in a pair ever occurs by itself or as a different pair,
|
||||
// then the pair is broken.
|
||||
pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
|
||||
unpair := func(p value.Pointer) {
|
||||
if !pairedTrunkPtrs[p].IsNil() {
|
||||
pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
|
||||
}
|
||||
pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
|
||||
}
|
||||
for _, trunk := range trunks {
|
||||
switch p := trunk.Metadata.(type) {
|
||||
case trunkReference:
|
||||
unpair(p.p) // standalone pointer cannot be part of a pair
|
||||
case trunkReferences:
|
||||
p0, ok0 := pairedTrunkPtrs[p.pp[0]]
|
||||
p1, ok1 := pairedTrunkPtrs[p.pp[1]]
|
||||
switch {
|
||||
case !ok0 && !ok1:
|
||||
// Register the newly seen pair.
|
||||
pairedTrunkPtrs[p.pp[0]] = p.pp[1]
|
||||
pairedTrunkPtrs[p.pp[1]] = p.pp[0]
|
||||
case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
|
||||
// Exact pair already seen; do nothing.
|
||||
default:
|
||||
// Pair conflicts with some other pair; break all pairs.
|
||||
unpair(p.pp[0])
|
||||
unpair(p.pp[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Correlate each pointer referenced by leaves to a unique identifier,
|
||||
// and print the IDs for each trunk that matches those pointers.
|
||||
var nextID uint
|
||||
ptrIDs := make(map[value.Pointer]uint)
|
||||
newID := func() uint {
|
||||
id := nextID
|
||||
nextID++
|
||||
return id
|
||||
}
|
||||
for _, trunk := range trunks {
|
||||
switch p := trunk.Metadata.(type) {
|
||||
case trunkReference:
|
||||
if print := leafPtrs[p.p]; print {
|
||||
id, ok := ptrIDs[p.p]
|
||||
if !ok {
|
||||
id = newID()
|
||||
ptrIDs[p.p] = id
|
||||
}
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||
}
|
||||
case trunkReferences:
|
||||
print0 := leafPtrs[p.pp[0]]
|
||||
print1 := leafPtrs[p.pp[1]]
|
||||
if print0 || print1 {
|
||||
id0, ok0 := ptrIDs[p.pp[0]]
|
||||
id1, ok1 := ptrIDs[p.pp[1]]
|
||||
isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
|
||||
if isPair {
|
||||
var id uint
|
||||
assert(ok0 == ok1) // must be seen together or not at all
|
||||
if ok0 {
|
||||
assert(id0 == id1) // must have the same ID
|
||||
id = id0
|
||||
} else {
|
||||
id = newID()
|
||||
ptrIDs[p.pp[0]] = id
|
||||
ptrIDs[p.pp[1]] = id
|
||||
}
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||
} else {
|
||||
if print0 && !ok0 {
|
||||
id0 = newID()
|
||||
ptrIDs[p.pp[0]] = id0
|
||||
}
|
||||
if print1 && !ok1 {
|
||||
id1 = newID()
|
||||
ptrIDs[p.pp[1]] = id1
|
||||
}
|
||||
switch {
|
||||
case print0 && print1:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
|
||||
case print0:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
|
||||
case print1:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update all leaf references with the unique identifier.
|
||||
for _, leaf := range leaves {
|
||||
if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
|
||||
leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatReference(id uint) string {
|
||||
return fmt.Sprintf("ref#%d", id)
|
||||
}
|
||||
|
||||
func updateReferencePrefix(prefix, ref string) string {
|
||||
if prefix == "" {
|
||||
return pointerDelimPrefix + ref + pointerDelimSuffix
|
||||
}
|
||||
suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
|
||||
return pointerDelimPrefix + ref + ": " + suffix
|
||||
}
|
||||
414
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
Normal file
414
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
// Copyright 2019, The 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 cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
var (
|
||||
anyType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
stringType = reflect.TypeOf((*string)(nil)).Elem()
|
||||
bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
|
||||
byteType = reflect.TypeOf((*byte)(nil)).Elem()
|
||||
)
|
||||
|
||||
type formatValueOptions struct {
|
||||
// AvoidStringer controls whether to avoid calling custom stringer
|
||||
// methods like error.Error or fmt.Stringer.String.
|
||||
AvoidStringer bool
|
||||
|
||||
// PrintAddresses controls whether to print the address of all pointers,
|
||||
// slice elements, and maps.
|
||||
PrintAddresses bool
|
||||
|
||||
// QualifiedNames controls whether FormatType uses the fully qualified name
|
||||
// (including the full package path as opposed to just the package name).
|
||||
QualifiedNames bool
|
||||
|
||||
// VerbosityLevel controls the amount of output to produce.
|
||||
// A higher value produces more output. A value of zero or lower produces
|
||||
// no output (represented using an ellipsis).
|
||||
// If LimitVerbosity is false, then the level is treated as infinite.
|
||||
VerbosityLevel int
|
||||
|
||||
// LimitVerbosity specifies that formatting should respect VerbosityLevel.
|
||||
LimitVerbosity bool
|
||||
}
|
||||
|
||||
// FormatType prints the type as if it were wrapping s.
|
||||
// This may return s as-is depending on the current type and TypeMode mode.
|
||||
func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
|
||||
// Check whether to emit the type or not.
|
||||
switch opts.TypeMode {
|
||||
case autoType:
|
||||
switch t.Kind() {
|
||||
case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
|
||||
if s.Equal(textNil) {
|
||||
return s
|
||||
}
|
||||
default:
|
||||
return s
|
||||
}
|
||||
if opts.DiffMode == diffIdentical {
|
||||
return s // elide type for identical nodes
|
||||
}
|
||||
case elideType:
|
||||
return s
|
||||
}
|
||||
|
||||
// Determine the type label, applying special handling for unnamed types.
|
||||
typeName := value.TypeString(t, opts.QualifiedNames)
|
||||
if t.Name() == "" {
|
||||
// According to Go grammar, certain type literals contain symbols that
|
||||
// do not strongly bind to the next lexicographical token (e.g., *T).
|
||||
switch t.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr:
|
||||
typeName = "(" + typeName + ")"
|
||||
}
|
||||
}
|
||||
return &textWrap{Prefix: typeName, Value: wrapParens(s)}
|
||||
}
|
||||
|
||||
// wrapParens wraps s with a set of parenthesis, but avoids it if the
|
||||
// wrapped node itself is already surrounded by a pair of parenthesis or braces.
|
||||
// It handles unwrapping one level of pointer-reference nodes.
|
||||
func wrapParens(s textNode) textNode {
|
||||
var refNode *textWrap
|
||||
if s2, ok := s.(*textWrap); ok {
|
||||
// Unwrap a single pointer reference node.
|
||||
switch s2.Metadata.(type) {
|
||||
case leafReference, trunkReference, trunkReferences:
|
||||
refNode = s2
|
||||
if s3, ok := refNode.Value.(*textWrap); ok {
|
||||
s2 = s3
|
||||
}
|
||||
}
|
||||
|
||||
// Already has delimiters that make parenthesis unnecessary.
|
||||
hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
|
||||
hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
|
||||
if hasParens || hasBraces {
|
||||
return s
|
||||
}
|
||||
}
|
||||
if refNode != nil {
|
||||
refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
|
||||
return s
|
||||
}
|
||||
return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
|
||||
}
|
||||
|
||||
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
||||
// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
|
||||
func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
// Check slice element for cycles.
|
||||
if parentKind == reflect.Slice {
|
||||
ptrRef, visited := ptrs.Push(v.Addr())
|
||||
if visited {
|
||||
return makeLeafReference(ptrRef, false)
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
|
||||
}
|
||||
|
||||
// Check whether there is an Error or String method to call.
|
||||
if !opts.AvoidStringer && v.CanInterface() {
|
||||
// Avoid calling Error or String methods on nil receivers since many
|
||||
// implementations crash when doing so.
|
||||
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
|
||||
var prefix, strVal string
|
||||
func() {
|
||||
// Swallow and ignore any panics from String or Error.
|
||||
defer func() { recover() }()
|
||||
switch v := v.Interface().(type) {
|
||||
case error:
|
||||
strVal = v.Error()
|
||||
prefix = "e"
|
||||
case fmt.Stringer:
|
||||
strVal = v.String()
|
||||
prefix = "s"
|
||||
}
|
||||
}()
|
||||
if prefix != "" {
|
||||
return opts.formatString(prefix, strVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether to explicitly wrap the result with the type.
|
||||
var skipType bool
|
||||
defer func() {
|
||||
if !skipType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
}()
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return textLine(fmt.Sprint(v.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return textLine(fmt.Sprint(v.Int()))
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return textLine(fmt.Sprint(v.Uint()))
|
||||
case reflect.Uint8:
|
||||
if parentKind == reflect.Slice || parentKind == reflect.Array {
|
||||
return textLine(formatHex(v.Uint()))
|
||||
}
|
||||
return textLine(fmt.Sprint(v.Uint()))
|
||||
case reflect.Uintptr:
|
||||
return textLine(formatHex(v.Uint()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return textLine(fmt.Sprint(v.Float()))
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return textLine(fmt.Sprint(v.Complex()))
|
||||
case reflect.String:
|
||||
return opts.formatString("", v.String())
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
return textLine(formatPointer(value.PointerOf(v), true))
|
||||
case reflect.Struct:
|
||||
var list textList
|
||||
v := makeAddressable(v) // needed for retrieveUnexportedField
|
||||
maxLen := v.NumField()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
vv := v.Field(i)
|
||||
if vv.IsZero() {
|
||||
continue // Elide fields with zero values
|
||||
}
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
sf := t.Field(i)
|
||||
if !isExported(sf.Name) {
|
||||
vv = retrieveUnexportedField(v, sf, true)
|
||||
}
|
||||
s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Key: sf.Name, Value: s})
|
||||
}
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
|
||||
// Check whether this is a []byte of text data.
|
||||
if t.Elem() == byteType {
|
||||
b := v.Bytes()
|
||||
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
|
||||
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
|
||||
out = opts.formatString("", string(b))
|
||||
skipType = true
|
||||
return opts.FormatType(t, out)
|
||||
}
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
maxLen := v.Len()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
var list textList
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Value: s})
|
||||
}
|
||||
|
||||
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
if t.Kind() == reflect.Slice && opts.PrintAddresses {
|
||||
header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
|
||||
out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
|
||||
}
|
||||
return out
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
|
||||
// Check pointer for cycles.
|
||||
ptrRef, visited := ptrs.Push(v)
|
||||
if visited {
|
||||
return makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
|
||||
maxLen := v.Len()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
var list textList
|
||||
for _, k := range value.SortKeys(v.MapKeys()) {
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
sk := formatMapKey(k, false, ptrs)
|
||||
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Key: sk, Value: sv})
|
||||
}
|
||||
|
||||
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||
return out
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
|
||||
// Check pointer for cycles.
|
||||
ptrRef, visited := ptrs.Push(v)
|
||||
if visited {
|
||||
out = makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||
return &textWrap{Prefix: "&", Value: out}
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
|
||||
// Skip the name only if this is an unnamed pointer type.
|
||||
// Otherwise taking the address of a value does not reproduce
|
||||
// the named pointer type.
|
||||
if v.Type().Name() == "" {
|
||||
skipType = true // Let the underlying value print the type instead
|
||||
}
|
||||
out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
|
||||
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||
out = &textWrap{Prefix: "&", Value: out}
|
||||
return out
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
// Interfaces accept different concrete types,
|
||||
// so configure the underlying value to explicitly print the type.
|
||||
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatString(prefix, s string) textNode {
|
||||
maxLen := len(s)
|
||||
maxLines := strings.Count(s, "\n") + 1
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...
|
||||
maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||
}
|
||||
|
||||
// For multiline strings, use the triple-quote syntax,
|
||||
// but only use it when printing removed or inserted nodes since
|
||||
// we only want the extra verbosity for those cases.
|
||||
lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
|
||||
isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
|
||||
for i := 0; i < len(lines) && isTripleQuoted; i++ {
|
||||
lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||
isPrintable := func(r rune) bool {
|
||||
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||
}
|
||||
line := lines[i]
|
||||
isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
|
||||
}
|
||||
if isTripleQuoted {
|
||||
var list textList
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||
for i, line := range lines {
|
||||
if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
|
||||
comment := commentString(fmt.Sprintf("%d elided lines", numElided))
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
|
||||
break
|
||||
}
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
|
||||
}
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||
return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
|
||||
}
|
||||
|
||||
// Format the string as a single-line quoted string.
|
||||
if len(s) > maxLen+len(textEllipsis) {
|
||||
return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
|
||||
}
|
||||
return textLine(prefix + formatString(s))
|
||||
}
|
||||
|
||||
// formatMapKey formats v as if it were a map key.
|
||||
// The result is guaranteed to be a single line.
|
||||
func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
|
||||
var opts formatOptions
|
||||
opts.DiffMode = diffIdentical
|
||||
opts.TypeMode = elideType
|
||||
opts.PrintAddresses = disambiguate
|
||||
opts.AvoidStringer = disambiguate
|
||||
opts.QualifiedNames = disambiguate
|
||||
opts.VerbosityLevel = maxVerbosityPreset
|
||||
opts.LimitVerbosity = true
|
||||
s := opts.FormatValue(v, reflect.Map, ptrs).String()
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
// formatString prints s as a double-quoted or backtick-quoted string.
|
||||
func formatString(s string) string {
|
||||
// Use quoted string if it the same length as a raw string literal.
|
||||
// Otherwise, attempt to use the raw string form.
|
||||
qs := strconv.Quote(s)
|
||||
if len(qs) == 1+len(s)+1 {
|
||||
return qs
|
||||
}
|
||||
|
||||
// Disallow newlines to ensure output is a single line.
|
||||
// Only allow printable runes for readability purposes.
|
||||
rawInvalid := func(r rune) bool {
|
||||
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
|
||||
}
|
||||
if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
return qs
|
||||
}
|
||||
|
||||
// formatHex prints u as a hexadecimal integer in Go notation.
|
||||
func formatHex(u uint64) string {
|
||||
var f string
|
||||
switch {
|
||||
case u <= 0xff:
|
||||
f = "0x%02x"
|
||||
case u <= 0xffff:
|
||||
f = "0x%04x"
|
||||
case u <= 0xffffff:
|
||||
f = "0x%06x"
|
||||
case u <= 0xffffffff:
|
||||
f = "0x%08x"
|
||||
case u <= 0xffffffffff:
|
||||
f = "0x%010x"
|
||||
case u <= 0xffffffffffff:
|
||||
f = "0x%012x"
|
||||
case u <= 0xffffffffffffff:
|
||||
f = "0x%014x"
|
||||
case u <= 0xffffffffffffffff:
|
||||
f = "0x%016x"
|
||||
}
|
||||
return fmt.Sprintf(f, u)
|
||||
}
|
||||
614
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
Normal file
614
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
Normal file
@@ -0,0 +1,614 @@
|
||||
// Copyright 2019, The 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 cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/diff"
|
||||
)
|
||||
|
||||
// CanFormatDiffSlice reports whether we support custom formatting for nodes
|
||||
// that are slices of primitive kinds or strings.
|
||||
func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
|
||||
switch {
|
||||
case opts.DiffMode != diffUnknown:
|
||||
return false // Must be formatting in diff mode
|
||||
case v.NumDiff == 0:
|
||||
return false // No differences detected
|
||||
case !v.ValueX.IsValid() || !v.ValueY.IsValid():
|
||||
return false // Both values must be valid
|
||||
case v.NumIgnored > 0:
|
||||
return false // Some ignore option was used
|
||||
case v.NumTransformed > 0:
|
||||
return false // Some transform option was used
|
||||
case v.NumCompared > 1:
|
||||
return false // More than one comparison was used
|
||||
case v.NumCompared == 1 && v.Type.Name() != "":
|
||||
// The need for cmp to check applicability of options on every element
|
||||
// in a slice is a significant performance detriment for large []byte.
|
||||
// The workaround is to specify Comparer(bytes.Equal),
|
||||
// which enables cmp to compare []byte more efficiently.
|
||||
// If they differ, we still want to provide batched diffing.
|
||||
// The logic disallows named types since they tend to have their own
|
||||
// String method, with nicer formatting than what this provides.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether this is an interface with the same concrete types.
|
||||
t := v.Type
|
||||
vx, vy := v.ValueX, v.ValueY
|
||||
if t.Kind() == reflect.Interface && !vx.IsNil() && !vy.IsNil() && vx.Elem().Type() == vy.Elem().Type() {
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
t = vx.Type()
|
||||
}
|
||||
|
||||
// Check whether we provide specialized diffing for this type.
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
case reflect.Array, reflect.Slice:
|
||||
// Only slices of primitive types have specialized handling.
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
// Both slice values have to be non-empty.
|
||||
if t.Kind() == reflect.Slice && (vx.Len() == 0 || vy.Len() == 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If a sufficient number of elements already differ,
|
||||
// use specialized formatting even if length requirement is not met.
|
||||
if v.NumDiff > v.NumSame {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
// Use specialized string diffing for longer slices or strings.
|
||||
const minLength = 32
|
||||
return vx.Len() >= minLength && vy.Len() >= minLength
|
||||
}
|
||||
|
||||
// FormatDiffSlice prints a diff for the slices (or strings) represented by v.
|
||||
// This provides custom-tailored logic to make printing of differences in
|
||||
// textual strings and slices of primitive kinds more readable.
|
||||
func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
||||
assert(opts.DiffMode == diffUnknown)
|
||||
t, vx, vy := v.Type, v.ValueX, v.ValueY
|
||||
if t.Kind() == reflect.Interface {
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
t = vx.Type()
|
||||
opts = opts.WithTypeMode(emitType)
|
||||
}
|
||||
|
||||
// Auto-detect the type of the data.
|
||||
var sx, sy string
|
||||
var ssx, ssy []string
|
||||
var isString, isMostlyText, isPureLinedText, isBinary bool
|
||||
switch {
|
||||
case t.Kind() == reflect.String:
|
||||
sx, sy = vx.String(), vy.String()
|
||||
isString = true
|
||||
case t.Kind() == reflect.Slice && t.Elem() == byteType:
|
||||
sx, sy = string(vx.Bytes()), string(vy.Bytes())
|
||||
isString = true
|
||||
case t.Kind() == reflect.Array:
|
||||
// Arrays need to be addressable for slice operations to work.
|
||||
vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem()
|
||||
vx2.Set(vx)
|
||||
vy2.Set(vy)
|
||||
vx, vy = vx2, vy2
|
||||
}
|
||||
if isString {
|
||||
var numTotalRunes, numValidRunes, numLines, lastLineIdx, maxLineLen int
|
||||
for i, r := range sx + sy {
|
||||
numTotalRunes++
|
||||
if (unicode.IsPrint(r) || unicode.IsSpace(r)) && r != utf8.RuneError {
|
||||
numValidRunes++
|
||||
}
|
||||
if r == '\n' {
|
||||
if maxLineLen < i-lastLineIdx {
|
||||
maxLineLen = i - lastLineIdx
|
||||
}
|
||||
lastLineIdx = i + 1
|
||||
numLines++
|
||||
}
|
||||
}
|
||||
isPureText := numValidRunes == numTotalRunes
|
||||
isMostlyText = float64(numValidRunes) > math.Floor(0.90*float64(numTotalRunes))
|
||||
isPureLinedText = isPureText && numLines >= 4 && maxLineLen <= 1024
|
||||
isBinary = !isMostlyText
|
||||
|
||||
// Avoid diffing by lines if it produces a significantly more complex
|
||||
// edit script than diffing by bytes.
|
||||
if isPureLinedText {
|
||||
ssx = strings.Split(sx, "\n")
|
||||
ssy = strings.Split(sy, "\n")
|
||||
esLines := diff.Difference(len(ssx), len(ssy), func(ix, iy int) diff.Result {
|
||||
return diff.BoolResult(ssx[ix] == ssy[iy])
|
||||
})
|
||||
esBytes := diff.Difference(len(sx), len(sy), func(ix, iy int) diff.Result {
|
||||
return diff.BoolResult(sx[ix] == sy[iy])
|
||||
})
|
||||
efficiencyLines := float64(esLines.Dist()) / float64(len(esLines))
|
||||
efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes))
|
||||
quotedLength := len(strconv.Quote(sx + sy))
|
||||
unquotedLength := len(sx) + len(sy)
|
||||
escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength)
|
||||
isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1
|
||||
}
|
||||
}
|
||||
|
||||
// Format the string into printable records.
|
||||
var list textList
|
||||
var delim string
|
||||
switch {
|
||||
// If the text appears to be multi-lined text,
|
||||
// then perform differencing across individual lines.
|
||||
case isPureLinedText:
|
||||
list = opts.formatDiffSlice(
|
||||
reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line",
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
s := formatString(v.Index(0).String())
|
||||
return textRecord{Diff: d, Value: textLine(s)}
|
||||
},
|
||||
)
|
||||
delim = "\n"
|
||||
|
||||
// If possible, use a custom triple-quote (""") syntax for printing
|
||||
// differences in a string literal. This format is more readable,
|
||||
// but has edge-cases where differences are visually indistinguishable.
|
||||
// This format is avoided under the following conditions:
|
||||
// - A line starts with `"""`
|
||||
// - A line starts with "..."
|
||||
// - A line contains non-printable characters
|
||||
// - Adjacent different lines differ only by whitespace
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// """
|
||||
// ... // 3 identical lines
|
||||
// foo
|
||||
// bar
|
||||
// - baz
|
||||
// + BAZ
|
||||
// """
|
||||
isTripleQuoted := true
|
||||
prevRemoveLines := map[string]bool{}
|
||||
prevInsertLines := map[string]bool{}
|
||||
var list2 textList
|
||||
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||
for _, r := range list {
|
||||
if !r.Value.Equal(textEllipsis) {
|
||||
line, _ := strconv.Unquote(string(r.Value.(textLine)))
|
||||
line = strings.TrimPrefix(strings.TrimSuffix(line, "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||
normLine := strings.Map(func(r rune) rune {
|
||||
if unicode.IsSpace(r) {
|
||||
return -1 // drop whitespace to avoid visually indistinguishable output
|
||||
}
|
||||
return r
|
||||
}, line)
|
||||
isPrintable := func(r rune) bool {
|
||||
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||
}
|
||||
isTripleQuoted = !strings.HasPrefix(line, `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == ""
|
||||
switch r.Diff {
|
||||
case diffRemoved:
|
||||
isTripleQuoted = isTripleQuoted && !prevInsertLines[normLine]
|
||||
prevRemoveLines[normLine] = true
|
||||
case diffInserted:
|
||||
isTripleQuoted = isTripleQuoted && !prevRemoveLines[normLine]
|
||||
prevInsertLines[normLine] = true
|
||||
}
|
||||
if !isTripleQuoted {
|
||||
break
|
||||
}
|
||||
r.Value = textLine(line)
|
||||
r.ElideComma = true
|
||||
}
|
||||
if !(r.Diff == diffRemoved || r.Diff == diffInserted) { // start a new non-adjacent difference group
|
||||
prevRemoveLines = map[string]bool{}
|
||||
prevInsertLines = map[string]bool{}
|
||||
}
|
||||
list2 = append(list2, r)
|
||||
}
|
||||
if r := list2[len(list2)-1]; r.Diff == diffIdentical && len(r.Value.(textLine)) == 0 {
|
||||
list2 = list2[:len(list2)-1] // elide single empty line at the end
|
||||
}
|
||||
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||
if isTripleQuoted {
|
||||
var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
if t != stringType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
case reflect.Slice:
|
||||
// Always emit type for slices since the triple-quote syntax
|
||||
// looks like a string (not a slice).
|
||||
opts = opts.WithTypeMode(emitType)
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// If the text appears to be single-lined text,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The output is printed as quoted strings.
|
||||
case isMostlyText:
|
||||
list = opts.formatDiffSlice(
|
||||
reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte",
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
s := formatString(v.String())
|
||||
return textRecord{Diff: d, Value: textLine(s)}
|
||||
},
|
||||
)
|
||||
|
||||
// If the text appears to be binary data,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The output is inspired by hexdump.
|
||||
case isBinary:
|
||||
list = opts.formatDiffSlice(
|
||||
reflect.ValueOf(sx), reflect.ValueOf(sy), 16, "byte",
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
var ss []string
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||
}
|
||||
s := strings.Join(ss, ", ")
|
||||
comment := commentString(fmt.Sprintf("%c|%v|", d, formatASCII(v.String())))
|
||||
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
|
||||
},
|
||||
)
|
||||
|
||||
// For all other slices of primitive types,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The size of each chunk depends on the width of the element kind.
|
||||
default:
|
||||
var chunkSize int
|
||||
if t.Elem().Kind() == reflect.Bool {
|
||||
chunkSize = 16
|
||||
} else {
|
||||
switch t.Elem().Bits() {
|
||||
case 8:
|
||||
chunkSize = 16
|
||||
case 16:
|
||||
chunkSize = 12
|
||||
case 32:
|
||||
chunkSize = 8
|
||||
default:
|
||||
chunkSize = 8
|
||||
}
|
||||
}
|
||||
list = opts.formatDiffSlice(
|
||||
vx, vy, chunkSize, t.Elem().Kind().String(),
|
||||
func(v reflect.Value, d diffMode) textRecord {
|
||||
var ss []string
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Int()))
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Uint()))
|
||||
case reflect.Uint8, reflect.Uintptr:
|
||||
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Interface()))
|
||||
}
|
||||
}
|
||||
s := strings.Join(ss, ", ")
|
||||
return textRecord{Diff: d, Value: textLine(s)}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Wrap the output with appropriate type information.
|
||||
var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
if !isMostlyText {
|
||||
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
|
||||
// Emit the type for extra clarity (e.g. "string{...}").
|
||||
if t.Kind() == reflect.String {
|
||||
opts = opts.WithTypeMode(emitType)
|
||||
}
|
||||
return opts.FormatType(t, out)
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||
if t != stringType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
case reflect.Slice:
|
||||
out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||
if t != bytesType {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// formatASCII formats s as an ASCII string.
|
||||
// This is useful for printing binary strings in a semi-legible way.
|
||||
func formatASCII(s string) string {
|
||||
b := bytes.Repeat([]byte{'.'}, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
if ' ' <= s[i] && s[i] <= '~' {
|
||||
b[i] = s[i]
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatDiffSlice(
|
||||
vx, vy reflect.Value, chunkSize int, name string,
|
||||
makeRec func(reflect.Value, diffMode) textRecord,
|
||||
) (list textList) {
|
||||
eq := func(ix, iy int) bool {
|
||||
return vx.Index(ix).Interface() == vy.Index(iy).Interface()
|
||||
}
|
||||
es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
|
||||
return diff.BoolResult(eq(ix, iy))
|
||||
})
|
||||
|
||||
appendChunks := func(v reflect.Value, d diffMode) int {
|
||||
n0 := v.Len()
|
||||
for v.Len() > 0 {
|
||||
n := chunkSize
|
||||
if n > v.Len() {
|
||||
n = v.Len()
|
||||
}
|
||||
list = append(list, makeRec(v.Slice(0, n), d))
|
||||
v = v.Slice(n, v.Len())
|
||||
}
|
||||
return n0 - v.Len()
|
||||
}
|
||||
|
||||
var numDiffs int
|
||||
maxLen := -1
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
|
||||
groups := coalesceAdjacentEdits(name, es)
|
||||
groups = coalesceInterveningIdentical(groups, chunkSize/4)
|
||||
groups = cleanupSurroundingIdentical(groups, eq)
|
||||
maxGroup := diffStats{Name: name}
|
||||
for i, ds := range groups {
|
||||
if maxLen >= 0 && numDiffs >= maxLen {
|
||||
maxGroup = maxGroup.Append(ds)
|
||||
continue
|
||||
}
|
||||
|
||||
// Print equal.
|
||||
if ds.NumDiff() == 0 {
|
||||
// Compute the number of leading and trailing equal bytes to print.
|
||||
var numLo, numHi int
|
||||
numEqual := ds.NumIgnored + ds.NumIdentical
|
||||
for numLo < chunkSize*numContextRecords && numLo+numHi < numEqual && i != 0 {
|
||||
numLo++
|
||||
}
|
||||
for numHi < chunkSize*numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
|
||||
numHi++
|
||||
}
|
||||
if numEqual-(numLo+numHi) <= chunkSize && ds.NumIgnored == 0 {
|
||||
numHi = numEqual - numLo // Avoid pointless coalescing of single equal row
|
||||
}
|
||||
|
||||
// Print the equal bytes.
|
||||
appendChunks(vx.Slice(0, numLo), diffIdentical)
|
||||
if numEqual > numLo+numHi {
|
||||
ds.NumIdentical -= numLo + numHi
|
||||
list.AppendEllipsis(ds)
|
||||
}
|
||||
appendChunks(vx.Slice(numEqual-numHi, numEqual), diffIdentical)
|
||||
vx = vx.Slice(numEqual, vx.Len())
|
||||
vy = vy.Slice(numEqual, vy.Len())
|
||||
continue
|
||||
}
|
||||
|
||||
// Print unequal.
|
||||
len0 := len(list)
|
||||
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
|
||||
vx = vx.Slice(nx, vx.Len())
|
||||
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
|
||||
vy = vy.Slice(ny, vy.Len())
|
||||
numDiffs += len(list) - len0
|
||||
}
|
||||
if maxGroup.IsZero() {
|
||||
assert(vx.Len() == 0 && vy.Len() == 0)
|
||||
} else {
|
||||
list.AppendEllipsis(maxGroup)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
|
||||
// equal or unequal counts.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Input: "..XXY...Y"
|
||||
// Output: [
|
||||
// {NumIdentical: 2},
|
||||
// {NumRemoved: 2, NumInserted 1},
|
||||
// {NumIdentical: 3},
|
||||
// {NumInserted: 1},
|
||||
// ]
|
||||
func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) {
|
||||
var prevMode byte
|
||||
lastStats := func(mode byte) *diffStats {
|
||||
if prevMode != mode {
|
||||
groups = append(groups, diffStats{Name: name})
|
||||
prevMode = mode
|
||||
}
|
||||
return &groups[len(groups)-1]
|
||||
}
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case diff.Identity:
|
||||
lastStats('=').NumIdentical++
|
||||
case diff.UniqueX:
|
||||
lastStats('!').NumRemoved++
|
||||
case diff.UniqueY:
|
||||
lastStats('!').NumInserted++
|
||||
case diff.Modified:
|
||||
lastStats('!').NumModified++
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// coalesceInterveningIdentical coalesces sufficiently short (<= windowSize)
|
||||
// equal groups into adjacent unequal groups that currently result in a
|
||||
// dual inserted/removed printout. This acts as a high-pass filter to smooth
|
||||
// out high-frequency changes within the windowSize.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// WindowSize: 16,
|
||||
// Input: [
|
||||
// {NumIdentical: 61}, // group 0
|
||||
// {NumRemoved: 3, NumInserted: 1}, // group 1
|
||||
// {NumIdentical: 6}, // ├── coalesce
|
||||
// {NumInserted: 2}, // ├── coalesce
|
||||
// {NumIdentical: 1}, // ├── coalesce
|
||||
// {NumRemoved: 9}, // └── coalesce
|
||||
// {NumIdentical: 64}, // group 2
|
||||
// {NumRemoved: 3, NumInserted: 1}, // group 3
|
||||
// {NumIdentical: 6}, // ├── coalesce
|
||||
// {NumInserted: 2}, // ├── coalesce
|
||||
// {NumIdentical: 1}, // ├── coalesce
|
||||
// {NumRemoved: 7}, // ├── coalesce
|
||||
// {NumIdentical: 1}, // ├── coalesce
|
||||
// {NumRemoved: 2}, // └── coalesce
|
||||
// {NumIdentical: 63}, // group 4
|
||||
// ]
|
||||
// Output: [
|
||||
// {NumIdentical: 61},
|
||||
// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3},
|
||||
// {NumIdentical: 64},
|
||||
// {NumIdentical: 8, NumRemoved: 12, NumInserted: 3},
|
||||
// {NumIdentical: 63},
|
||||
// ]
|
||||
func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats {
|
||||
groups, groupsOrig := groups[:0], groups
|
||||
for i, ds := range groupsOrig {
|
||||
if len(groups) >= 2 && ds.NumDiff() > 0 {
|
||||
prev := &groups[len(groups)-2] // Unequal group
|
||||
curr := &groups[len(groups)-1] // Equal group
|
||||
next := &groupsOrig[i] // Unequal group
|
||||
hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0
|
||||
hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0
|
||||
if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize {
|
||||
*prev = prev.Append(*curr).Append(*next)
|
||||
groups = groups[:len(groups)-1] // Truncate off equal group
|
||||
continue
|
||||
}
|
||||
}
|
||||
groups = append(groups, ds)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// cleanupSurroundingIdentical scans through all unequal groups, and
|
||||
// moves any leading sequence of equal elements to the preceding equal group and
|
||||
// moves and trailing sequence of equal elements to the succeeding equal group.
|
||||
//
|
||||
// This is necessary since coalesceInterveningIdentical may coalesce edit groups
|
||||
// together such that leading/trailing spans of equal elements becomes possible.
|
||||
// Note that this can occur even with an optimal diffing algorithm.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Input: [
|
||||
// {NumIdentical: 61},
|
||||
// {NumIdentical: 1 , NumRemoved: 11, NumInserted: 2}, // assume 3 leading identical elements
|
||||
// {NumIdentical: 67},
|
||||
// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, // assume 10 trailing identical elements
|
||||
// {NumIdentical: 54},
|
||||
// ]
|
||||
// Output: [
|
||||
// {NumIdentical: 64}, // incremented by 3
|
||||
// {NumRemoved: 9},
|
||||
// {NumIdentical: 67},
|
||||
// {NumRemoved: 9},
|
||||
// {NumIdentical: 64}, // incremented by 10
|
||||
// ]
|
||||
func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats {
|
||||
var ix, iy int // indexes into sequence x and y
|
||||
for i, ds := range groups {
|
||||
// Handle equal group.
|
||||
if ds.NumDiff() == 0 {
|
||||
ix += ds.NumIdentical
|
||||
iy += ds.NumIdentical
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle unequal group.
|
||||
nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified
|
||||
ny := ds.NumIdentical + ds.NumInserted + ds.NumModified
|
||||
var numLeadingIdentical, numTrailingIdentical int
|
||||
for j := 0; j < nx && j < ny && eq(ix+j, iy+j); j++ {
|
||||
numLeadingIdentical++
|
||||
}
|
||||
for j := 0; j < nx && j < ny && eq(ix+nx-1-j, iy+ny-1-j); j++ {
|
||||
numTrailingIdentical++
|
||||
}
|
||||
if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 {
|
||||
if numLeadingIdentical > 0 {
|
||||
// Remove leading identical span from this group and
|
||||
// insert it into the preceding group.
|
||||
if i-1 >= 0 {
|
||||
groups[i-1].NumIdentical += numLeadingIdentical
|
||||
} else {
|
||||
// No preceding group exists, so prepend a new group,
|
||||
// but do so after we finish iterating over all groups.
|
||||
defer func() {
|
||||
groups = append([]diffStats{{Name: groups[0].Name, NumIdentical: numLeadingIdentical}}, groups...)
|
||||
}()
|
||||
}
|
||||
// Increment indexes since the preceding group would have handled this.
|
||||
ix += numLeadingIdentical
|
||||
iy += numLeadingIdentical
|
||||
}
|
||||
if numTrailingIdentical > 0 {
|
||||
// Remove trailing identical span from this group and
|
||||
// insert it into the succeeding group.
|
||||
if i+1 < len(groups) {
|
||||
groups[i+1].NumIdentical += numTrailingIdentical
|
||||
} else {
|
||||
// No succeeding group exists, so append a new group,
|
||||
// but do so after we finish iterating over all groups.
|
||||
defer func() {
|
||||
groups = append(groups, diffStats{Name: groups[len(groups)-1].Name, NumIdentical: numTrailingIdentical})
|
||||
}()
|
||||
}
|
||||
// Do not increment indexes since the succeeding group will handle this.
|
||||
}
|
||||
|
||||
// Update this group since some identical elements were removed.
|
||||
nx -= numIdentical
|
||||
ny -= numIdentical
|
||||
groups[i] = diffStats{Name: ds.Name, NumRemoved: nx, NumInserted: ny}
|
||||
}
|
||||
ix += nx
|
||||
iy += ny
|
||||
}
|
||||
return groups
|
||||
}
|
||||
432
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
Normal file
432
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
// Copyright 2019, The 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 cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
)
|
||||
|
||||
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||
|
||||
const maxColumnLength = 80
|
||||
|
||||
type indentMode int
|
||||
|
||||
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
|
||||
// The output of Diff is documented as being unstable to provide future
|
||||
// flexibility in changing the output for more humanly readable reports.
|
||||
// This logic intentionally introduces instability to the exact output
|
||||
// so that users can detect accidental reliance on stability early on,
|
||||
// rather than much later when an actual change to the format occurs.
|
||||
if flags.Deterministic || randBool {
|
||||
// Use regular spaces (U+0020).
|
||||
switch d {
|
||||
case diffUnknown, diffIdentical:
|
||||
b = append(b, " "...)
|
||||
case diffRemoved:
|
||||
b = append(b, "- "...)
|
||||
case diffInserted:
|
||||
b = append(b, "+ "...)
|
||||
}
|
||||
} else {
|
||||
// Use non-breaking spaces (U+00a0).
|
||||
switch d {
|
||||
case diffUnknown, diffIdentical:
|
||||
b = append(b, " "...)
|
||||
case diffRemoved:
|
||||
b = append(b, "- "...)
|
||||
case diffInserted:
|
||||
b = append(b, "+ "...)
|
||||
}
|
||||
}
|
||||
return repeatCount(n).appendChar(b, '\t')
|
||||
}
|
||||
|
||||
type repeatCount int
|
||||
|
||||
func (n repeatCount) appendChar(b []byte, c byte) []byte {
|
||||
for ; n > 0; n-- {
|
||||
b = append(b, c)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// textNode is a simplified tree-based representation of structured text.
|
||||
// Possible node types are textWrap, textList, or textLine.
|
||||
type textNode interface {
|
||||
// Len reports the length in bytes of a single-line version of the tree.
|
||||
// Nested textRecord.Diff and textRecord.Comment fields are ignored.
|
||||
Len() int
|
||||
// Equal reports whether the two trees are structurally identical.
|
||||
// Nested textRecord.Diff and textRecord.Comment fields are compared.
|
||||
Equal(textNode) bool
|
||||
// String returns the string representation of the text tree.
|
||||
// It is not guaranteed that len(x.String()) == x.Len(),
|
||||
// nor that x.String() == y.String() implies that x.Equal(y).
|
||||
String() string
|
||||
|
||||
// formatCompactTo formats the contents of the tree as a single-line string
|
||||
// to the provided buffer. Any nested textRecord.Diff and textRecord.Comment
|
||||
// fields are ignored.
|
||||
//
|
||||
// However, not all nodes in the tree should be collapsed as a single-line.
|
||||
// If a node can be collapsed as a single-line, it is replaced by a textLine
|
||||
// node. Since the top-level node cannot replace itself, this also returns
|
||||
// the current node itself.
|
||||
//
|
||||
// This does not mutate the receiver.
|
||||
formatCompactTo([]byte, diffMode) ([]byte, textNode)
|
||||
// formatExpandedTo formats the contents of the tree as a multi-line string
|
||||
// to the provided buffer. In order for column alignment to operate well,
|
||||
// formatCompactTo must be called before calling formatExpandedTo.
|
||||
formatExpandedTo([]byte, diffMode, indentMode) []byte
|
||||
}
|
||||
|
||||
// textWrap is a wrapper that concatenates a prefix and/or a suffix
|
||||
// to the underlying node.
|
||||
type textWrap struct {
|
||||
Prefix string // e.g., "bytes.Buffer{"
|
||||
Value textNode // textWrap | textList | textLine
|
||||
Suffix string // e.g., "}"
|
||||
Metadata interface{} // arbitrary metadata; has no effect on formatting
|
||||
}
|
||||
|
||||
func (s *textWrap) Len() int {
|
||||
return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
|
||||
}
|
||||
func (s1 *textWrap) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(*textWrap); ok {
|
||||
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (s *textWrap) String() string {
|
||||
var d diffMode
|
||||
var n indentMode
|
||||
_, s2 := s.formatCompactTo(nil, d)
|
||||
b := n.appendIndent(nil, d) // Leading indent
|
||||
b = s2.formatExpandedTo(b, d, n) // Main body
|
||||
b = append(b, '\n') // Trailing newline
|
||||
return string(b)
|
||||
}
|
||||
func (s *textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
n0 := len(b) // Original buffer length
|
||||
b = append(b, s.Prefix...)
|
||||
b, s.Value = s.Value.formatCompactTo(b, d)
|
||||
b = append(b, s.Suffix...)
|
||||
if _, ok := s.Value.(textLine); ok {
|
||||
return b, textLine(b[n0:])
|
||||
}
|
||||
return b, s
|
||||
}
|
||||
func (s *textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||
b = append(b, s.Prefix...)
|
||||
b = s.Value.formatExpandedTo(b, d, n)
|
||||
b = append(b, s.Suffix...)
|
||||
return b
|
||||
}
|
||||
|
||||
// textList is a comma-separated list of textWrap or textLine nodes.
|
||||
// The list may be formatted as multi-lines or single-line at the discretion
|
||||
// of the textList.formatCompactTo method.
|
||||
type textList []textRecord
|
||||
type textRecord struct {
|
||||
Diff diffMode // e.g., 0 or '-' or '+'
|
||||
Key string // e.g., "MyField"
|
||||
Value textNode // textWrap | textLine
|
||||
ElideComma bool // avoid trailing comma
|
||||
Comment fmt.Stringer // e.g., "6 identical fields"
|
||||
}
|
||||
|
||||
// AppendEllipsis appends a new ellipsis node to the list if none already
|
||||
// exists at the end. If cs is non-zero it coalesces the statistics with the
|
||||
// previous diffStats.
|
||||
func (s *textList) AppendEllipsis(ds diffStats) {
|
||||
hasStats := !ds.IsZero()
|
||||
if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
|
||||
if hasStats {
|
||||
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds})
|
||||
} else {
|
||||
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true})
|
||||
}
|
||||
return
|
||||
}
|
||||
if hasStats {
|
||||
(*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds)
|
||||
}
|
||||
}
|
||||
|
||||
func (s textList) Len() (n int) {
|
||||
for i, r := range s {
|
||||
n += len(r.Key)
|
||||
if r.Key != "" {
|
||||
n += len(": ")
|
||||
}
|
||||
n += r.Value.Len()
|
||||
if i < len(s)-1 {
|
||||
n += len(", ")
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (s1 textList) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(textList); ok {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for i := range s1 {
|
||||
r1, r2 := s1[i], s2[i]
|
||||
if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s textList) String() string {
|
||||
return (&textWrap{Prefix: "{", Value: s, Suffix: "}"}).String()
|
||||
}
|
||||
|
||||
func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
s = append(textList(nil), s...) // Avoid mutating original
|
||||
|
||||
// Determine whether we can collapse this list as a single line.
|
||||
n0 := len(b) // Original buffer length
|
||||
var multiLine bool
|
||||
for i, r := range s {
|
||||
if r.Diff == diffInserted || r.Diff == diffRemoved {
|
||||
multiLine = true
|
||||
}
|
||||
b = append(b, r.Key...)
|
||||
if r.Key != "" {
|
||||
b = append(b, ": "...)
|
||||
}
|
||||
b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff)
|
||||
if _, ok := s[i].Value.(textLine); !ok {
|
||||
multiLine = true
|
||||
}
|
||||
if r.Comment != nil {
|
||||
multiLine = true
|
||||
}
|
||||
if i < len(s)-1 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
}
|
||||
// Force multi-lined output when printing a removed/inserted node that
|
||||
// is sufficiently long.
|
||||
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength {
|
||||
multiLine = true
|
||||
}
|
||||
if !multiLine {
|
||||
return b, textLine(b[n0:])
|
||||
}
|
||||
return b, s
|
||||
}
|
||||
|
||||
func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||
alignKeyLens := s.alignLens(
|
||||
func(r textRecord) bool {
|
||||
_, isLine := r.Value.(textLine)
|
||||
return r.Key == "" || !isLine
|
||||
},
|
||||
func(r textRecord) int { return utf8.RuneCountInString(r.Key) },
|
||||
)
|
||||
alignValueLens := s.alignLens(
|
||||
func(r textRecord) bool {
|
||||
_, isLine := r.Value.(textLine)
|
||||
return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
|
||||
},
|
||||
func(r textRecord) int { return utf8.RuneCount(r.Value.(textLine)) },
|
||||
)
|
||||
|
||||
// Format lists of simple lists in a batched form.
|
||||
// If the list is sequence of only textLine values,
|
||||
// then batch multiple values on a single line.
|
||||
var isSimple bool
|
||||
for _, r := range s {
|
||||
_, isLine := r.Value.(textLine)
|
||||
isSimple = r.Diff == 0 && r.Key == "" && isLine && r.Comment == nil
|
||||
if !isSimple {
|
||||
break
|
||||
}
|
||||
}
|
||||
if isSimple {
|
||||
n++
|
||||
var batch []byte
|
||||
emitBatch := func() {
|
||||
if len(batch) > 0 {
|
||||
b = n.appendIndent(append(b, '\n'), d)
|
||||
b = append(b, bytes.TrimRight(batch, " ")...)
|
||||
batch = batch[:0]
|
||||
}
|
||||
}
|
||||
for _, r := range s {
|
||||
line := r.Value.(textLine)
|
||||
if len(batch)+len(line)+len(", ") > maxColumnLength {
|
||||
emitBatch()
|
||||
}
|
||||
batch = append(batch, line...)
|
||||
batch = append(batch, ", "...)
|
||||
}
|
||||
emitBatch()
|
||||
n--
|
||||
return n.appendIndent(append(b, '\n'), d)
|
||||
}
|
||||
|
||||
// Format the list as a multi-lined output.
|
||||
n++
|
||||
for i, r := range s {
|
||||
b = n.appendIndent(append(b, '\n'), d|r.Diff)
|
||||
if r.Key != "" {
|
||||
b = append(b, r.Key+": "...)
|
||||
}
|
||||
b = alignKeyLens[i].appendChar(b, ' ')
|
||||
|
||||
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
|
||||
if !r.ElideComma {
|
||||
b = append(b, ',')
|
||||
}
|
||||
b = alignValueLens[i].appendChar(b, ' ')
|
||||
|
||||
if r.Comment != nil {
|
||||
b = append(b, " // "+r.Comment.String()...)
|
||||
}
|
||||
}
|
||||
n--
|
||||
|
||||
return n.appendIndent(append(b, '\n'), d)
|
||||
}
|
||||
|
||||
func (s textList) alignLens(
|
||||
skipFunc func(textRecord) bool,
|
||||
lenFunc func(textRecord) int,
|
||||
) []repeatCount {
|
||||
var startIdx, endIdx, maxLen int
|
||||
lens := make([]repeatCount, len(s))
|
||||
for i, r := range s {
|
||||
if skipFunc(r) {
|
||||
for j := startIdx; j < endIdx && j < len(s); j++ {
|
||||
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
|
||||
}
|
||||
startIdx, endIdx, maxLen = i+1, i+1, 0
|
||||
} else {
|
||||
if maxLen < lenFunc(r) {
|
||||
maxLen = lenFunc(r)
|
||||
}
|
||||
endIdx = i + 1
|
||||
}
|
||||
}
|
||||
for j := startIdx; j < endIdx && j < len(s); j++ {
|
||||
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
|
||||
}
|
||||
return lens
|
||||
}
|
||||
|
||||
// textLine is a single-line segment of text and is always a leaf node
|
||||
// in the textNode tree.
|
||||
type textLine []byte
|
||||
|
||||
var (
|
||||
textNil = textLine("nil")
|
||||
textEllipsis = textLine("...")
|
||||
)
|
||||
|
||||
func (s textLine) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s1 textLine) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(textLine); ok {
|
||||
return bytes.Equal([]byte(s1), []byte(s2))
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (s textLine) String() string {
|
||||
return string(s)
|
||||
}
|
||||
func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
return append(b, s...), s
|
||||
}
|
||||
func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte {
|
||||
return append(b, s...)
|
||||
}
|
||||
|
||||
type diffStats struct {
|
||||
Name string
|
||||
NumIgnored int
|
||||
NumIdentical int
|
||||
NumRemoved int
|
||||
NumInserted int
|
||||
NumModified int
|
||||
}
|
||||
|
||||
func (s diffStats) IsZero() bool {
|
||||
s.Name = ""
|
||||
return s == diffStats{}
|
||||
}
|
||||
|
||||
func (s diffStats) NumDiff() int {
|
||||
return s.NumRemoved + s.NumInserted + s.NumModified
|
||||
}
|
||||
|
||||
func (s diffStats) Append(ds diffStats) diffStats {
|
||||
assert(s.Name == ds.Name)
|
||||
s.NumIgnored += ds.NumIgnored
|
||||
s.NumIdentical += ds.NumIdentical
|
||||
s.NumRemoved += ds.NumRemoved
|
||||
s.NumInserted += ds.NumInserted
|
||||
s.NumModified += ds.NumModified
|
||||
return s
|
||||
}
|
||||
|
||||
// String prints a humanly-readable summary of coalesced records.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
|
||||
func (s diffStats) String() string {
|
||||
var ss []string
|
||||
var sum int
|
||||
labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"}
|
||||
counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified}
|
||||
for i, n := range counts {
|
||||
if n > 0 {
|
||||
ss = append(ss, fmt.Sprintf("%d %v", n, labels[i]))
|
||||
}
|
||||
sum += n
|
||||
}
|
||||
|
||||
// Pluralize the name (adjusting for some obscure English grammar rules).
|
||||
name := s.Name
|
||||
if sum > 1 {
|
||||
name += "s"
|
||||
if strings.HasSuffix(name, "ys") {
|
||||
name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries"
|
||||
}
|
||||
}
|
||||
|
||||
// Format the list according to English grammar (with Oxford comma).
|
||||
switch n := len(ss); n {
|
||||
case 0:
|
||||
return ""
|
||||
case 1, 2:
|
||||
return strings.Join(ss, " and ") + " " + name
|
||||
default:
|
||||
return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name
|
||||
}
|
||||
}
|
||||
|
||||
type commentString string
|
||||
|
||||
func (s commentString) String() string { return string(s) }
|
||||
121
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
Normal file
121
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2019, The 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 cmp
|
||||
|
||||
import "reflect"
|
||||
|
||||
// valueNode represents a single node within a report, which is a
|
||||
// structured representation of the value tree, containing information
|
||||
// regarding which nodes are equal or not.
|
||||
type valueNode struct {
|
||||
parent *valueNode
|
||||
|
||||
Type reflect.Type
|
||||
ValueX reflect.Value
|
||||
ValueY reflect.Value
|
||||
|
||||
// NumSame is the number of leaf nodes that are equal.
|
||||
// All descendants are equal only if NumDiff is 0.
|
||||
NumSame int
|
||||
// NumDiff is the number of leaf nodes that are not equal.
|
||||
NumDiff int
|
||||
// NumIgnored is the number of leaf nodes that are ignored.
|
||||
NumIgnored int
|
||||
// NumCompared is the number of leaf nodes that were compared
|
||||
// using an Equal method or Comparer function.
|
||||
NumCompared int
|
||||
// NumTransformed is the number of non-leaf nodes that were transformed.
|
||||
NumTransformed int
|
||||
// NumChildren is the number of transitive descendants of this node.
|
||||
// This counts from zero; thus, leaf nodes have no descendants.
|
||||
NumChildren int
|
||||
// MaxDepth is the maximum depth of the tree. This counts from zero;
|
||||
// thus, leaf nodes have a depth of zero.
|
||||
MaxDepth int
|
||||
|
||||
// Records is a list of struct fields, slice elements, or map entries.
|
||||
Records []reportRecord // If populated, implies Value is not populated
|
||||
|
||||
// Value is the result of a transformation, pointer indirect, of
|
||||
// type assertion.
|
||||
Value *valueNode // If populated, implies Records is not populated
|
||||
|
||||
// TransformerName is the name of the transformer.
|
||||
TransformerName string // If non-empty, implies Value is populated
|
||||
}
|
||||
type reportRecord struct {
|
||||
Key reflect.Value // Invalid for slice element
|
||||
Value *valueNode
|
||||
}
|
||||
|
||||
func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) {
|
||||
vx, vy := ps.Values()
|
||||
child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy}
|
||||
switch s := ps.(type) {
|
||||
case StructField:
|
||||
assert(parent.Value == nil)
|
||||
parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child})
|
||||
case SliceIndex:
|
||||
assert(parent.Value == nil)
|
||||
parent.Records = append(parent.Records, reportRecord{Value: child})
|
||||
case MapIndex:
|
||||
assert(parent.Value == nil)
|
||||
parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child})
|
||||
case Indirect:
|
||||
assert(parent.Value == nil && parent.Records == nil)
|
||||
parent.Value = child
|
||||
case TypeAssertion:
|
||||
assert(parent.Value == nil && parent.Records == nil)
|
||||
parent.Value = child
|
||||
case Transform:
|
||||
assert(parent.Value == nil && parent.Records == nil)
|
||||
parent.Value = child
|
||||
parent.TransformerName = s.Name()
|
||||
parent.NumTransformed++
|
||||
default:
|
||||
assert(parent == nil) // Must be the root step
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
func (r *valueNode) Report(rs Result) {
|
||||
assert(r.MaxDepth == 0) // May only be called on leaf nodes
|
||||
|
||||
if rs.ByIgnore() {
|
||||
r.NumIgnored++
|
||||
} else {
|
||||
if rs.Equal() {
|
||||
r.NumSame++
|
||||
} else {
|
||||
r.NumDiff++
|
||||
}
|
||||
}
|
||||
assert(r.NumSame+r.NumDiff+r.NumIgnored == 1)
|
||||
|
||||
if rs.ByMethod() {
|
||||
r.NumCompared++
|
||||
}
|
||||
if rs.ByFunc() {
|
||||
r.NumCompared++
|
||||
}
|
||||
assert(r.NumCompared <= 1)
|
||||
}
|
||||
|
||||
func (child *valueNode) PopStep() (parent *valueNode) {
|
||||
if child.parent == nil {
|
||||
return nil
|
||||
}
|
||||
parent = child.parent
|
||||
parent.NumSame += child.NumSame
|
||||
parent.NumDiff += child.NumDiff
|
||||
parent.NumIgnored += child.NumIgnored
|
||||
parent.NumCompared += child.NumCompared
|
||||
parent.NumTransformed += child.NumTransformed
|
||||
parent.NumChildren += child.NumChildren + 1
|
||||
if parent.MaxDepth < child.MaxDepth+1 {
|
||||
parent.MaxDepth = child.MaxDepth + 1
|
||||
}
|
||||
return parent
|
||||
}
|
||||
23
vendor/github.com/google/nftables/CONTRIBUTING.md
generated
vendored
Normal file
23
vendor/github.com/google/nftables/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution,
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
202
vendor/github.com/google/nftables/LICENSE
generated
vendored
Normal file
202
vendor/github.com/google/nftables/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
24
vendor/github.com/google/nftables/README.md
generated
vendored
Normal file
24
vendor/github.com/google/nftables/README.md
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
[](https://github.com/google/nftables/actions/workflows/push.yml)
|
||||
[](https://godoc.org/github.com/google/nftables)
|
||||
|
||||
**This is not the correct repository for issues with the Linux nftables
|
||||
project!** This repository contains a third-party Go package to programmatically
|
||||
interact with nftables. Find the official nftables website at
|
||||
https://wiki.nftables.org/
|
||||
|
||||
This package manipulates Linux nftables (the iptables successor). It is
|
||||
implemented in pure Go, i.e. does not wrap libnftnl.
|
||||
|
||||
This is not an official Google product.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
This package is in very early stages, and only contains enough data types and
|
||||
functions to install very basic nftables rules. It is likely that mistakes with
|
||||
the data types/API will be identified as more functionality is added.
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are very welcome!
|
||||
|
||||
|
||||
300
vendor/github.com/google/nftables/alignedbuff/alignedbuff.go
generated
vendored
Normal file
300
vendor/github.com/google/nftables/alignedbuff/alignedbuff.go
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
// Package alignedbuff implements encoding and decoding aligned data elements
|
||||
// to/from buffers in native endianess.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The alignment/padding as implemented in this package must match that of
|
||||
// kernel's and user space C implementations for a particular architecture (bit
|
||||
// size). Please see also the "dummy structure" _xt_align
|
||||
// (https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/x_tables.h#L93)
|
||||
// as well as the associated XT_ALIGN C preprocessor macro.
|
||||
//
|
||||
// In particular, we rely on the Go compiler to follow the same architecture
|
||||
// alignments as the C compiler(s) on Linux.
|
||||
package alignedbuff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
)
|
||||
|
||||
// ErrEOF signals trying to read beyond the available payload information.
|
||||
var ErrEOF = errors.New("not enough data left")
|
||||
|
||||
// AlignedBuff implements marshalling and unmarshalling information in
|
||||
// platform/architecture native endianess and data type alignment. It
|
||||
// additionally covers some of the nftables-xtables translation-specific
|
||||
// idiosyncracies to the extend needed in order to properly marshal and
|
||||
// unmarshal Match and Target expressions, and their Info payload in particular.
|
||||
type AlignedBuff struct {
|
||||
data []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
// New returns a new AlignedBuff for marshalling aligned data in native
|
||||
// endianess.
|
||||
func New() AlignedBuff {
|
||||
return AlignedBuff{}
|
||||
}
|
||||
|
||||
// NewWithData returns a new AlignedBuff for unmarshalling the passed data in
|
||||
// native endianess.
|
||||
func NewWithData(data []byte) AlignedBuff {
|
||||
return AlignedBuff{data: data}
|
||||
}
|
||||
|
||||
// Data returns the properly padded info payload data written before by calling
|
||||
// the various Uint8, Uint16, ... marshalling functions.
|
||||
func (a *AlignedBuff) Data() []byte {
|
||||
// The Linux kernel expects payloads to be padded to the next uint64
|
||||
// alignment.
|
||||
a.alignWrite(uint64AlignMask)
|
||||
return a.data
|
||||
}
|
||||
|
||||
// BytesAligned32 unmarshals the given amount of bytes starting with the native
|
||||
// alignment for uint32 data types. It returns ErrEOF when trying to read beyond
|
||||
// the payload.
|
||||
//
|
||||
// BytesAligned32 is used to unmarshal IP addresses for different IP versions,
|
||||
// which are always aligned the same way as the native alignment for uint32.
|
||||
func (a *AlignedBuff) BytesAligned32(size int) ([]byte, error) {
|
||||
if err := a.alignCheckedRead(uint32AlignMask); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.pos > len(a.data)-size {
|
||||
return nil, ErrEOF
|
||||
}
|
||||
data := a.data[a.pos : a.pos+size]
|
||||
a.pos += size
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Uint8 unmarshals an uint8 in native endianess and alignment. It returns
|
||||
// ErrEOF when trying to read beyond the payload.
|
||||
func (a *AlignedBuff) Uint8() (uint8, error) {
|
||||
if a.pos >= len(a.data) {
|
||||
return 0, ErrEOF
|
||||
}
|
||||
v := a.data[a.pos]
|
||||
a.pos++
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Uint16 unmarshals an uint16 in native endianess and alignment. It returns
|
||||
// ErrEOF when trying to read beyond the payload.
|
||||
func (a *AlignedBuff) Uint16() (uint16, error) {
|
||||
if err := a.alignCheckedRead(uint16AlignMask); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := binaryutil.NativeEndian.Uint16(a.data[a.pos : a.pos+2])
|
||||
a.pos += 2
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Uint16BE unmarshals an uint16 in "network" (=big endian) endianess and native
|
||||
// uint16 alignment. It returns ErrEOF when trying to read beyond the payload.
|
||||
func (a *AlignedBuff) Uint16BE() (uint16, error) {
|
||||
if err := a.alignCheckedRead(uint16AlignMask); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := binaryutil.BigEndian.Uint16(a.data[a.pos : a.pos+2])
|
||||
a.pos += 2
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Uint32 unmarshals an uint32 in native endianess and alignment. It returns
|
||||
// ErrEOF when trying to read beyond the payload.
|
||||
func (a *AlignedBuff) Uint32() (uint32, error) {
|
||||
if err := a.alignCheckedRead(uint32AlignMask); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := binaryutil.NativeEndian.Uint32(a.data[a.pos : a.pos+4])
|
||||
a.pos += 4
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Uint64 unmarshals an uint64 in native endianess and alignment. It returns
|
||||
// ErrEOF when trying to read beyond the payload.
|
||||
func (a *AlignedBuff) Uint64() (uint64, error) {
|
||||
if err := a.alignCheckedRead(uint64AlignMask); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := binaryutil.NativeEndian.Uint64(a.data[a.pos : a.pos+8])
|
||||
a.pos += 8
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Int32 unmarshals an int32 in native endianess and alignment. It returns
|
||||
// ErrEOF when trying to read beyond the payload.
|
||||
func (a *AlignedBuff) Int32() (int32, error) {
|
||||
if err := a.alignCheckedRead(int32AlignMask); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := binaryutil.Int32(a.data[a.pos : a.pos+4])
|
||||
a.pos += 4
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// String unmarshals a null terminated string
|
||||
func (a *AlignedBuff) String() (string, error) {
|
||||
len := 0
|
||||
for {
|
||||
if a.data[a.pos+len] == 0x00 {
|
||||
break
|
||||
}
|
||||
len++
|
||||
}
|
||||
|
||||
v := binaryutil.String(a.data[a.pos : a.pos+len])
|
||||
a.pos += len
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// StringWithLength unmarshals a string of a given length (for non-null
|
||||
// terminated strings)
|
||||
func (a *AlignedBuff) StringWithLength(len int) (string, error) {
|
||||
v := binaryutil.String(a.data[a.pos : a.pos+len])
|
||||
a.pos += len
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Uint unmarshals an uint in native endianess and alignment for the C "unsigned
|
||||
// int" type. It returns ErrEOF when trying to read beyond the payload. Please
|
||||
// note that on 64bit platforms, the size and alignment of C's and Go's unsigned
|
||||
// integer data types differ, so we encapsulate this difference here.
|
||||
func (a *AlignedBuff) Uint() (uint, error) {
|
||||
switch uintSize {
|
||||
case 2:
|
||||
v, err := a.Uint16()
|
||||
return uint(v), err
|
||||
case 4:
|
||||
v, err := a.Uint32()
|
||||
return uint(v), err
|
||||
case 8:
|
||||
v, err := a.Uint64()
|
||||
return uint(v), err
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported uint size %d", uintSize))
|
||||
}
|
||||
}
|
||||
|
||||
// PutBytesAligned32 marshals the given bytes starting with the native alignment
|
||||
// for uint32 data types. It additionaly adds padding to reach the specified
|
||||
// size.
|
||||
//
|
||||
// PutBytesAligned32 is used to marshal IP addresses for different IP versions,
|
||||
// which are always aligned the same way as the native alignment for uint32.
|
||||
func (a *AlignedBuff) PutBytesAligned32(data []byte, size int) {
|
||||
a.alignWrite(uint32AlignMask)
|
||||
a.data = append(a.data, data...)
|
||||
a.pos += len(data)
|
||||
if len(data) < size {
|
||||
padding := size - len(data)
|
||||
a.data = append(a.data, bytes.Repeat([]byte{0}, padding)...)
|
||||
a.pos += padding
|
||||
}
|
||||
}
|
||||
|
||||
// PutUint8 marshals an uint8 in native endianess and alignment.
|
||||
func (a *AlignedBuff) PutUint8(v uint8) {
|
||||
a.data = append(a.data, v)
|
||||
a.pos++
|
||||
}
|
||||
|
||||
// PutUint16 marshals an uint16 in native endianess and alignment.
|
||||
func (a *AlignedBuff) PutUint16(v uint16) {
|
||||
a.alignWrite(uint16AlignMask)
|
||||
a.data = append(a.data, binaryutil.NativeEndian.PutUint16(v)...)
|
||||
a.pos += 2
|
||||
}
|
||||
|
||||
// PutUint16BE marshals an uint16 in "network" (=big endian) endianess and
|
||||
// native uint16 alignment.
|
||||
func (a *AlignedBuff) PutUint16BE(v uint16) {
|
||||
a.alignWrite(uint16AlignMask)
|
||||
a.data = append(a.data, binaryutil.BigEndian.PutUint16(v)...)
|
||||
a.pos += 2
|
||||
}
|
||||
|
||||
// PutUint32 marshals an uint32 in native endianess and alignment.
|
||||
func (a *AlignedBuff) PutUint32(v uint32) {
|
||||
a.alignWrite(uint32AlignMask)
|
||||
a.data = append(a.data, binaryutil.NativeEndian.PutUint32(v)...)
|
||||
a.pos += 4
|
||||
}
|
||||
|
||||
// PutUint64 marshals an uint64 in native endianess and alignment.
|
||||
func (a *AlignedBuff) PutUint64(v uint64) {
|
||||
a.alignWrite(uint64AlignMask)
|
||||
a.data = append(a.data, binaryutil.NativeEndian.PutUint64(v)...)
|
||||
a.pos += 8
|
||||
}
|
||||
|
||||
// PutInt32 marshals an int32 in native endianess and alignment.
|
||||
func (a *AlignedBuff) PutInt32(v int32) {
|
||||
a.alignWrite(int32AlignMask)
|
||||
a.data = append(a.data, binaryutil.PutInt32(v)...)
|
||||
a.pos += 4
|
||||
}
|
||||
|
||||
// PutString marshals a string.
|
||||
func (a *AlignedBuff) PutString(v string) {
|
||||
a.data = append(a.data, binaryutil.PutString(v)...)
|
||||
a.pos += len(v)
|
||||
}
|
||||
|
||||
// PutUint marshals an uint in native endianess and alignment for the C
|
||||
// "unsigned int" type. Please note that on 64bit platforms, the size and
|
||||
// alignment of C's and Go's unsigned integer data types differ, so we
|
||||
// encapsulate this difference here.
|
||||
func (a *AlignedBuff) PutUint(v uint) {
|
||||
switch uintSize {
|
||||
case 2:
|
||||
a.PutUint16(uint16(v))
|
||||
case 4:
|
||||
a.PutUint32(uint32(v))
|
||||
case 8:
|
||||
a.PutUint64(uint64(v))
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported uint size %d", uintSize))
|
||||
}
|
||||
}
|
||||
|
||||
// alignCheckedRead aligns the (read) position if necessary and suitable
|
||||
// according to the specified alignment mask. alignCheckedRead returns an error
|
||||
// if after any necessary alignment there isn't enough data left to be read into
|
||||
// a value of the size corresponding to the specified alignment mask.
|
||||
func (a *AlignedBuff) alignCheckedRead(m int) error {
|
||||
a.pos = (a.pos + m) & ^m
|
||||
if a.pos > len(a.data)-(m+1) {
|
||||
return ErrEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// alignWrite aligns the (write) position if necessary and suitable according to
|
||||
// the specified alignment mask. It doubles as final payload padding helpmate in
|
||||
// order to keep the kernel happy.
|
||||
func (a *AlignedBuff) alignWrite(m int) {
|
||||
pos := (a.pos + m) & ^m
|
||||
if pos != a.pos {
|
||||
a.data = append(a.data, padding[:pos-a.pos]...)
|
||||
a.pos = pos
|
||||
}
|
||||
}
|
||||
|
||||
// This is ... ugly.
|
||||
var uint16AlignMask = int(unsafe.Alignof(uint16(0)) - 1)
|
||||
var uint32AlignMask = int(unsafe.Alignof(uint32(0)) - 1)
|
||||
var uint64AlignMask = int(unsafe.Alignof(uint64(0)) - 1)
|
||||
var padding = bytes.Repeat([]byte{0}, uint64AlignMask)
|
||||
|
||||
var int32AlignMask = int(unsafe.Alignof(int32(0)) - 1)
|
||||
|
||||
// And this even worse.
|
||||
var uintSize = unsafe.Sizeof(uint32(0))
|
||||
125
vendor/github.com/google/nftables/binaryutil/binaryutil.go
generated
vendored
Normal file
125
vendor/github.com/google/nftables/binaryutil/binaryutil.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package binaryutil contains convenience wrappers around encoding/binary.
|
||||
package binaryutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ByteOrder is like binary.ByteOrder, but allocates memory and returns byte
|
||||
// slices, for convenience.
|
||||
type ByteOrder interface {
|
||||
PutUint16(v uint16) []byte
|
||||
PutUint32(v uint32) []byte
|
||||
PutUint64(v uint64) []byte
|
||||
Uint16(b []byte) uint16
|
||||
Uint32(b []byte) uint32
|
||||
Uint64(b []byte) uint64
|
||||
}
|
||||
|
||||
// NativeEndian is either little endian or big endian, depending on the native
|
||||
// endian-ness, and allocates memory and returns byte slices, for convenience.
|
||||
var NativeEndian ByteOrder = &nativeEndian{}
|
||||
|
||||
type nativeEndian struct{}
|
||||
|
||||
func (nativeEndian) PutUint16(v uint16) []byte {
|
||||
buf := make([]byte, 2)
|
||||
*(*uint16)(unsafe.Pointer(&buf[0])) = v
|
||||
return buf
|
||||
}
|
||||
|
||||
func (nativeEndian) PutUint32(v uint32) []byte {
|
||||
buf := make([]byte, 4)
|
||||
*(*uint32)(unsafe.Pointer(&buf[0])) = v
|
||||
return buf
|
||||
}
|
||||
|
||||
func (nativeEndian) PutUint64(v uint64) []byte {
|
||||
buf := make([]byte, 8)
|
||||
*(*uint64)(unsafe.Pointer(&buf[0])) = v
|
||||
return buf
|
||||
}
|
||||
|
||||
func (nativeEndian) Uint16(b []byte) uint16 {
|
||||
return *(*uint16)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
func (nativeEndian) Uint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
func (nativeEndian) Uint64(b []byte) uint64 {
|
||||
return *(*uint64)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
// BigEndian is like binary.BigEndian, but allocates memory and returns byte
|
||||
// slices, for convenience.
|
||||
var BigEndian ByteOrder = &bigEndian{}
|
||||
|
||||
type bigEndian struct{}
|
||||
|
||||
func (bigEndian) PutUint16(v uint16) []byte {
|
||||
buf := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(buf, v)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (bigEndian) PutUint32(v uint32) []byte {
|
||||
buf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(buf, v)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (bigEndian) PutUint64(v uint64) []byte {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, v)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (bigEndian) Uint16(b []byte) uint16 {
|
||||
return binary.BigEndian.Uint16(b)
|
||||
}
|
||||
|
||||
func (bigEndian) Uint32(b []byte) uint32 {
|
||||
return binary.BigEndian.Uint32(b)
|
||||
}
|
||||
|
||||
func (bigEndian) Uint64(b []byte) uint64 {
|
||||
return binary.BigEndian.Uint64(b)
|
||||
}
|
||||
|
||||
// For dealing with types not supported by the encoding/binary interface
|
||||
|
||||
func PutInt32(v int32) []byte {
|
||||
buf := make([]byte, 4)
|
||||
*(*int32)(unsafe.Pointer(&buf[0])) = v
|
||||
return buf
|
||||
}
|
||||
|
||||
func Int32(b []byte) int32 {
|
||||
return *(*int32)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
func PutString(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func String(b []byte) string {
|
||||
return string(bytes.TrimRight(b, "\x00"))
|
||||
}
|
||||
328
vendor/github.com/google/nftables/chain.go
generated
vendored
Normal file
328
vendor/github.com/google/nftables/chain.go
generated
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// ChainHook specifies at which step in packet processing the Chain should be
|
||||
// executed. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_hooks
|
||||
type ChainHook uint32
|
||||
|
||||
// Possible ChainHook values.
|
||||
var (
|
||||
ChainHookPrerouting *ChainHook = ChainHookRef(unix.NF_INET_PRE_ROUTING)
|
||||
ChainHookInput *ChainHook = ChainHookRef(unix.NF_INET_LOCAL_IN)
|
||||
ChainHookForward *ChainHook = ChainHookRef(unix.NF_INET_FORWARD)
|
||||
ChainHookOutput *ChainHook = ChainHookRef(unix.NF_INET_LOCAL_OUT)
|
||||
ChainHookPostrouting *ChainHook = ChainHookRef(unix.NF_INET_POST_ROUTING)
|
||||
ChainHookIngress *ChainHook = ChainHookRef(unix.NF_NETDEV_INGRESS)
|
||||
ChainHookEgress *ChainHook = ChainHookRef(unix.NF_NETDEV_EGRESS)
|
||||
)
|
||||
|
||||
// ChainHookRef returns a pointer to a ChainHookRef value.
|
||||
func ChainHookRef(h ChainHook) *ChainHook {
|
||||
return &h
|
||||
}
|
||||
|
||||
// ChainPriority orders the chain relative to Netfilter internal operations. See
|
||||
// also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_priority
|
||||
type ChainPriority int32
|
||||
|
||||
// Possible ChainPriority values.
|
||||
var ( // from /usr/include/linux/netfilter_ipv4.h
|
||||
ChainPriorityFirst *ChainPriority = ChainPriorityRef(math.MinInt32)
|
||||
ChainPriorityConntrackDefrag *ChainPriority = ChainPriorityRef(-400)
|
||||
ChainPriorityRaw *ChainPriority = ChainPriorityRef(-300)
|
||||
ChainPrioritySELinuxFirst *ChainPriority = ChainPriorityRef(-225)
|
||||
ChainPriorityConntrack *ChainPriority = ChainPriorityRef(-200)
|
||||
ChainPriorityMangle *ChainPriority = ChainPriorityRef(-150)
|
||||
ChainPriorityNATDest *ChainPriority = ChainPriorityRef(-100)
|
||||
ChainPriorityFilter *ChainPriority = ChainPriorityRef(0)
|
||||
ChainPrioritySecurity *ChainPriority = ChainPriorityRef(50)
|
||||
ChainPriorityNATSource *ChainPriority = ChainPriorityRef(100)
|
||||
ChainPrioritySELinuxLast *ChainPriority = ChainPriorityRef(225)
|
||||
ChainPriorityConntrackHelper *ChainPriority = ChainPriorityRef(300)
|
||||
ChainPriorityConntrackConfirm *ChainPriority = ChainPriorityRef(math.MaxInt32)
|
||||
ChainPriorityLast *ChainPriority = ChainPriorityRef(math.MaxInt32)
|
||||
)
|
||||
|
||||
// ChainPriorityRef returns a pointer to a ChainPriority value.
|
||||
func ChainPriorityRef(p ChainPriority) *ChainPriority {
|
||||
return &p
|
||||
}
|
||||
|
||||
// ChainType defines what this chain will be used for. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_types
|
||||
type ChainType string
|
||||
|
||||
// Possible ChainType values.
|
||||
const (
|
||||
ChainTypeFilter ChainType = "filter"
|
||||
ChainTypeRoute ChainType = "route"
|
||||
ChainTypeNAT ChainType = "nat"
|
||||
)
|
||||
|
||||
// ChainPolicy defines what this chain default policy will be.
|
||||
type ChainPolicy uint32
|
||||
|
||||
// Possible ChainPolicy values.
|
||||
const (
|
||||
ChainPolicyDrop ChainPolicy = iota
|
||||
ChainPolicyAccept
|
||||
)
|
||||
|
||||
// A Chain contains Rules. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains
|
||||
type Chain struct {
|
||||
Name string
|
||||
Table *Table
|
||||
Hooknum *ChainHook
|
||||
Priority *ChainPriority
|
||||
Type ChainType
|
||||
Policy *ChainPolicy
|
||||
Device string
|
||||
}
|
||||
|
||||
// AddChain adds the specified Chain. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Adding_base_chains
|
||||
func (cc *Conn) AddChain(c *Chain) *Chain {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_CHAIN_TABLE, Data: []byte(c.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_CHAIN_NAME, Data: []byte(c.Name + "\x00")},
|
||||
})
|
||||
|
||||
if c.Hooknum != nil && c.Priority != nil {
|
||||
hookAttr := []netlink.Attribute{
|
||||
{Type: unix.NFTA_HOOK_HOOKNUM, Data: binaryutil.BigEndian.PutUint32(uint32(*c.Hooknum))},
|
||||
{Type: unix.NFTA_HOOK_PRIORITY, Data: binaryutil.BigEndian.PutUint32(uint32(*c.Priority))},
|
||||
}
|
||||
|
||||
if c.Device != "" {
|
||||
hookAttr = append(hookAttr, netlink.Attribute{Type: unix.NFTA_HOOK_DEV, Data: []byte(c.Device + "\x00")})
|
||||
}
|
||||
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_CHAIN_HOOK, Data: cc.marshalAttr(hookAttr)},
|
||||
})...)
|
||||
}
|
||||
|
||||
if c.Policy != nil {
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_CHAIN_POLICY, Data: binaryutil.BigEndian.PutUint32(uint32(*c.Policy))},
|
||||
})...)
|
||||
}
|
||||
if c.Type != "" {
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_CHAIN_TYPE, Data: []byte(c.Type + "\x00")},
|
||||
})...)
|
||||
}
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: append(extraHeader(uint8(c.Table.Family), 0), data...),
|
||||
})
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// DelChain deletes the specified Chain. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Deleting_chains
|
||||
func (cc *Conn) DelChain(c *Chain) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_CHAIN_TABLE, Data: []byte(c.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_CHAIN_NAME, Data: []byte(c.Name + "\x00")},
|
||||
})
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELCHAIN),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(c.Table.Family), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
// FlushChain removes all rules within the specified Chain. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Flushing_chain
|
||||
func (cc *Conn) FlushChain(c *Chain) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_TABLE, Data: []byte(c.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")},
|
||||
})
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(c.Table.Family), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
// ListChains returns currently configured chains in the kernel
|
||||
func (cc *Conn) ListChains() ([]*Chain, error) {
|
||||
return cc.ListChainsOfTableFamily(TableFamilyUnspecified)
|
||||
}
|
||||
|
||||
// ListChain returns a single chain configured in the specified table
|
||||
func (cc *Conn) ListChain(table *Table, chain string) (*Chain, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_TABLE_NAME, Data: []byte(table.Name + "\x00")},
|
||||
{Type: unix.NFTA_CHAIN_NAME, Data: []byte(chain + "\x00")},
|
||||
}
|
||||
msg := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETCHAIN),
|
||||
Flags: netlink.Request,
|
||||
},
|
||||
Data: append(extraHeader(uint8(table.Family), 0), cc.marshalAttr(attrs)...),
|
||||
}
|
||||
|
||||
response, err := conn.Execute(msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conn.Execute failed: %v", err)
|
||||
}
|
||||
|
||||
if got, want := len(response), 1; got != want {
|
||||
return nil, fmt.Errorf("expected %d response message for chain, got %d", want, got)
|
||||
}
|
||||
|
||||
ch, err := chainFromMsg(response[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// ListChainsOfTableFamily returns currently configured chains for the specified
|
||||
// family in the kernel. It lists all chains ins all tables if family is
|
||||
// TableFamilyUnspecified.
|
||||
func (cc *Conn) ListChainsOfTableFamily(family TableFamily) ([]*Chain, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
msg := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETCHAIN),
|
||||
Flags: netlink.Request | netlink.Dump,
|
||||
},
|
||||
Data: extraHeader(uint8(family), 0),
|
||||
}
|
||||
|
||||
response, err := conn.Execute(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var chains []*Chain
|
||||
for _, m := range response {
|
||||
c, err := chainFromMsg(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chains = append(chains, c)
|
||||
}
|
||||
|
||||
return chains, nil
|
||||
}
|
||||
|
||||
func chainFromMsg(msg netlink.Message) (*Chain, error) {
|
||||
newChainHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN)
|
||||
delChainHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELCHAIN)
|
||||
if got, want1, want2 := msg.Header.Type, newChainHeaderType, delChainHeaderType; got != want1 && got != want2 {
|
||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
|
||||
}
|
||||
|
||||
var c Chain
|
||||
|
||||
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_CHAIN_NAME:
|
||||
c.Name = ad.String()
|
||||
case unix.NFTA_TABLE_NAME:
|
||||
c.Table = &Table{Name: ad.String()}
|
||||
// msg[0] carries TableFamily byte indicating whether it is IPv4, IPv6 or something else
|
||||
c.Table.Family = TableFamily(msg.Data[0])
|
||||
case unix.NFTA_CHAIN_TYPE:
|
||||
c.Type = ChainType(ad.String())
|
||||
case unix.NFTA_CHAIN_POLICY:
|
||||
policy := ChainPolicy(binaryutil.BigEndian.Uint32(ad.Bytes()))
|
||||
c.Policy = &policy
|
||||
case unix.NFTA_CHAIN_HOOK:
|
||||
ad.Do(func(b []byte) error {
|
||||
c.Hooknum, c.Priority, err = hookFromMsg(b)
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func hookFromMsg(b []byte) (*ChainHook, *ChainPriority, error) {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
var hooknum ChainHook
|
||||
var prio ChainPriority
|
||||
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_HOOK_HOOKNUM:
|
||||
hooknum = ChainHook(ad.Uint32())
|
||||
case unix.NFTA_HOOK_PRIORITY:
|
||||
prio = ChainPriority(ad.Uint32())
|
||||
}
|
||||
}
|
||||
|
||||
return &hooknum, &prio, nil
|
||||
}
|
||||
89
vendor/github.com/google/nftables/compat_policy.go
generated
vendored
Normal file
89
vendor/github.com/google/nftables/compat_policy.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/expr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const nft_RULE_COMPAT_F_INV uint32 = (1 << 1)
|
||||
const nft_RULE_COMPAT_F_MASK uint32 = nft_RULE_COMPAT_F_INV
|
||||
|
||||
// Used by xt match or target like xt_tcpudp to set compat policy between xtables and nftables
|
||||
// https://elixir.bootlin.com/linux/v5.12/source/net/netfilter/nft_compat.c#L187
|
||||
type compatPolicy struct {
|
||||
Proto uint32
|
||||
Flag uint32
|
||||
}
|
||||
|
||||
var xtMatchCompatMap map[string]*compatPolicy = map[string]*compatPolicy{
|
||||
"tcp": {
|
||||
Proto: unix.IPPROTO_TCP,
|
||||
},
|
||||
"udp": {
|
||||
Proto: unix.IPPROTO_UDP,
|
||||
},
|
||||
"udplite": {
|
||||
Proto: unix.IPPROTO_UDPLITE,
|
||||
},
|
||||
"tcpmss": {
|
||||
Proto: unix.IPPROTO_TCP,
|
||||
},
|
||||
"sctp": {
|
||||
Proto: unix.IPPROTO_SCTP,
|
||||
},
|
||||
"osf": {
|
||||
Proto: unix.IPPROTO_TCP,
|
||||
},
|
||||
"ipcomp": {
|
||||
Proto: unix.IPPROTO_COMP,
|
||||
},
|
||||
"esp": {
|
||||
Proto: unix.IPPROTO_ESP,
|
||||
},
|
||||
}
|
||||
|
||||
var xtTargetCompatMap map[string]*compatPolicy = map[string]*compatPolicy{
|
||||
"TCPOPTSTRIP": {
|
||||
Proto: unix.IPPROTO_TCP,
|
||||
},
|
||||
"TCPMSS": {
|
||||
Proto: unix.IPPROTO_TCP,
|
||||
},
|
||||
}
|
||||
|
||||
func getCompatPolicy(exprs []expr.Any) (*compatPolicy, error) {
|
||||
var exprItem expr.Any
|
||||
var compat *compatPolicy
|
||||
|
||||
for _, iter := range exprs {
|
||||
var tmpExprItem expr.Any
|
||||
var tmpCompat *compatPolicy
|
||||
switch item := iter.(type) {
|
||||
case *expr.Match:
|
||||
if compat, ok := xtMatchCompatMap[item.Name]; ok {
|
||||
tmpCompat = compat
|
||||
tmpExprItem = item
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case *expr.Target:
|
||||
if compat, ok := xtTargetCompatMap[item.Name]; ok {
|
||||
tmpCompat = compat
|
||||
tmpExprItem = item
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if compat == nil {
|
||||
compat = tmpCompat
|
||||
exprItem = tmpExprItem
|
||||
} else if *compat != *tmpCompat {
|
||||
return nil, fmt.Errorf("%#v and %#v has conflict compat policy %#v vs %#v", exprItem, tmpExprItem, compat, tmpCompat)
|
||||
}
|
||||
}
|
||||
return compat, nil
|
||||
}
|
||||
341
vendor/github.com/google/nftables/conn.go
generated
vendored
Normal file
341
vendor/github.com/google/nftables/conn.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/mdlayher/netlink"
|
||||
"github.com/mdlayher/netlink/nltest"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// A Conn represents a netlink connection of the nftables family.
|
||||
//
|
||||
// All methods return their input, so that variables can be defined from string
|
||||
// literals when desired.
|
||||
//
|
||||
// Commands are buffered. Flush sends all buffered commands in a single batch.
|
||||
type Conn struct {
|
||||
TestDial nltest.Func // for testing only; passed to nltest.Dial
|
||||
NetNS int // fd referencing the network namespace netlink will interact with.
|
||||
|
||||
lasting bool // establish a lasting connection to be used across multiple netlink operations.
|
||||
mu sync.Mutex // protects the following state
|
||||
messages []netlink.Message
|
||||
err error
|
||||
nlconn *netlink.Conn // netlink socket using NETLINK_NETFILTER protocol.
|
||||
}
|
||||
|
||||
// ConnOption is an option to change the behavior of the nftables Conn returned by Open.
|
||||
type ConnOption func(*Conn)
|
||||
|
||||
// New returns a netlink connection for querying and modifying nftables. Some
|
||||
// aspects of the new netlink connection can be configured using the options
|
||||
// WithNetNSFd, WithTestDial, and AsLasting.
|
||||
//
|
||||
// A lasting netlink connection should be closed by calling CloseLasting() to
|
||||
// close the underlying lasting netlink connection, cancelling all pending
|
||||
// operations using this connection.
|
||||
func New(opts ...ConnOption) (*Conn, error) {
|
||||
cc := &Conn{}
|
||||
for _, opt := range opts {
|
||||
opt(cc)
|
||||
}
|
||||
|
||||
if !cc.lasting {
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
nlconn, err := cc.dialNetlink()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc.nlconn = nlconn
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// AsLasting creates the new netlink connection as a lasting connection that is
|
||||
// reused across multiple netlink operations, instead of opening and closing the
|
||||
// underlying netlink connection only for the duration of a single netlink
|
||||
// operation.
|
||||
func AsLasting() ConnOption {
|
||||
return func(cc *Conn) {
|
||||
// We cannot create the underlying connection yet, as we are called
|
||||
// anywhere in the option processing chain and there might be later
|
||||
// options still modifying connection behavior.
|
||||
cc.lasting = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithNetNSFd sets the network namespace to create a new netlink connection to:
|
||||
// the fd must reference a network namespace.
|
||||
func WithNetNSFd(fd int) ConnOption {
|
||||
return func(cc *Conn) {
|
||||
cc.NetNS = fd
|
||||
}
|
||||
}
|
||||
|
||||
// WithTestDial sets the specified nltest.Func when creating a new netlink
|
||||
// connection.
|
||||
func WithTestDial(f nltest.Func) ConnOption {
|
||||
return func(cc *Conn) {
|
||||
cc.TestDial = f
|
||||
}
|
||||
}
|
||||
|
||||
// netlinkCloser is returned by netlinkConn(UnderLock) and must be called after
|
||||
// being done with the returned netlink connection in order to properly close
|
||||
// this connection, if necessary.
|
||||
type netlinkCloser func() error
|
||||
|
||||
// netlinkConn returns a netlink connection together with a netlinkCloser that
|
||||
// later must be called by the caller when it doesn't need the returned netlink
|
||||
// connection anymore. The netlinkCloser will close the netlink connection when
|
||||
// necessary. If New has been told to create a lasting connection, then this
|
||||
// lasting netlink connection will be returned, otherwise a new "transient"
|
||||
// netlink connection will be opened and returned instead. netlinkConn must not
|
||||
// be called while the Conn.mu lock is currently helt (this will cause a
|
||||
// deadlock). Use netlinkConnUnderLock instead in such situations.
|
||||
func (cc *Conn) netlinkConn() (*netlink.Conn, netlinkCloser, error) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
return cc.netlinkConnUnderLock()
|
||||
}
|
||||
|
||||
// netlinkConnUnderLock works like netlinkConn but must be called while holding
|
||||
// the Conn.mu lock.
|
||||
func (cc *Conn) netlinkConnUnderLock() (*netlink.Conn, netlinkCloser, error) {
|
||||
if cc.nlconn != nil {
|
||||
return cc.nlconn, func() error { return nil }, nil
|
||||
}
|
||||
nlconn, err := cc.dialNetlink()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nlconn, func() error { return nlconn.Close() }, nil
|
||||
}
|
||||
|
||||
func receiveAckAware(nlconn *netlink.Conn, sentMsgFlags netlink.HeaderFlags) ([]netlink.Message, error) {
|
||||
if nlconn == nil {
|
||||
return nil, errors.New("netlink conn is not initialized")
|
||||
}
|
||||
|
||||
// first receive will be the message that we expect
|
||||
reply, err := nlconn.Receive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (sentMsgFlags & netlink.Acknowledge) == 0 {
|
||||
// we did not request an ack
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
if (sentMsgFlags & netlink.Dump) == netlink.Dump {
|
||||
// sent message has Dump flag set, there will be no acks
|
||||
// https://github.com/torvalds/linux/blob/7e062cda7d90543ac8c7700fc7c5527d0c0f22ad/net/netlink/af_netlink.c#L2387-L2390
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
if len(reply) != 0 {
|
||||
last := reply[len(reply)-1]
|
||||
for re := last.Header.Type; (re&netlink.Overrun) == netlink.Overrun && (re&netlink.Done) != netlink.Done; re = last.Header.Type {
|
||||
// we are not finished, the message is overrun
|
||||
r, err := nlconn.Receive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reply = append(reply, r...)
|
||||
last = reply[len(reply)-1]
|
||||
}
|
||||
|
||||
if last.Header.Type == netlink.Error && binaryutil.BigEndian.Uint32(last.Data[:4]) == 0 {
|
||||
// we have already collected an ack
|
||||
return reply, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Now we expect an ack
|
||||
ack, err := nlconn.Receive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ack) == 0 {
|
||||
// received an empty ack?
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
msg := ack[0]
|
||||
if msg.Header.Type != netlink.Error {
|
||||
// acks should be delivered as NLMSG_ERROR
|
||||
return nil, fmt.Errorf("expected header %v, but got %v", netlink.Error, msg.Header.Type)
|
||||
}
|
||||
|
||||
if binaryutil.BigEndian.Uint32(msg.Data[:4]) != 0 {
|
||||
// if errno field is not set to 0 (success), this is an error
|
||||
return nil, fmt.Errorf("error delivered in message: %v", msg.Data)
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
// CloseLasting closes the lasting netlink connection that has been opened using
|
||||
// AsLasting option when creating this connection. If either no lasting netlink
|
||||
// connection has been opened or the lasting connection is already in the
|
||||
// process of closing or has been closed, CloseLasting will immediately return
|
||||
// without any error.
|
||||
//
|
||||
// CloseLasting will terminate all pending netlink operations using the lasting
|
||||
// connection.
|
||||
//
|
||||
// After closing a lasting connection, the connection will revert to using
|
||||
// on-demand transient netlink connections when calling further netlink
|
||||
// operations (such as GetTables).
|
||||
func (cc *Conn) CloseLasting() error {
|
||||
// Don't acquire the lock for the whole duration of the CloseLasting
|
||||
// operation, but instead only so long as to make sure to only run the
|
||||
// netlink socket close on the first time with a lasting netlink socket. As
|
||||
// there is only the New() constructor, but no Open() method, it's
|
||||
// impossible to reopen a lasting connection.
|
||||
cc.mu.Lock()
|
||||
nlconn := cc.nlconn
|
||||
cc.nlconn = nil
|
||||
cc.mu.Unlock()
|
||||
if nlconn != nil {
|
||||
return nlconn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush sends all buffered commands in a single batch to nftables.
|
||||
func (cc *Conn) Flush() error {
|
||||
cc.mu.Lock()
|
||||
defer func() {
|
||||
cc.messages = nil
|
||||
cc.mu.Unlock()
|
||||
}()
|
||||
if len(cc.messages) == 0 {
|
||||
// Messages were already programmed, returning nil
|
||||
return nil
|
||||
}
|
||||
if cc.err != nil {
|
||||
return cc.err // serialization error
|
||||
}
|
||||
conn, closer, err := cc.netlinkConnUnderLock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
if _, err := conn.SendMessages(batch(cc.messages)); err != nil {
|
||||
return fmt.Errorf("SendMessages: %w", err)
|
||||
}
|
||||
|
||||
var errs error
|
||||
// Fetch the requested acknowledgement for each message we sent.
|
||||
for _, msg := range cc.messages {
|
||||
if _, err := receiveAckAware(conn, msg.Header.Flags); err != nil {
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
// Kernel will only send one permission error to user space.
|
||||
return err
|
||||
}
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil {
|
||||
return fmt.Errorf("conn.Receive: %w", errs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushRuleset flushes the entire ruleset. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Operations_at_ruleset_level
|
||||
func (cc *Conn) FlushRuleset() {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: extraHeader(0, 0),
|
||||
})
|
||||
}
|
||||
|
||||
func (cc *Conn) dialNetlink() (*netlink.Conn, error) {
|
||||
if cc.TestDial != nil {
|
||||
return nltest.Dial(cc.TestDial), nil
|
||||
}
|
||||
|
||||
return netlink.Dial(unix.NETLINK_NETFILTER, &netlink.Config{NetNS: cc.NetNS})
|
||||
}
|
||||
|
||||
func (cc *Conn) setErr(err error) {
|
||||
if cc.err != nil {
|
||||
return
|
||||
}
|
||||
cc.err = err
|
||||
}
|
||||
|
||||
func (cc *Conn) marshalAttr(attrs []netlink.Attribute) []byte {
|
||||
b, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
cc.setErr(err)
|
||||
return nil
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (cc *Conn) marshalExpr(fam byte, e expr.Any) []byte {
|
||||
b, err := expr.Marshal(fam, e)
|
||||
if err != nil {
|
||||
cc.setErr(err)
|
||||
return nil
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func batch(messages []netlink.Message) []netlink.Message {
|
||||
batch := []netlink.Message{
|
||||
{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN),
|
||||
Flags: netlink.Request,
|
||||
},
|
||||
Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
|
||||
},
|
||||
}
|
||||
|
||||
batch = append(batch, messages...)
|
||||
|
||||
batch = append(batch, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_END),
|
||||
Flags: netlink.Request,
|
||||
},
|
||||
Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
|
||||
})
|
||||
|
||||
return batch
|
||||
}
|
||||
69
vendor/github.com/google/nftables/counter.go
generated
vendored
Normal file
69
vendor/github.com/google/nftables/counter.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// CounterObj implements Obj.
|
||||
type CounterObj struct {
|
||||
Table *Table
|
||||
Name string // e.g. “fwded”
|
||||
|
||||
Bytes uint64
|
||||
Packets uint64
|
||||
}
|
||||
|
||||
func (c *CounterObj) unmarshal(ad *netlink.AttributeDecoder) error {
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_COUNTER_BYTES:
|
||||
c.Bytes = ad.Uint64()
|
||||
case unix.NFTA_COUNTER_PACKETS:
|
||||
c.Packets = ad.Uint64()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
|
||||
func (c *CounterObj) table() *Table {
|
||||
return c.Table
|
||||
}
|
||||
|
||||
func (c *CounterObj) family() TableFamily {
|
||||
return c.Table.Family
|
||||
}
|
||||
|
||||
func (c *CounterObj) marshal(data bool) ([]byte, error) {
|
||||
obj, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_COUNTER_BYTES, Data: binaryutil.BigEndian.PutUint64(c.Bytes)},
|
||||
{Type: unix.NFTA_COUNTER_PACKETS, Data: binaryutil.BigEndian.PutUint64(c.Packets)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_OBJ_TABLE, Data: []byte(c.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_OBJ_NAME, Data: []byte(c.Name + "\x00")},
|
||||
{Type: unix.NFTA_OBJ_TYPE, Data: binaryutil.BigEndian.PutUint32(unix.NFT_OBJECT_COUNTER)},
|
||||
}
|
||||
if data {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA, Data: obj})
|
||||
}
|
||||
return netlink.MarshalAttributes(attrs)
|
||||
}
|
||||
16
vendor/github.com/google/nftables/doc.go
generated
vendored
Normal file
16
vendor/github.com/google/nftables/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package nftables manipulates Linux nftables (the iptables successor).
|
||||
package nftables
|
||||
102
vendor/github.com/google/nftables/expr/bitwise.go
generated
vendored
Normal file
102
vendor/github.com/google/nftables/expr/bitwise.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Bitwise struct {
|
||||
SourceRegister uint32
|
||||
DestRegister uint32
|
||||
Len uint32
|
||||
Mask []byte
|
||||
Xor []byte
|
||||
}
|
||||
|
||||
func (e *Bitwise) marshal(fam byte) ([]byte, error) {
|
||||
mask, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_DATA_VALUE, Data: e.Mask},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xor, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_DATA_VALUE, Data: e.Xor},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_BITWISE_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)},
|
||||
{Type: unix.NFTA_BITWISE_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)},
|
||||
{Type: unix.NFTA_BITWISE_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_BITWISE_MASK, Data: mask},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_BITWISE_XOR, Data: xor},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("bitwise\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Bitwise) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_BITWISE_SREG:
|
||||
e.SourceRegister = ad.Uint32()
|
||||
case unix.NFTA_BITWISE_DREG:
|
||||
e.DestRegister = ad.Uint32()
|
||||
case unix.NFTA_BITWISE_LEN:
|
||||
e.Len = ad.Uint32()
|
||||
case unix.NFTA_BITWISE_MASK:
|
||||
// Since NFTA_BITWISE_MASK is nested, it requires additional decoding
|
||||
ad.Nested(func(nad *netlink.AttributeDecoder) error {
|
||||
for nad.Next() {
|
||||
switch nad.Type() {
|
||||
case unix.NFTA_DATA_VALUE:
|
||||
e.Mask = nad.Bytes()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
case unix.NFTA_BITWISE_XOR:
|
||||
// Since NFTA_BITWISE_XOR is nested, it requires additional decoding
|
||||
ad.Nested(func(nad *netlink.AttributeDecoder) error {
|
||||
for nad.Next() {
|
||||
switch nad.Type() {
|
||||
case unix.NFTA_DATA_VALUE:
|
||||
e.Xor = nad.Bytes()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
59
vendor/github.com/google/nftables/expr/byteorder.go
generated
vendored
Normal file
59
vendor/github.com/google/nftables/expr/byteorder.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type ByteorderOp uint32
|
||||
|
||||
const (
|
||||
ByteorderNtoh ByteorderOp = unix.NFT_BYTEORDER_NTOH
|
||||
ByteorderHton ByteorderOp = unix.NFT_BYTEORDER_HTON
|
||||
)
|
||||
|
||||
type Byteorder struct {
|
||||
SourceRegister uint32
|
||||
DestRegister uint32
|
||||
Op ByteorderOp
|
||||
Len uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
func (e *Byteorder) marshal(fam byte) ([]byte, error) {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_BYTEORDER_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)},
|
||||
{Type: unix.NFTA_BYTEORDER_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)},
|
||||
{Type: unix.NFTA_BYTEORDER_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))},
|
||||
{Type: unix.NFTA_BYTEORDER_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)},
|
||||
{Type: unix.NFTA_BYTEORDER_SIZE, Data: binaryutil.BigEndian.PutUint32(e.Size)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("byteorder\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Byteorder) unmarshal(fam byte, data []byte) error {
|
||||
return fmt.Errorf("not yet implemented")
|
||||
}
|
||||
70
vendor/github.com/google/nftables/expr/connlimit.go
generated
vendored
Normal file
70
vendor/github.com/google/nftables/expr/connlimit.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// Per https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1167
|
||||
NFTA_CONNLIMIT_UNSPEC = iota
|
||||
NFTA_CONNLIMIT_COUNT
|
||||
NFTA_CONNLIMIT_FLAGS
|
||||
NFT_CONNLIMIT_F_INV = 1
|
||||
)
|
||||
|
||||
// Per https://git.netfilter.org/libnftnl/tree/src/expr/connlimit.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c
|
||||
type Connlimit struct {
|
||||
Count uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
func (e *Connlimit) marshal(fam byte) ([]byte, error) {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: NFTA_CONNLIMIT_COUNT, Data: binaryutil.BigEndian.PutUint32(e.Count)},
|
||||
{Type: NFTA_CONNLIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("connlimit\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Connlimit) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case NFTA_CONNLIMIT_COUNT:
|
||||
e.Count = binaryutil.BigEndian.Uint32(ad.Bytes())
|
||||
case NFTA_CONNLIMIT_FLAGS:
|
||||
e.Flags = binaryutil.BigEndian.Uint32(ad.Bytes())
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
60
vendor/github.com/google/nftables/expr/counter.go
generated
vendored
Normal file
60
vendor/github.com/google/nftables/expr/counter.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Counter struct {
|
||||
Bytes uint64
|
||||
Packets uint64
|
||||
}
|
||||
|
||||
func (e *Counter) marshal(fam byte) ([]byte, error) {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_COUNTER_BYTES, Data: binaryutil.BigEndian.PutUint64(e.Bytes)},
|
||||
{Type: unix.NFTA_COUNTER_PACKETS, Data: binaryutil.BigEndian.PutUint64(e.Packets)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("counter\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Counter) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_COUNTER_BYTES:
|
||||
e.Bytes = ad.Uint64()
|
||||
case unix.NFTA_COUNTER_PACKETS:
|
||||
e.Packets = ad.Uint64()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
115
vendor/github.com/google/nftables/expr/ct.go
generated
vendored
Normal file
115
vendor/github.com/google/nftables/expr/ct.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// CtKey specifies which piece of conntrack information should be loaded. See
|
||||
// also https://wiki.nftables.org/wiki-nftables/index.php/Matching_connection_tracking_stateful_metainformation
|
||||
type CtKey uint32
|
||||
|
||||
// Possible CtKey values.
|
||||
const (
|
||||
CtKeySTATE CtKey = unix.NFT_CT_STATE
|
||||
CtKeyDIRECTION CtKey = unix.NFT_CT_DIRECTION
|
||||
CtKeySTATUS CtKey = unix.NFT_CT_STATUS
|
||||
CtKeyMARK CtKey = unix.NFT_CT_MARK
|
||||
CtKeySECMARK CtKey = unix.NFT_CT_SECMARK
|
||||
CtKeyEXPIRATION CtKey = unix.NFT_CT_EXPIRATION
|
||||
CtKeyHELPER CtKey = unix.NFT_CT_HELPER
|
||||
CtKeyL3PROTOCOL CtKey = unix.NFT_CT_L3PROTOCOL
|
||||
CtKeySRC CtKey = unix.NFT_CT_SRC
|
||||
CtKeyDST CtKey = unix.NFT_CT_DST
|
||||
CtKeyPROTOCOL CtKey = unix.NFT_CT_PROTOCOL
|
||||
CtKeyPROTOSRC CtKey = unix.NFT_CT_PROTO_SRC
|
||||
CtKeyPROTODST CtKey = unix.NFT_CT_PROTO_DST
|
||||
CtKeyLABELS CtKey = unix.NFT_CT_LABELS
|
||||
CtKeyPKTS CtKey = unix.NFT_CT_PKTS
|
||||
CtKeyBYTES CtKey = unix.NFT_CT_BYTES
|
||||
CtKeyAVGPKT CtKey = unix.NFT_CT_AVGPKT
|
||||
CtKeyZONE CtKey = unix.NFT_CT_ZONE
|
||||
CtKeyEVENTMASK CtKey = unix.NFT_CT_EVENTMASK
|
||||
|
||||
// https://sources.debian.org/src//nftables/0.9.8-3/src/ct.c/?hl=39#L39
|
||||
CtStateBitINVALID uint32 = 1
|
||||
CtStateBitESTABLISHED uint32 = 2
|
||||
CtStateBitRELATED uint32 = 4
|
||||
CtStateBitNEW uint32 = 8
|
||||
CtStateBitUNTRACKED uint32 = 64
|
||||
)
|
||||
|
||||
// Ct defines type for NFT connection tracking
|
||||
type Ct struct {
|
||||
Register uint32
|
||||
SourceRegister bool
|
||||
Key CtKey
|
||||
}
|
||||
|
||||
func (e *Ct) marshal(fam byte) ([]byte, error) {
|
||||
regData := []byte{}
|
||||
exprData, err := netlink.MarshalAttributes(
|
||||
[]netlink.Attribute{
|
||||
{Type: unix.NFTA_CT_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.SourceRegister {
|
||||
regData, err = netlink.MarshalAttributes(
|
||||
[]netlink.Attribute{
|
||||
{Type: unix.NFTA_CT_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
},
|
||||
)
|
||||
} else {
|
||||
regData, err = netlink.MarshalAttributes(
|
||||
[]netlink.Attribute{
|
||||
{Type: unix.NFTA_CT_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
},
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprData = append(exprData, regData...)
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("ct\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Ct) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_CT_KEY:
|
||||
e.Key = CtKey(ad.Uint32())
|
||||
case unix.NFTA_CT_DREG:
|
||||
e.Register = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
67
vendor/github.com/google/nftables/expr/dup.go
generated
vendored
Normal file
67
vendor/github.com/google/nftables/expr/dup.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Dup struct {
|
||||
RegAddr uint32
|
||||
RegDev uint32
|
||||
IsRegDevSet bool
|
||||
}
|
||||
|
||||
func (e *Dup) marshal(fam byte) ([]byte, error) {
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_DUP_SREG_ADDR, Data: binaryutil.BigEndian.PutUint32(e.RegAddr)},
|
||||
}
|
||||
|
||||
if e.IsRegDevSet {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_DUP_SREG_DEV, Data: binaryutil.BigEndian.PutUint32(e.RegDev)})
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("dup\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Dup) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_DUP_SREG_ADDR:
|
||||
e.RegAddr = ad.Uint32()
|
||||
case unix.NFTA_DUP_SREG_DEV:
|
||||
e.RegDev = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
149
vendor/github.com/google/nftables/expr/dynset.go
generated
vendored
Normal file
149
vendor/github.com/google/nftables/expr/dynset.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/internal/parseexprfunc"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Not yet supported by unix package
|
||||
// https://cs.opensource.google/go/x/sys/+/c6bc011c:unix/ztypes_linux.go;l=2027-2036
|
||||
const (
|
||||
NFTA_DYNSET_EXPRESSIONS = 0xa
|
||||
NFT_DYNSET_F_EXPR = (1 << 1)
|
||||
)
|
||||
|
||||
// Dynset represent a rule dynamically adding or updating a set or a map based on an incoming packet.
|
||||
type Dynset struct {
|
||||
SrcRegKey uint32
|
||||
SrcRegData uint32
|
||||
SetID uint32
|
||||
SetName string
|
||||
Operation uint32
|
||||
Timeout time.Duration
|
||||
Invert bool
|
||||
Exprs []Any
|
||||
}
|
||||
|
||||
func (e *Dynset) marshal(fam byte) ([]byte, error) {
|
||||
// See: https://git.netfilter.org/libnftnl/tree/src/expr/dynset.c
|
||||
var opAttrs []netlink.Attribute
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_SREG_KEY, Data: binaryutil.BigEndian.PutUint32(e.SrcRegKey)})
|
||||
if e.SrcRegData != 0 {
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_SREG_DATA, Data: binaryutil.BigEndian.PutUint32(e.SrcRegData)})
|
||||
}
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_OP, Data: binaryutil.BigEndian.PutUint32(e.Operation)})
|
||||
if e.Timeout != 0 {
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(e.Timeout.Milliseconds()))})
|
||||
}
|
||||
var flags uint32
|
||||
if e.Invert {
|
||||
flags |= unix.NFT_DYNSET_F_INV
|
||||
}
|
||||
|
||||
opAttrs = append(opAttrs,
|
||||
netlink.Attribute{Type: unix.NFTA_DYNSET_SET_NAME, Data: []byte(e.SetName + "\x00")},
|
||||
netlink.Attribute{Type: unix.NFTA_DYNSET_SET_ID, Data: binaryutil.BigEndian.PutUint32(e.SetID)})
|
||||
|
||||
// Per https://git.netfilter.org/libnftnl/tree/src/expr/dynset.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n170
|
||||
if len(e.Exprs) > 0 {
|
||||
flags |= NFT_DYNSET_F_EXPR
|
||||
switch len(e.Exprs) {
|
||||
case 1:
|
||||
exprData, err := Marshal(fam, e.Exprs[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_EXPR, Data: exprData})
|
||||
default:
|
||||
var elemAttrs []netlink.Attribute
|
||||
for _, ex := range e.Exprs {
|
||||
exprData, err := Marshal(fam, ex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elemAttrs = append(elemAttrs, netlink.Attribute{Type: unix.NFTA_LIST_ELEM, Data: exprData})
|
||||
}
|
||||
elemData, err := netlink.MarshalAttributes(elemAttrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: NFTA_DYNSET_EXPRESSIONS, Data: elemData})
|
||||
}
|
||||
}
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)})
|
||||
|
||||
opData, err := netlink.MarshalAttributes(opAttrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("dynset\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: opData},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Dynset) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_DYNSET_SET_NAME:
|
||||
e.SetName = ad.String()
|
||||
case unix.NFTA_DYNSET_SET_ID:
|
||||
e.SetID = ad.Uint32()
|
||||
case unix.NFTA_DYNSET_SREG_KEY:
|
||||
e.SrcRegKey = ad.Uint32()
|
||||
case unix.NFTA_DYNSET_SREG_DATA:
|
||||
e.SrcRegData = ad.Uint32()
|
||||
case unix.NFTA_DYNSET_OP:
|
||||
e.Operation = ad.Uint32()
|
||||
case unix.NFTA_DYNSET_TIMEOUT:
|
||||
e.Timeout = time.Duration(time.Millisecond * time.Duration(ad.Uint64()))
|
||||
case unix.NFTA_DYNSET_FLAGS:
|
||||
e.Invert = (ad.Uint32() & unix.NFT_DYNSET_F_INV) != 0
|
||||
case unix.NFTA_DYNSET_EXPR:
|
||||
exprs, err := parseexprfunc.ParseExprBytesFunc(fam, ad, ad.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.setInterfaceExprs(exprs)
|
||||
case NFTA_DYNSET_EXPRESSIONS:
|
||||
exprs, err := parseexprfunc.ParseExprMsgFunc(fam, ad.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.setInterfaceExprs(exprs)
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
|
||||
func (e *Dynset) setInterfaceExprs(exprs []interface{}) {
|
||||
e.Exprs = make([]Any, len(exprs))
|
||||
for i := range exprs {
|
||||
e.Exprs[i] = exprs[i].(Any)
|
||||
}
|
||||
}
|
||||
430
vendor/github.com/google/nftables/expr/expr.go
generated
vendored
Normal file
430
vendor/github.com/google/nftables/expr/expr.go
generated
vendored
Normal file
@@ -0,0 +1,430 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package expr provides nftables rule expressions.
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/internal/parseexprfunc"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func init() {
|
||||
parseexprfunc.ParseExprBytesFunc = func(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]interface{}, error) {
|
||||
exprs, err := exprsFromBytes(fam, ad, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]interface{}, len(exprs))
|
||||
for idx, expr := range exprs {
|
||||
result[idx] = expr
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
parseexprfunc.ParseExprMsgFunc = func(fam byte, b []byte) ([]interface{}, error) {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
var exprs []interface{}
|
||||
for ad.Next() {
|
||||
e, err := parseexprfunc.ParseExprBytesFunc(fam, ad, b)
|
||||
if err != nil {
|
||||
return e, err
|
||||
}
|
||||
exprs = append(exprs, e...)
|
||||
}
|
||||
return exprs, ad.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal serializes the specified expression into a byte slice.
|
||||
func Marshal(fam byte, e Any) ([]byte, error) {
|
||||
return e.marshal(fam)
|
||||
}
|
||||
|
||||
// Unmarshal fills an expression from the specified byte slice.
|
||||
func Unmarshal(fam byte, data []byte, e Any) error {
|
||||
return e.unmarshal(fam, data)
|
||||
}
|
||||
|
||||
// exprsFromBytes parses nested raw expressions bytes
|
||||
// to construct nftables expressions
|
||||
func exprsFromBytes(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]Any, error) {
|
||||
var exprs []Any
|
||||
ad.Do(func(b []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
var name string
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_EXPR_NAME:
|
||||
name = ad.String()
|
||||
if name == "notrack" {
|
||||
e := &Notrack{}
|
||||
exprs = append(exprs, e)
|
||||
}
|
||||
case unix.NFTA_EXPR_DATA:
|
||||
var e Any
|
||||
switch name {
|
||||
case "ct":
|
||||
e = &Ct{}
|
||||
case "range":
|
||||
e = &Range{}
|
||||
case "meta":
|
||||
e = &Meta{}
|
||||
case "cmp":
|
||||
e = &Cmp{}
|
||||
case "counter":
|
||||
e = &Counter{}
|
||||
case "objref":
|
||||
e = &Objref{}
|
||||
case "payload":
|
||||
e = &Payload{}
|
||||
case "lookup":
|
||||
e = &Lookup{}
|
||||
case "immediate":
|
||||
e = &Immediate{}
|
||||
case "bitwise":
|
||||
e = &Bitwise{}
|
||||
case "redir":
|
||||
e = &Redir{}
|
||||
case "nat":
|
||||
e = &NAT{}
|
||||
case "limit":
|
||||
e = &Limit{}
|
||||
case "quota":
|
||||
e = &Quota{}
|
||||
case "dynset":
|
||||
e = &Dynset{}
|
||||
case "log":
|
||||
e = &Log{}
|
||||
case "exthdr":
|
||||
e = &Exthdr{}
|
||||
case "match":
|
||||
e = &Match{}
|
||||
case "target":
|
||||
e = &Target{}
|
||||
case "connlimit":
|
||||
e = &Connlimit{}
|
||||
case "queue":
|
||||
e = &Queue{}
|
||||
case "flow_offload":
|
||||
e = &FlowOffload{}
|
||||
case "reject":
|
||||
e = &Reject{}
|
||||
case "masq":
|
||||
e = &Masq{}
|
||||
case "hash":
|
||||
e = &Hash{}
|
||||
}
|
||||
if e == nil {
|
||||
// TODO: introduce an opaque expression type so that users know
|
||||
// something is here.
|
||||
continue // unsupported expression type
|
||||
}
|
||||
|
||||
ad.Do(func(b []byte) error {
|
||||
if err := Unmarshal(fam, b, e); err != nil {
|
||||
return err
|
||||
}
|
||||
// Verdict expressions are a special-case of immediate expressions, so
|
||||
// if the expression is an immediate writing nothing into the verdict
|
||||
// register (invalid), re-parse it as a verdict expression.
|
||||
if imm, isImmediate := e.(*Immediate); isImmediate && imm.Register == unix.NFT_REG_VERDICT && len(imm.Data) == 0 {
|
||||
e = &Verdict{}
|
||||
if err := Unmarshal(fam, b, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
exprs = append(exprs, e)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
})
|
||||
return exprs, ad.Err()
|
||||
}
|
||||
|
||||
// Any is an interface implemented by any expression type.
|
||||
type Any interface {
|
||||
marshal(fam byte) ([]byte, error)
|
||||
unmarshal(fam byte, data []byte) error
|
||||
}
|
||||
|
||||
// MetaKey specifies which piece of meta information should be loaded. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation
|
||||
type MetaKey uint32
|
||||
|
||||
// Possible MetaKey values.
|
||||
const (
|
||||
MetaKeyLEN MetaKey = unix.NFT_META_LEN
|
||||
MetaKeyPROTOCOL MetaKey = unix.NFT_META_PROTOCOL
|
||||
MetaKeyPRIORITY MetaKey = unix.NFT_META_PRIORITY
|
||||
MetaKeyMARK MetaKey = unix.NFT_META_MARK
|
||||
MetaKeyIIF MetaKey = unix.NFT_META_IIF
|
||||
MetaKeyOIF MetaKey = unix.NFT_META_OIF
|
||||
MetaKeyIIFNAME MetaKey = unix.NFT_META_IIFNAME
|
||||
MetaKeyOIFNAME MetaKey = unix.NFT_META_OIFNAME
|
||||
MetaKeyIIFTYPE MetaKey = unix.NFT_META_IIFTYPE
|
||||
MetaKeyOIFTYPE MetaKey = unix.NFT_META_OIFTYPE
|
||||
MetaKeySKUID MetaKey = unix.NFT_META_SKUID
|
||||
MetaKeySKGID MetaKey = unix.NFT_META_SKGID
|
||||
MetaKeyNFTRACE MetaKey = unix.NFT_META_NFTRACE
|
||||
MetaKeyRTCLASSID MetaKey = unix.NFT_META_RTCLASSID
|
||||
MetaKeySECMARK MetaKey = unix.NFT_META_SECMARK
|
||||
MetaKeyNFPROTO MetaKey = unix.NFT_META_NFPROTO
|
||||
MetaKeyL4PROTO MetaKey = unix.NFT_META_L4PROTO
|
||||
MetaKeyBRIIIFNAME MetaKey = unix.NFT_META_BRI_IIFNAME
|
||||
MetaKeyBRIOIFNAME MetaKey = unix.NFT_META_BRI_OIFNAME
|
||||
MetaKeyPKTTYPE MetaKey = unix.NFT_META_PKTTYPE
|
||||
MetaKeyCPU MetaKey = unix.NFT_META_CPU
|
||||
MetaKeyIIFGROUP MetaKey = unix.NFT_META_IIFGROUP
|
||||
MetaKeyOIFGROUP MetaKey = unix.NFT_META_OIFGROUP
|
||||
MetaKeyCGROUP MetaKey = unix.NFT_META_CGROUP
|
||||
MetaKeyPRANDOM MetaKey = unix.NFT_META_PRANDOM
|
||||
)
|
||||
|
||||
// Meta loads packet meta information for later comparisons. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation
|
||||
type Meta struct {
|
||||
Key MetaKey
|
||||
SourceRegister bool
|
||||
Register uint32
|
||||
}
|
||||
|
||||
func (e *Meta) marshal(fam byte) ([]byte, error) {
|
||||
regData := []byte{}
|
||||
exprData, err := netlink.MarshalAttributes(
|
||||
[]netlink.Attribute{
|
||||
{Type: unix.NFTA_META_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.SourceRegister {
|
||||
regData, err = netlink.MarshalAttributes(
|
||||
[]netlink.Attribute{
|
||||
{Type: unix.NFTA_META_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
},
|
||||
)
|
||||
} else {
|
||||
regData, err = netlink.MarshalAttributes(
|
||||
[]netlink.Attribute{
|
||||
{Type: unix.NFTA_META_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
},
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprData = append(exprData, regData...)
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("meta\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Meta) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_META_SREG:
|
||||
e.Register = ad.Uint32()
|
||||
e.SourceRegister = true
|
||||
case unix.NFTA_META_DREG:
|
||||
e.Register = ad.Uint32()
|
||||
case unix.NFTA_META_KEY:
|
||||
e.Key = MetaKey(ad.Uint32())
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
|
||||
// Masq (Masquerade) is a special case of SNAT, where the source address is
|
||||
// automagically set to the address of the output interface. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)#Masquerading
|
||||
type Masq struct {
|
||||
Random bool
|
||||
FullyRandom bool
|
||||
Persistent bool
|
||||
ToPorts bool
|
||||
RegProtoMin uint32
|
||||
RegProtoMax uint32
|
||||
}
|
||||
|
||||
const (
|
||||
// NF_NAT_RANGE_PROTO_RANDOM defines flag for a random masquerade
|
||||
NF_NAT_RANGE_PROTO_RANDOM = unix.NF_NAT_RANGE_PROTO_RANDOM
|
||||
// NF_NAT_RANGE_PROTO_RANDOM_FULLY defines flag for a fully random masquerade
|
||||
NF_NAT_RANGE_PROTO_RANDOM_FULLY = unix.NF_NAT_RANGE_PROTO_RANDOM_FULLY
|
||||
// NF_NAT_RANGE_PERSISTENT defines flag for a persistent masquerade
|
||||
NF_NAT_RANGE_PERSISTENT = unix.NF_NAT_RANGE_PERSISTENT
|
||||
// NF_NAT_RANGE_PREFIX defines flag for a prefix masquerade
|
||||
NF_NAT_RANGE_PREFIX = unix.NF_NAT_RANGE_NETMAP
|
||||
)
|
||||
|
||||
func (e *Masq) marshal(fam byte) ([]byte, error) {
|
||||
msgData := []byte{}
|
||||
if !e.ToPorts {
|
||||
flags := uint32(0)
|
||||
if e.Random {
|
||||
flags |= NF_NAT_RANGE_PROTO_RANDOM
|
||||
}
|
||||
if e.FullyRandom {
|
||||
flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY
|
||||
}
|
||||
if e.Persistent {
|
||||
flags |= NF_NAT_RANGE_PERSISTENT
|
||||
}
|
||||
if flags != 0 {
|
||||
flagsData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_MASQ_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgData = append(msgData, flagsData...)
|
||||
}
|
||||
} else {
|
||||
regsData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_MASQ_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMin)}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgData = append(msgData, regsData...)
|
||||
if e.RegProtoMax != 0 {
|
||||
regsData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_MASQ_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMax)}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgData = append(msgData, regsData...)
|
||||
}
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("masq\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: msgData},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Masq) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_MASQ_REG_PROTO_MIN:
|
||||
e.ToPorts = true
|
||||
e.RegProtoMin = ad.Uint32()
|
||||
case unix.NFTA_MASQ_REG_PROTO_MAX:
|
||||
e.RegProtoMax = ad.Uint32()
|
||||
case unix.NFTA_MASQ_FLAGS:
|
||||
flags := ad.Uint32()
|
||||
e.Persistent = (flags & NF_NAT_RANGE_PERSISTENT) != 0
|
||||
e.Random = (flags & NF_NAT_RANGE_PROTO_RANDOM) != 0
|
||||
e.FullyRandom = (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) != 0
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
|
||||
// CmpOp specifies which type of comparison should be performed.
|
||||
type CmpOp uint32
|
||||
|
||||
// Possible CmpOp values.
|
||||
const (
|
||||
CmpOpEq CmpOp = unix.NFT_CMP_EQ
|
||||
CmpOpNeq CmpOp = unix.NFT_CMP_NEQ
|
||||
CmpOpLt CmpOp = unix.NFT_CMP_LT
|
||||
CmpOpLte CmpOp = unix.NFT_CMP_LTE
|
||||
CmpOpGt CmpOp = unix.NFT_CMP_GT
|
||||
CmpOpGte CmpOp = unix.NFT_CMP_GTE
|
||||
)
|
||||
|
||||
// Cmp compares a register with the specified data.
|
||||
type Cmp struct {
|
||||
Op CmpOp
|
||||
Register uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (e *Cmp) marshal(fam byte) ([]byte, error) {
|
||||
cmpData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_DATA_VALUE, Data: e.Data},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_CMP_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
{Type: unix.NFTA_CMP_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_CMP_DATA, Data: cmpData},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("cmp\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Cmp) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_CMP_SREG:
|
||||
e.Register = ad.Uint32()
|
||||
case unix.NFTA_CMP_OP:
|
||||
e.Op = CmpOp(ad.Uint32())
|
||||
case unix.NFTA_CMP_DATA:
|
||||
ad.Do(func(b []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE {
|
||||
ad.Do(func(b []byte) error {
|
||||
e.Data = b
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return ad.Err()
|
||||
})
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
102
vendor/github.com/google/nftables/expr/exthdr.go
generated
vendored
Normal file
102
vendor/github.com/google/nftables/expr/exthdr.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type ExthdrOp uint32
|
||||
|
||||
const (
|
||||
ExthdrOpIpv6 ExthdrOp = unix.NFT_EXTHDR_OP_IPV6
|
||||
ExthdrOpTcpopt ExthdrOp = unix.NFT_EXTHDR_OP_TCPOPT
|
||||
)
|
||||
|
||||
type Exthdr struct {
|
||||
DestRegister uint32
|
||||
Type uint8
|
||||
Offset uint32
|
||||
Len uint32
|
||||
Flags uint32
|
||||
Op ExthdrOp
|
||||
SourceRegister uint32
|
||||
}
|
||||
|
||||
func (e *Exthdr) marshal(fam byte) ([]byte, error) {
|
||||
var attr []netlink.Attribute
|
||||
|
||||
// Operations are differentiated by the Op and whether the SourceRegister
|
||||
// or DestRegister is set. Mixing them results in EOPNOTSUPP.
|
||||
if e.SourceRegister != 0 {
|
||||
attr = []netlink.Attribute{
|
||||
{Type: unix.NFTA_EXTHDR_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)}}
|
||||
} else {
|
||||
attr = []netlink.Attribute{
|
||||
{Type: unix.NFTA_EXTHDR_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}}
|
||||
}
|
||||
|
||||
attr = append(attr,
|
||||
netlink.Attribute{Type: unix.NFTA_EXTHDR_TYPE, Data: []byte{e.Type}},
|
||||
netlink.Attribute{Type: unix.NFTA_EXTHDR_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)},
|
||||
netlink.Attribute{Type: unix.NFTA_EXTHDR_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)},
|
||||
netlink.Attribute{Type: unix.NFTA_EXTHDR_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))})
|
||||
|
||||
// Flags is only set if DREG is set
|
||||
if e.DestRegister != 0 {
|
||||
attr = append(attr,
|
||||
netlink.Attribute{Type: unix.NFTA_EXTHDR_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)})
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("exthdr\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Exthdr) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_EXTHDR_DREG:
|
||||
e.DestRegister = ad.Uint32()
|
||||
case unix.NFTA_EXTHDR_TYPE:
|
||||
e.Type = ad.Uint8()
|
||||
case unix.NFTA_EXTHDR_OFFSET:
|
||||
e.Offset = ad.Uint32()
|
||||
case unix.NFTA_EXTHDR_LEN:
|
||||
e.Len = ad.Uint32()
|
||||
case unix.NFTA_EXTHDR_FLAGS:
|
||||
e.Flags = ad.Uint32()
|
||||
case unix.NFTA_EXTHDR_OP:
|
||||
e.Op = ExthdrOp(ad.Uint32())
|
||||
case unix.NFTA_EXTHDR_SREG:
|
||||
e.SourceRegister = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
128
vendor/github.com/google/nftables/expr/fib.go
generated
vendored
Normal file
128
vendor/github.com/google/nftables/expr/fib.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Fib defines fib expression structure
|
||||
type Fib struct {
|
||||
Register uint32
|
||||
ResultOIF bool
|
||||
ResultOIFNAME bool
|
||||
ResultADDRTYPE bool
|
||||
FlagSADDR bool
|
||||
FlagDADDR bool
|
||||
FlagMARK bool
|
||||
FlagIIF bool
|
||||
FlagOIF bool
|
||||
FlagPRESENT bool
|
||||
}
|
||||
|
||||
func (e *Fib) marshal(fam byte) ([]byte, error) {
|
||||
data := []byte{}
|
||||
reg, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_FIB_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, reg...)
|
||||
flags := uint32(0)
|
||||
if e.FlagSADDR {
|
||||
flags |= unix.NFTA_FIB_F_SADDR
|
||||
}
|
||||
if e.FlagDADDR {
|
||||
flags |= unix.NFTA_FIB_F_DADDR
|
||||
}
|
||||
if e.FlagMARK {
|
||||
flags |= unix.NFTA_FIB_F_MARK
|
||||
}
|
||||
if e.FlagIIF {
|
||||
flags |= unix.NFTA_FIB_F_IIF
|
||||
}
|
||||
if e.FlagOIF {
|
||||
flags |= unix.NFTA_FIB_F_OIF
|
||||
}
|
||||
if e.FlagPRESENT {
|
||||
flags |= unix.NFTA_FIB_F_PRESENT
|
||||
}
|
||||
if flags != 0 {
|
||||
flg, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_FIB_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, flg...)
|
||||
}
|
||||
results := uint32(0)
|
||||
if e.ResultOIF {
|
||||
results |= unix.NFT_FIB_RESULT_OIF
|
||||
}
|
||||
if e.ResultOIFNAME {
|
||||
results |= unix.NFT_FIB_RESULT_OIFNAME
|
||||
}
|
||||
if e.ResultADDRTYPE {
|
||||
results |= unix.NFT_FIB_RESULT_ADDRTYPE
|
||||
}
|
||||
if results != 0 {
|
||||
rslt, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_FIB_RESULT, Data: binaryutil.BigEndian.PutUint32(results)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, rslt...)
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("fib\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Fib) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_FIB_DREG:
|
||||
e.Register = ad.Uint32()
|
||||
case unix.NFTA_FIB_RESULT:
|
||||
result := ad.Uint32()
|
||||
e.ResultOIF = (result & unix.NFT_FIB_RESULT_OIF) == 1
|
||||
e.ResultOIFNAME = (result & unix.NFT_FIB_RESULT_OIFNAME) == 1
|
||||
e.ResultADDRTYPE = (result & unix.NFT_FIB_RESULT_ADDRTYPE) == 1
|
||||
case unix.NFTA_FIB_FLAGS:
|
||||
flags := ad.Uint32()
|
||||
e.FlagSADDR = (flags & unix.NFTA_FIB_F_SADDR) == 1
|
||||
e.FlagDADDR = (flags & unix.NFTA_FIB_F_DADDR) == 1
|
||||
e.FlagMARK = (flags & unix.NFTA_FIB_F_MARK) == 1
|
||||
e.FlagIIF = (flags & unix.NFTA_FIB_F_IIF) == 1
|
||||
e.FlagOIF = (flags & unix.NFTA_FIB_F_OIF) == 1
|
||||
e.FlagPRESENT = (flags & unix.NFTA_FIB_F_PRESENT) == 1
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
59
vendor/github.com/google/nftables/expr/flow_offload.go
generated
vendored
Normal file
59
vendor/github.com/google/nftables/expr/flow_offload.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const NFTNL_EXPR_FLOW_TABLE_NAME = 1
|
||||
|
||||
type FlowOffload struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e *FlowOffload) marshal(fam byte) ([]byte, error) {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: NFTNL_EXPR_FLOW_TABLE_NAME, Data: []byte(e.Name)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("flow_offload\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *FlowOffload) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case NFTNL_EXPR_FLOW_TABLE_NAME:
|
||||
e.Name = ad.String()
|
||||
}
|
||||
}
|
||||
|
||||
return ad.Err()
|
||||
}
|
||||
94
vendor/github.com/google/nftables/expr/hash.go
generated
vendored
Normal file
94
vendor/github.com/google/nftables/expr/hash.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type HashType uint32
|
||||
|
||||
const (
|
||||
HashTypeJenkins HashType = unix.NFT_HASH_JENKINS
|
||||
HashTypeSym HashType = unix.NFT_HASH_SYM
|
||||
)
|
||||
|
||||
// Hash defines type for nftables internal hashing functions
|
||||
type Hash struct {
|
||||
SourceRegister uint32
|
||||
DestRegister uint32
|
||||
Length uint32
|
||||
Modulus uint32
|
||||
Seed uint32
|
||||
Offset uint32
|
||||
Type HashType
|
||||
}
|
||||
|
||||
func (e *Hash) marshal(fam byte) ([]byte, error) {
|
||||
hashAttrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_HASH_SREG, Data: binaryutil.BigEndian.PutUint32(uint32(e.SourceRegister))},
|
||||
{Type: unix.NFTA_HASH_DREG, Data: binaryutil.BigEndian.PutUint32(uint32(e.DestRegister))},
|
||||
{Type: unix.NFTA_HASH_LEN, Data: binaryutil.BigEndian.PutUint32(uint32(e.Length))},
|
||||
{Type: unix.NFTA_HASH_MODULUS, Data: binaryutil.BigEndian.PutUint32(uint32(e.Modulus))},
|
||||
}
|
||||
if e.Seed != 0 {
|
||||
hashAttrs = append(hashAttrs, netlink.Attribute{
|
||||
Type: unix.NFTA_HASH_SEED, Data: binaryutil.BigEndian.PutUint32(uint32(e.Seed)),
|
||||
})
|
||||
}
|
||||
hashAttrs = append(hashAttrs, []netlink.Attribute{
|
||||
{Type: unix.NFTA_HASH_OFFSET, Data: binaryutil.BigEndian.PutUint32(uint32(e.Offset))},
|
||||
{Type: unix.NFTA_HASH_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))},
|
||||
}...)
|
||||
data, err := netlink.MarshalAttributes(hashAttrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("hash\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Hash) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_HASH_SREG:
|
||||
e.SourceRegister = ad.Uint32()
|
||||
case unix.NFTA_HASH_DREG:
|
||||
e.DestRegister = ad.Uint32()
|
||||
case unix.NFTA_HASH_LEN:
|
||||
e.Length = ad.Uint32()
|
||||
case unix.NFTA_HASH_MODULUS:
|
||||
e.Modulus = ad.Uint32()
|
||||
case unix.NFTA_HASH_SEED:
|
||||
e.Seed = ad.Uint32()
|
||||
case unix.NFTA_HASH_OFFSET:
|
||||
e.Offset = ad.Uint32()
|
||||
case unix.NFTA_HASH_TYPE:
|
||||
e.Type = HashType(ad.Uint32())
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
79
vendor/github.com/google/nftables/expr/immediate.go
generated
vendored
Normal file
79
vendor/github.com/google/nftables/expr/immediate.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Immediate struct {
|
||||
Register uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (e *Immediate) marshal(fam byte) ([]byte, error) {
|
||||
immData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_DATA_VALUE, Data: e.Data},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_IMMEDIATE_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_IMMEDIATE_DATA, Data: immData},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("immediate\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Immediate) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_IMMEDIATE_DREG:
|
||||
e.Register = ad.Uint32()
|
||||
case unix.NFTA_IMMEDIATE_DATA:
|
||||
nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err)
|
||||
}
|
||||
for nestedAD.Next() {
|
||||
switch nestedAD.Type() {
|
||||
case unix.NFTA_DATA_VALUE:
|
||||
e.Data = nestedAD.Bytes()
|
||||
}
|
||||
}
|
||||
if nestedAD.Err() != nil {
|
||||
return fmt.Errorf("decoding immediate: %v", nestedAD.Err())
|
||||
}
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
128
vendor/github.com/google/nftables/expr/limit.go
generated
vendored
Normal file
128
vendor/github.com/google/nftables/expr/limit.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// LimitType represents the type of the limit expression.
|
||||
type LimitType uint32
|
||||
|
||||
// Imported from the nft_limit_type enum in netfilter/nf_tables.h.
|
||||
const (
|
||||
LimitTypePkts LimitType = unix.NFT_LIMIT_PKTS
|
||||
LimitTypePktBytes LimitType = unix.NFT_LIMIT_PKT_BYTES
|
||||
)
|
||||
|
||||
// LimitTime represents the limit unit.
|
||||
type LimitTime uint64
|
||||
|
||||
// Possible limit unit values.
|
||||
const (
|
||||
LimitTimeSecond LimitTime = 1
|
||||
LimitTimeMinute LimitTime = 60
|
||||
LimitTimeHour LimitTime = 60 * 60
|
||||
LimitTimeDay LimitTime = 60 * 60 * 24
|
||||
LimitTimeWeek LimitTime = 60 * 60 * 24 * 7
|
||||
)
|
||||
|
||||
func limitTime(value uint64) (LimitTime, error) {
|
||||
switch LimitTime(value) {
|
||||
case LimitTimeSecond:
|
||||
return LimitTimeSecond, nil
|
||||
case LimitTimeMinute:
|
||||
return LimitTimeMinute, nil
|
||||
case LimitTimeHour:
|
||||
return LimitTimeHour, nil
|
||||
case LimitTimeDay:
|
||||
return LimitTimeDay, nil
|
||||
case LimitTimeWeek:
|
||||
return LimitTimeWeek, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("expr: invalid limit unit value %d", value)
|
||||
}
|
||||
}
|
||||
|
||||
// Limit represents a rate limit expression.
|
||||
type Limit struct {
|
||||
Type LimitType
|
||||
Rate uint64
|
||||
Over bool
|
||||
Unit LimitTime
|
||||
Burst uint32
|
||||
}
|
||||
|
||||
func (l *Limit) marshal(fam byte) ([]byte, error) {
|
||||
var flags uint32
|
||||
if l.Over {
|
||||
flags = unix.NFT_LIMIT_F_INV
|
||||
}
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_LIMIT_RATE, Data: binaryutil.BigEndian.PutUint64(l.Rate)},
|
||||
{Type: unix.NFTA_LIMIT_UNIT, Data: binaryutil.BigEndian.PutUint64(uint64(l.Unit))},
|
||||
{Type: unix.NFTA_LIMIT_BURST, Data: binaryutil.BigEndian.PutUint32(l.Burst)},
|
||||
{Type: unix.NFTA_LIMIT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(l.Type))},
|
||||
{Type: unix.NFTA_LIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)},
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("limit\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Limit) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_LIMIT_RATE:
|
||||
l.Rate = ad.Uint64()
|
||||
case unix.NFTA_LIMIT_UNIT:
|
||||
l.Unit, err = limitTime(ad.Uint64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case unix.NFTA_LIMIT_BURST:
|
||||
l.Burst = ad.Uint32()
|
||||
case unix.NFTA_LIMIT_TYPE:
|
||||
l.Type = LimitType(ad.Uint32())
|
||||
if l.Type != LimitTypePkts && l.Type != LimitTypePktBytes {
|
||||
return fmt.Errorf("expr: invalid limit type %d", l.Type)
|
||||
}
|
||||
case unix.NFTA_LIMIT_FLAGS:
|
||||
l.Over = (ad.Uint32() & unix.NFT_LIMIT_F_INV) == 1
|
||||
default:
|
||||
return errors.New("expr: unhandled limit netlink attribute")
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
150
vendor/github.com/google/nftables/expr/log.go
generated
vendored
Normal file
150
vendor/github.com/google/nftables/expr/log.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type LogLevel uint32
|
||||
|
||||
const (
|
||||
// See https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=5b364657a35f4e4cd5d220ba2a45303d729c8eca#n1226
|
||||
LogLevelEmerg LogLevel = iota
|
||||
LogLevelAlert
|
||||
LogLevelCrit
|
||||
LogLevelErr
|
||||
LogLevelWarning
|
||||
LogLevelNotice
|
||||
LogLevelInfo
|
||||
LogLevelDebug
|
||||
LogLevelAudit
|
||||
)
|
||||
|
||||
type LogFlags uint32
|
||||
|
||||
const (
|
||||
// See https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_log.h?id=5b364657a35f4e4cd5d220ba2a45303d729c8eca
|
||||
LogFlagsTCPSeq LogFlags = 0x01 << iota
|
||||
LogFlagsTCPOpt
|
||||
LogFlagsIPOpt
|
||||
LogFlagsUID
|
||||
LogFlagsNFLog
|
||||
LogFlagsMACDecode
|
||||
LogFlagsMask LogFlags = 0x2f
|
||||
)
|
||||
|
||||
// Log defines type for NFT logging
|
||||
// See https://git.netfilter.org/libnftnl/tree/src/expr/log.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n25
|
||||
type Log struct {
|
||||
Level LogLevel
|
||||
// Refers to log flags (flags all, flags ip options, ...)
|
||||
Flags LogFlags
|
||||
// Equivalent to expression flags.
|
||||
// Indicates that an option is set by setting a bit
|
||||
// on index referred by the NFTA_LOG_* value.
|
||||
// See https://cs.opensource.google/go/x/sys/+/3681064d:unix/ztypes_linux.go;l=2126;drc=3681064d51587c1db0324b3d5c23c2ddbcff6e8f
|
||||
Key uint32
|
||||
Snaplen uint32
|
||||
Group uint16
|
||||
QThreshold uint16
|
||||
// Log prefix string content
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (e *Log) marshal(fam byte) ([]byte, error) {
|
||||
// Per https://git.netfilter.org/libnftnl/tree/src/expr/log.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n129
|
||||
attrs := make([]netlink.Attribute, 0)
|
||||
if e.Key&(1<<unix.NFTA_LOG_GROUP) != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: unix.NFTA_LOG_GROUP,
|
||||
Data: binaryutil.BigEndian.PutUint16(e.Group),
|
||||
})
|
||||
}
|
||||
if e.Key&(1<<unix.NFTA_LOG_PREFIX) != 0 {
|
||||
prefix := append(e.Data, '\x00')
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: unix.NFTA_LOG_PREFIX,
|
||||
Data: prefix,
|
||||
})
|
||||
}
|
||||
if e.Key&(1<<unix.NFTA_LOG_SNAPLEN) != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: unix.NFTA_LOG_SNAPLEN,
|
||||
Data: binaryutil.BigEndian.PutUint32(e.Snaplen),
|
||||
})
|
||||
}
|
||||
if e.Key&(1<<unix.NFTA_LOG_QTHRESHOLD) != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: unix.NFTA_LOG_QTHRESHOLD,
|
||||
Data: binaryutil.BigEndian.PutUint16(e.QThreshold),
|
||||
})
|
||||
}
|
||||
if e.Key&(1<<unix.NFTA_LOG_LEVEL) != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: unix.NFTA_LOG_LEVEL,
|
||||
Data: binaryutil.BigEndian.PutUint32(uint32(e.Level)),
|
||||
})
|
||||
}
|
||||
if e.Key&(1<<unix.NFTA_LOG_FLAGS) != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: unix.NFTA_LOG_FLAGS,
|
||||
Data: binaryutil.BigEndian.PutUint32(uint32(e.Flags)),
|
||||
})
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("log\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Log) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
e.Key |= 1 << uint32(ad.Type())
|
||||
data := ad.Bytes()
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_LOG_GROUP:
|
||||
e.Group = binaryutil.BigEndian.Uint16(data)
|
||||
case unix.NFTA_LOG_PREFIX:
|
||||
// Getting rid of \x00 at the end of string
|
||||
e.Data = data[:len(data)-1]
|
||||
case unix.NFTA_LOG_SNAPLEN:
|
||||
e.Snaplen = binaryutil.BigEndian.Uint32(data)
|
||||
case unix.NFTA_LOG_QTHRESHOLD:
|
||||
e.QThreshold = binaryutil.BigEndian.Uint16(data)
|
||||
case unix.NFTA_LOG_LEVEL:
|
||||
e.Level = LogLevel(binaryutil.BigEndian.Uint32(data))
|
||||
case unix.NFTA_LOG_FLAGS:
|
||||
e.Flags = LogFlags(binaryutil.BigEndian.Uint32(data))
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
85
vendor/github.com/google/nftables/expr/lookup.go
generated
vendored
Normal file
85
vendor/github.com/google/nftables/expr/lookup.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Lookup represents a match against the contents of a set.
|
||||
type Lookup struct {
|
||||
SourceRegister uint32
|
||||
DestRegister uint32
|
||||
IsDestRegSet bool
|
||||
|
||||
SetID uint32
|
||||
SetName string
|
||||
Invert bool
|
||||
}
|
||||
|
||||
func (e *Lookup) marshal(fam byte) ([]byte, error) {
|
||||
// See: https://git.netfilter.org/libnftnl/tree/src/expr/lookup.c?id=6dc1c3d8bb64077da7f3f28c7368fb087d10a492#n115
|
||||
var opAttrs []netlink.Attribute
|
||||
if e.SourceRegister != 0 {
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_LOOKUP_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)})
|
||||
}
|
||||
if e.IsDestRegSet {
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_LOOKUP_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)})
|
||||
}
|
||||
if e.Invert {
|
||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_LOOKUP_FLAGS, Data: binaryutil.BigEndian.PutUint32(unix.NFT_LOOKUP_F_INV)})
|
||||
}
|
||||
opAttrs = append(opAttrs,
|
||||
netlink.Attribute{Type: unix.NFTA_LOOKUP_SET, Data: []byte(e.SetName + "\x00")},
|
||||
netlink.Attribute{Type: unix.NFTA_LOOKUP_SET_ID, Data: binaryutil.BigEndian.PutUint32(e.SetID)},
|
||||
)
|
||||
opData, err := netlink.MarshalAttributes(opAttrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("lookup\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: opData},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Lookup) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_LOOKUP_SET:
|
||||
e.SetName = ad.String()
|
||||
case unix.NFTA_LOOKUP_SET_ID:
|
||||
e.SetID = ad.Uint32()
|
||||
case unix.NFTA_LOOKUP_SREG:
|
||||
e.SourceRegister = ad.Uint32()
|
||||
case unix.NFTA_LOOKUP_DREG:
|
||||
e.DestRegister = ad.Uint32()
|
||||
e.IsDestRegSet = true
|
||||
case unix.NFTA_LOOKUP_FLAGS:
|
||||
e.Invert = (ad.Uint32() & unix.NFT_LOOKUP_F_INV) != 0
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
75
vendor/github.com/google/nftables/expr/match.go
generated
vendored
Normal file
75
vendor/github.com/google/nftables/expr/match.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/xt"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// See https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n30
|
||||
type Match struct {
|
||||
Name string
|
||||
Rev uint32
|
||||
Info xt.InfoAny
|
||||
}
|
||||
|
||||
func (e *Match) marshal(fam byte) ([]byte, error) {
|
||||
// Per https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n38
|
||||
name := e.Name
|
||||
// limit the extension name as (some) user-space tools do and leave room for
|
||||
// trailing \x00
|
||||
if len(name) >= /* sic! */ XTablesExtensionNameMaxLen {
|
||||
name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00.
|
||||
}
|
||||
// Marshalling assumes that the correct Info type for the particular table
|
||||
// family and Match revision has been set.
|
||||
info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_MATCH_NAME, Data: []byte(name + "\x00")},
|
||||
{Type: unix.NFTA_MATCH_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)},
|
||||
{Type: unix.NFTA_MATCH_INFO, Data: info},
|
||||
}
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("match\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Match) unmarshal(fam byte, data []byte) error {
|
||||
// Per https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info []byte
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_MATCH_NAME:
|
||||
// We are forgiving here, accepting any length and even missing terminating \x00.
|
||||
e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00"))
|
||||
case unix.NFTA_MATCH_REV:
|
||||
e.Rev = ad.Uint32()
|
||||
case unix.NFTA_MATCH_INFO:
|
||||
info = ad.Bytes()
|
||||
}
|
||||
}
|
||||
if err = ad.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info)
|
||||
return err
|
||||
}
|
||||
132
vendor/github.com/google/nftables/expr/nat.go
generated
vendored
Normal file
132
vendor/github.com/google/nftables/expr/nat.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type NATType uint32
|
||||
|
||||
// Possible NATType values.
|
||||
const (
|
||||
NATTypeSourceNAT NATType = unix.NFT_NAT_SNAT
|
||||
NATTypeDestNAT NATType = unix.NFT_NAT_DNAT
|
||||
)
|
||||
|
||||
type NAT struct {
|
||||
Type NATType
|
||||
Family uint32 // TODO: typed const
|
||||
RegAddrMin uint32
|
||||
RegAddrMax uint32
|
||||
RegProtoMin uint32
|
||||
RegProtoMax uint32
|
||||
Random bool
|
||||
FullyRandom bool
|
||||
Persistent bool
|
||||
Prefix bool
|
||||
}
|
||||
|
||||
// |00048|N-|00001| |len |flags| type|
|
||||
// |00008|--|00001| |len |flags| type|
|
||||
// | 6e 61 74 00 | | data | n a t
|
||||
// |00036|N-|00002| |len |flags| type|
|
||||
// |00008|--|00001| |len |flags| type| NFTA_NAT_TYPE
|
||||
// | 00 00 00 01 | | data | NFT_NAT_DNAT
|
||||
// |00008|--|00002| |len |flags| type| NFTA_NAT_FAMILY
|
||||
// | 00 00 00 02 | | data | NFPROTO_IPV4
|
||||
// |00008|--|00003| |len |flags| type| NFTA_NAT_REG_ADDR_MIN
|
||||
// | 00 00 00 01 | | data | reg 1
|
||||
// |00008|--|00005| |len |flags| type| NFTA_NAT_REG_PROTO_MIN
|
||||
// | 00 00 00 02 | | data | reg 2
|
||||
|
||||
func (e *NAT) marshal(fam byte) ([]byte, error) {
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_NAT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))},
|
||||
{Type: unix.NFTA_NAT_FAMILY, Data: binaryutil.BigEndian.PutUint32(e.Family)},
|
||||
}
|
||||
if e.RegAddrMin != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_ADDR_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegAddrMin)})
|
||||
if e.RegAddrMax != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_ADDR_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegAddrMax)})
|
||||
}
|
||||
}
|
||||
if e.RegProtoMin != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMin)})
|
||||
if e.RegProtoMax != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMax)})
|
||||
}
|
||||
}
|
||||
flags := uint32(0)
|
||||
if e.Random {
|
||||
flags |= NF_NAT_RANGE_PROTO_RANDOM
|
||||
}
|
||||
if e.FullyRandom {
|
||||
flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY
|
||||
}
|
||||
if e.Persistent {
|
||||
flags |= NF_NAT_RANGE_PERSISTENT
|
||||
}
|
||||
if e.Prefix {
|
||||
flags |= NF_NAT_RANGE_PREFIX
|
||||
}
|
||||
if flags != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_NAT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)})
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("nat\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *NAT) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_NAT_TYPE:
|
||||
e.Type = NATType(ad.Uint32())
|
||||
case unix.NFTA_NAT_FAMILY:
|
||||
e.Family = ad.Uint32()
|
||||
case unix.NFTA_NAT_REG_ADDR_MIN:
|
||||
e.RegAddrMin = ad.Uint32()
|
||||
case unix.NFTA_NAT_REG_ADDR_MAX:
|
||||
e.RegAddrMax = ad.Uint32()
|
||||
case unix.NFTA_NAT_REG_PROTO_MIN:
|
||||
e.RegProtoMin = ad.Uint32()
|
||||
case unix.NFTA_NAT_REG_PROTO_MAX:
|
||||
e.RegProtoMax = ad.Uint32()
|
||||
case unix.NFTA_NAT_FLAGS:
|
||||
flags := ad.Uint32()
|
||||
e.Persistent = (flags & NF_NAT_RANGE_PERSISTENT) != 0
|
||||
e.Random = (flags & NF_NAT_RANGE_PROTO_RANDOM) != 0
|
||||
e.FullyRandom = (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) != 0
|
||||
e.Prefix = (flags & NF_NAT_RANGE_PREFIX) != 0
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
38
vendor/github.com/google/nftables/expr/notrack.go
generated
vendored
Normal file
38
vendor/github.com/google/nftables/expr/notrack.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Notrack struct{}
|
||||
|
||||
func (e *Notrack) marshal(fam byte) ([]byte, error) {
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("notrack\x00")},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Notrack) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ad.Err()
|
||||
}
|
||||
78
vendor/github.com/google/nftables/expr/numgen.go
generated
vendored
Normal file
78
vendor/github.com/google/nftables/expr/numgen.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Numgen defines Numgen expression structure
|
||||
type Numgen struct {
|
||||
Register uint32
|
||||
Modulus uint32
|
||||
Type uint32
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
func (e *Numgen) marshal(fam byte) ([]byte, error) {
|
||||
// Currently only two types are supported, failing if Type is not of two known types
|
||||
switch e.Type {
|
||||
case unix.NFT_NG_INCREMENTAL:
|
||||
case unix.NFT_NG_RANDOM:
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported numgen type %d", e.Type)
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_NG_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
{Type: unix.NFTA_NG_MODULUS, Data: binaryutil.BigEndian.PutUint32(e.Modulus)},
|
||||
{Type: unix.NFTA_NG_TYPE, Data: binaryutil.BigEndian.PutUint32(e.Type)},
|
||||
{Type: unix.NFTA_NG_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("numgen\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Numgen) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_NG_DREG:
|
||||
e.Register = ad.Uint32()
|
||||
case unix.NFTA_NG_MODULUS:
|
||||
e.Modulus = ad.Uint32()
|
||||
case unix.NFTA_NG_TYPE:
|
||||
e.Type = ad.Uint32()
|
||||
case unix.NFTA_NG_OFFSET:
|
||||
e.Offset = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
60
vendor/github.com/google/nftables/expr/objref.go
generated
vendored
Normal file
60
vendor/github.com/google/nftables/expr/objref.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Objref struct {
|
||||
Type int // TODO: enum
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e *Objref) marshal(fam byte) ([]byte, error) {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_OBJREF_IMM_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))},
|
||||
{Type: unix.NFTA_OBJREF_IMM_NAME, Data: []byte(e.Name)}, // NOT \x00-terminated?!
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("objref\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Objref) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_OBJREF_IMM_TYPE:
|
||||
e.Type = int(ad.Uint32())
|
||||
case unix.NFTA_OBJREF_IMM_NAME:
|
||||
e.Name = ad.String()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
131
vendor/github.com/google/nftables/expr/payload.go
generated
vendored
Normal file
131
vendor/github.com/google/nftables/expr/payload.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type PayloadBase uint32
|
||||
type PayloadCsumType uint32
|
||||
type PayloadOperationType uint32
|
||||
|
||||
// Possible PayloadBase values.
|
||||
const (
|
||||
PayloadBaseLLHeader PayloadBase = unix.NFT_PAYLOAD_LL_HEADER
|
||||
PayloadBaseNetworkHeader PayloadBase = unix.NFT_PAYLOAD_NETWORK_HEADER
|
||||
PayloadBaseTransportHeader PayloadBase = unix.NFT_PAYLOAD_TRANSPORT_HEADER
|
||||
)
|
||||
|
||||
// Possible PayloadCsumType values.
|
||||
const (
|
||||
CsumTypeNone PayloadCsumType = unix.NFT_PAYLOAD_CSUM_NONE
|
||||
CsumTypeInet PayloadCsumType = unix.NFT_PAYLOAD_CSUM_INET
|
||||
)
|
||||
|
||||
// Possible PayloadOperationType values.
|
||||
const (
|
||||
PayloadLoad PayloadOperationType = iota
|
||||
PayloadWrite
|
||||
)
|
||||
|
||||
type Payload struct {
|
||||
OperationType PayloadOperationType
|
||||
DestRegister uint32
|
||||
SourceRegister uint32
|
||||
Base PayloadBase
|
||||
Offset uint32
|
||||
Len uint32
|
||||
CsumType PayloadCsumType
|
||||
CsumOffset uint32
|
||||
CsumFlags uint32
|
||||
}
|
||||
|
||||
func (e *Payload) marshal(fam byte) ([]byte, error) {
|
||||
|
||||
var attrs []netlink.Attribute
|
||||
|
||||
if e.OperationType == PayloadWrite {
|
||||
attrs = []netlink.Attribute{
|
||||
{Type: unix.NFTA_PAYLOAD_SREG, Data: binaryutil.BigEndian.PutUint32(e.SourceRegister)},
|
||||
}
|
||||
} else {
|
||||
attrs = []netlink.Attribute{
|
||||
{Type: unix.NFTA_PAYLOAD_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)},
|
||||
}
|
||||
}
|
||||
|
||||
attrs = append(attrs,
|
||||
netlink.Attribute{Type: unix.NFTA_PAYLOAD_BASE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Base))},
|
||||
netlink.Attribute{Type: unix.NFTA_PAYLOAD_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)},
|
||||
netlink.Attribute{Type: unix.NFTA_PAYLOAD_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)},
|
||||
)
|
||||
|
||||
if e.CsumType > 0 {
|
||||
attrs = append(attrs,
|
||||
netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.CsumType))},
|
||||
netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_OFFSET, Data: binaryutil.BigEndian.PutUint32(uint32(e.CsumOffset))},
|
||||
)
|
||||
if e.CsumFlags > 0 {
|
||||
attrs = append(attrs,
|
||||
netlink.Attribute{Type: unix.NFTA_PAYLOAD_CSUM_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.CsumFlags)},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("payload\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Payload) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_PAYLOAD_DREG:
|
||||
e.DestRegister = ad.Uint32()
|
||||
case unix.NFTA_PAYLOAD_SREG:
|
||||
e.SourceRegister = ad.Uint32()
|
||||
e.OperationType = PayloadWrite
|
||||
case unix.NFTA_PAYLOAD_BASE:
|
||||
e.Base = PayloadBase(ad.Uint32())
|
||||
case unix.NFTA_PAYLOAD_OFFSET:
|
||||
e.Offset = ad.Uint32()
|
||||
case unix.NFTA_PAYLOAD_LEN:
|
||||
e.Len = ad.Uint32()
|
||||
case unix.NFTA_PAYLOAD_CSUM_TYPE:
|
||||
e.CsumType = PayloadCsumType(ad.Uint32())
|
||||
case unix.NFTA_PAYLOAD_CSUM_OFFSET:
|
||||
e.CsumOffset = ad.Uint32()
|
||||
case unix.NFTA_PAYLOAD_CSUM_FLAGS:
|
||||
e.CsumFlags = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
81
vendor/github.com/google/nftables/expr/queue.go
generated
vendored
Normal file
81
vendor/github.com/google/nftables/expr/queue.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type QueueAttribute uint16
|
||||
|
||||
type QueueFlag uint16
|
||||
|
||||
// Possible QueueAttribute values
|
||||
const (
|
||||
QueueNum QueueAttribute = unix.NFTA_QUEUE_NUM
|
||||
QueueTotal QueueAttribute = unix.NFTA_QUEUE_TOTAL
|
||||
QueueFlags QueueAttribute = unix.NFTA_QUEUE_FLAGS
|
||||
|
||||
QueueFlagBypass QueueFlag = unix.NFT_QUEUE_FLAG_BYPASS
|
||||
QueueFlagFanout QueueFlag = unix.NFT_QUEUE_FLAG_CPU_FANOUT
|
||||
QueueFlagMask QueueFlag = unix.NFT_QUEUE_FLAG_MASK
|
||||
)
|
||||
|
||||
type Queue struct {
|
||||
Num uint16
|
||||
Total uint16
|
||||
Flag QueueFlag
|
||||
}
|
||||
|
||||
func (e *Queue) marshal(fam byte) ([]byte, error) {
|
||||
if e.Total == 0 {
|
||||
e.Total = 1 // The total default value is 1
|
||||
}
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_QUEUE_NUM, Data: binaryutil.BigEndian.PutUint16(e.Num)},
|
||||
{Type: unix.NFTA_QUEUE_TOTAL, Data: binaryutil.BigEndian.PutUint16(e.Total)},
|
||||
{Type: unix.NFTA_QUEUE_FLAGS, Data: binaryutil.BigEndian.PutUint16(uint16(e.Flag))},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("queue\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Queue) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_QUEUE_NUM:
|
||||
e.Num = ad.Uint16()
|
||||
case unix.NFTA_QUEUE_TOTAL:
|
||||
e.Total = ad.Uint16()
|
||||
case unix.NFTA_QUEUE_FLAGS:
|
||||
e.Flag = QueueFlag(ad.Uint16())
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
76
vendor/github.com/google/nftables/expr/quota.go
generated
vendored
Normal file
76
vendor/github.com/google/nftables/expr/quota.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Quota defines a threshold against a number of bytes.
|
||||
type Quota struct {
|
||||
Bytes uint64
|
||||
Consumed uint64
|
||||
Over bool
|
||||
}
|
||||
|
||||
func (q *Quota) marshal(fam byte) ([]byte, error) {
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_QUOTA_BYTES, Data: binaryutil.BigEndian.PutUint64(q.Bytes)},
|
||||
{Type: unix.NFTA_QUOTA_CONSUMED, Data: binaryutil.BigEndian.PutUint64(q.Consumed)},
|
||||
}
|
||||
|
||||
flags := uint32(0)
|
||||
if q.Over {
|
||||
flags = unix.NFT_QUOTA_F_INV
|
||||
}
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: unix.NFTA_QUOTA_FLAGS,
|
||||
Data: binaryutil.BigEndian.PutUint32(flags),
|
||||
})
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("quota\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (q *Quota) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_QUOTA_BYTES:
|
||||
q.Bytes = ad.Uint64()
|
||||
case unix.NFTA_QUOTA_CONSUMED:
|
||||
q.Consumed = ad.Uint64()
|
||||
case unix.NFTA_QUOTA_FLAGS:
|
||||
q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) == 1
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
124
vendor/github.com/google/nftables/expr/range.go
generated
vendored
Normal file
124
vendor/github.com/google/nftables/expr/range.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Range implements range expression
|
||||
type Range struct {
|
||||
Op CmpOp
|
||||
Register uint32
|
||||
FromData []byte
|
||||
ToData []byte
|
||||
}
|
||||
|
||||
func (e *Range) marshal(fam byte) ([]byte, error) {
|
||||
var attrs []netlink.Attribute
|
||||
var err error
|
||||
var rangeFromData, rangeToData []byte
|
||||
|
||||
if e.Register > 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)})
|
||||
}
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))})
|
||||
if len(e.FromData) > 0 {
|
||||
rangeFromData, err = nestedAttr(e.FromData, unix.NFTA_RANGE_FROM_DATA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(e.ToData) > 0 {
|
||||
rangeToData, err = nestedAttr(e.ToData, unix.NFTA_RANGE_TO_DATA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, rangeFromData...)
|
||||
data = append(data, rangeToData...)
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("range\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Range) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_RANGE_OP:
|
||||
e.Op = CmpOp(ad.Uint32())
|
||||
case unix.NFTA_RANGE_SREG:
|
||||
e.Register = ad.Uint32()
|
||||
case unix.NFTA_RANGE_FROM_DATA:
|
||||
ad.Do(func(b []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE {
|
||||
ad.Do(func(b []byte) error {
|
||||
e.FromData = b
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return ad.Err()
|
||||
})
|
||||
case unix.NFTA_RANGE_TO_DATA:
|
||||
ad.Do(func(b []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE {
|
||||
ad.Do(func(b []byte) error {
|
||||
e.ToData = b
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return ad.Err()
|
||||
})
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
|
||||
func nestedAttr(data []byte, attrType uint16) ([]byte, error) {
|
||||
ae := netlink.NewAttributeEncoder()
|
||||
ae.Do(unix.NLA_F_NESTED|attrType, func() ([]byte, error) {
|
||||
nae := netlink.NewAttributeEncoder()
|
||||
nae.ByteOrder = binary.BigEndian
|
||||
nae.Bytes(unix.NFTA_DATA_VALUE, data)
|
||||
|
||||
return nae.Encode()
|
||||
})
|
||||
return ae.Encode()
|
||||
}
|
||||
71
vendor/github.com/google/nftables/expr/redirect.go
generated
vendored
Normal file
71
vendor/github.com/google/nftables/expr/redirect.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Redir struct {
|
||||
RegisterProtoMin uint32
|
||||
RegisterProtoMax uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
func (e *Redir) marshal(fam byte) ([]byte, error) {
|
||||
var attrs []netlink.Attribute
|
||||
if e.RegisterProtoMin > 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMin)})
|
||||
}
|
||||
if e.RegisterProtoMax > 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMax)})
|
||||
}
|
||||
if e.Flags > 0 {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)})
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("redir\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Redir) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_REDIR_REG_PROTO_MIN:
|
||||
e.RegisterProtoMin = ad.Uint32()
|
||||
case unix.NFTA_REDIR_REG_PROTO_MAX:
|
||||
e.RegisterProtoMax = ad.Uint32()
|
||||
case unix.NFTA_REDIR_FLAGS:
|
||||
e.Flags = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
59
vendor/github.com/google/nftables/expr/reject.go
generated
vendored
Normal file
59
vendor/github.com/google/nftables/expr/reject.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Reject struct {
|
||||
Type uint32
|
||||
Code uint8
|
||||
}
|
||||
|
||||
func (e *Reject) marshal(fam byte) ([]byte, error) {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_REJECT_TYPE, Data: binaryutil.BigEndian.PutUint32(e.Type)},
|
||||
{Type: unix.NFTA_REJECT_ICMP_CODE, Data: []byte{e.Code}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("reject\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Reject) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_REJECT_TYPE:
|
||||
e.Type = ad.Uint32()
|
||||
case unix.NFTA_REJECT_ICMP_CODE:
|
||||
e.Code = ad.Uint8()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
55
vendor/github.com/google/nftables/expr/rt.go
generated
vendored
Normal file
55
vendor/github.com/google/nftables/expr/rt.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type RtKey uint32
|
||||
|
||||
const (
|
||||
RtClassid RtKey = unix.NFT_RT_CLASSID
|
||||
RtNexthop4 RtKey = unix.NFT_RT_NEXTHOP4
|
||||
RtNexthop6 RtKey = unix.NFT_RT_NEXTHOP6
|
||||
RtTCPMSS RtKey = unix.NFT_RT_TCPMSS
|
||||
)
|
||||
|
||||
type Rt struct {
|
||||
Register uint32
|
||||
Key RtKey
|
||||
}
|
||||
|
||||
func (e *Rt) marshal(fam byte) ([]byte, error) {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RT_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))},
|
||||
{Type: unix.NFTA_RT_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("rt\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Rt) unmarshal(fam byte, data []byte) error {
|
||||
return fmt.Errorf("not yet implemented")
|
||||
}
|
||||
89
vendor/github.com/google/nftables/expr/socket.go
generated
vendored
Normal file
89
vendor/github.com/google/nftables/expr/socket.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
)
|
||||
|
||||
type Socket struct {
|
||||
Key SocketKey
|
||||
Level uint32
|
||||
Register uint32
|
||||
}
|
||||
|
||||
type SocketKey uint32
|
||||
|
||||
const (
|
||||
// TODO, Once the constants below are available in golang.org/x/sys/unix, switch to use those.
|
||||
NFTA_SOCKET_KEY = 1
|
||||
NFTA_SOCKET_DREG = 2
|
||||
NFTA_SOCKET_LEVEL = 3
|
||||
|
||||
NFT_SOCKET_TRANSPARENT = 0
|
||||
NFT_SOCKET_MARK = 1
|
||||
NFT_SOCKET_WILDCARD = 2
|
||||
NFT_SOCKET_CGROUPV2 = 3
|
||||
|
||||
SocketKeyTransparent SocketKey = NFT_SOCKET_TRANSPARENT
|
||||
SocketKeyMark SocketKey = NFT_SOCKET_MARK
|
||||
SocketKeyWildcard SocketKey = NFT_SOCKET_WILDCARD
|
||||
SocketKeyCgroupv2 SocketKey = NFT_SOCKET_CGROUPV2
|
||||
)
|
||||
|
||||
func (e *Socket) marshal(fam byte) ([]byte, error) {
|
||||
// NOTE: Socket.Level is only used when Socket.Key == SocketKeyCgroupv2. But `nft` always encoding it. Check link below:
|
||||
// http://git.netfilter.org/nftables/tree/src/netlink_linearize.c?id=0583bac241ea18c9d7f61cb20ca04faa1e043b78#n319
|
||||
exprData, err := netlink.MarshalAttributes(
|
||||
[]netlink.Attribute{
|
||||
{Type: NFTA_SOCKET_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
|
||||
{Type: NFTA_SOCKET_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))},
|
||||
{Type: NFTA_SOCKET_LEVEL, Data: binaryutil.BigEndian.PutUint32(uint32(e.Level))},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("socket\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Socket) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case NFTA_SOCKET_DREG:
|
||||
e.Register = ad.Uint32()
|
||||
case NFTA_SOCKET_KEY:
|
||||
e.Key = SocketKey(ad.Uint32())
|
||||
case NFTA_SOCKET_LEVEL:
|
||||
e.Level = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
79
vendor/github.com/google/nftables/expr/target.go
generated
vendored
Normal file
79
vendor/github.com/google/nftables/expr/target.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/xt"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n28
|
||||
const XTablesExtensionNameMaxLen = 29
|
||||
|
||||
// See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n30
|
||||
type Target struct {
|
||||
Name string
|
||||
Rev uint32
|
||||
Info xt.InfoAny
|
||||
}
|
||||
|
||||
func (e *Target) marshal(fam byte) ([]byte, error) {
|
||||
// Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n38
|
||||
name := e.Name
|
||||
// limit the extension name as (some) user-space tools do and leave room for
|
||||
// trailing \x00
|
||||
if len(name) >= /* sic! */ XTablesExtensionNameMaxLen {
|
||||
name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00.
|
||||
}
|
||||
// Marshalling assumes that the correct Info type for the particular table
|
||||
// family and Match revision has been set.
|
||||
info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_TARGET_NAME, Data: []byte(name + "\x00")},
|
||||
{Type: unix.NFTA_TARGET_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)},
|
||||
{Type: unix.NFTA_TARGET_INFO, Data: info},
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("target\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Target) unmarshal(fam byte, data []byte) error {
|
||||
// Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info []byte
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_TARGET_NAME:
|
||||
// We are forgiving here, accepting any length and even missing terminating \x00.
|
||||
e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00"))
|
||||
case unix.NFTA_TARGET_REV:
|
||||
e.Rev = ad.Uint32()
|
||||
case unix.NFTA_TARGET_INFO:
|
||||
info = ad.Bytes()
|
||||
}
|
||||
}
|
||||
if err = ad.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info)
|
||||
return err
|
||||
}
|
||||
82
vendor/github.com/google/nftables/expr/tproxy.go
generated
vendored
Normal file
82
vendor/github.com/google/nftables/expr/tproxy.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// NFTA_TPROXY_FAMILY defines attribute for a table family
|
||||
NFTA_TPROXY_FAMILY = 0x01
|
||||
// NFTA_TPROXY_REG_ADDR defines attribute for a register carrying redirection address value
|
||||
NFTA_TPROXY_REG_ADDR = 0x02
|
||||
// NFTA_TPROXY_REG_PORT defines attribute for a register carrying redirection port value
|
||||
NFTA_TPROXY_REG_PORT = 0x03
|
||||
)
|
||||
|
||||
// TProxy defines struct with parameters for the transparent proxy
|
||||
type TProxy struct {
|
||||
Family byte
|
||||
TableFamily byte
|
||||
RegAddr uint32
|
||||
RegPort uint32
|
||||
}
|
||||
|
||||
func (e *TProxy) marshal(fam byte) ([]byte, error) {
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: NFTA_TPROXY_FAMILY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Family))},
|
||||
{Type: NFTA_TPROXY_REG_PORT, Data: binaryutil.BigEndian.PutUint32(e.RegPort)},
|
||||
}
|
||||
|
||||
if e.RegAddr != 0 {
|
||||
attrs = append(attrs, netlink.Attribute{
|
||||
Type: NFTA_TPROXY_REG_ADDR,
|
||||
Data: binaryutil.BigEndian.PutUint32(e.RegAddr),
|
||||
})
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("tproxy\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *TProxy) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case NFTA_TPROXY_FAMILY:
|
||||
e.Family = ad.Uint8()
|
||||
case NFTA_TPROXY_REG_PORT:
|
||||
e.RegPort = ad.Uint32()
|
||||
case NFTA_TPROXY_REG_ADDR:
|
||||
e.RegAddr = ad.Uint32()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
128
vendor/github.com/google/nftables/expr/verdict.go
generated
vendored
Normal file
128
vendor/github.com/google/nftables/expr/verdict.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// This code assembles the verdict structure, as expected by the
|
||||
// nftables netlink API.
|
||||
// For further information, consult:
|
||||
// - netfilter.h (Linux kernel)
|
||||
// - net/netfilter/nf_tables_api.c (Linux kernel)
|
||||
// - src/expr/data_reg.c (linbnftnl)
|
||||
|
||||
type Verdict struct {
|
||||
Kind VerdictKind
|
||||
Chain string
|
||||
}
|
||||
|
||||
type VerdictKind int64
|
||||
|
||||
// Verdicts, as per netfilter.h and netfilter/nf_tables.h.
|
||||
const (
|
||||
VerdictReturn VerdictKind = iota - 5
|
||||
VerdictGoto
|
||||
VerdictJump
|
||||
VerdictBreak
|
||||
VerdictContinue
|
||||
VerdictDrop
|
||||
VerdictAccept
|
||||
VerdictStolen
|
||||
VerdictQueue
|
||||
VerdictRepeat
|
||||
VerdictStop
|
||||
)
|
||||
|
||||
func (e *Verdict) marshal(fam byte) ([]byte, error) {
|
||||
// A verdict is a tree of netlink attributes structured as follows:
|
||||
// NFTA_LIST_ELEM | NLA_F_NESTED {
|
||||
// NFTA_EXPR_NAME { "immediate\x00" }
|
||||
// NFTA_EXPR_DATA | NLA_F_NESTED {
|
||||
// NFTA_IMMEDIATE_DREG { NFT_REG_VERDICT }
|
||||
// NFTA_IMMEDIATE_DATA | NLA_F_NESTED {
|
||||
// the verdict code
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_VERDICT_CODE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Kind))},
|
||||
}
|
||||
if e.Chain != "" {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_VERDICT_CHAIN, Data: []byte(e.Chain + "\x00")})
|
||||
}
|
||||
codeData, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
immData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_DATA_VERDICT, Data: codeData},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_IMMEDIATE_DREG, Data: binaryutil.BigEndian.PutUint32(unix.NFT_REG_VERDICT)},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_IMMEDIATE_DATA, Data: immData},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_EXPR_NAME, Data: []byte("immediate\x00")},
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Verdict) unmarshal(fam byte, data []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_IMMEDIATE_DATA:
|
||||
nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err)
|
||||
}
|
||||
for nestedAD.Next() {
|
||||
switch nestedAD.Type() {
|
||||
case unix.NFTA_DATA_VERDICT:
|
||||
e.Kind = VerdictKind(int32(binaryutil.BigEndian.Uint32(nestedAD.Bytes()[4:8])))
|
||||
if len(nestedAD.Bytes()) > 12 {
|
||||
e.Chain = string(bytes.Trim(nestedAD.Bytes()[12:], "\x00"))
|
||||
}
|
||||
}
|
||||
}
|
||||
if nestedAD.Err() != nil {
|
||||
return fmt.Errorf("decoding immediate: %v", nestedAD.Err())
|
||||
}
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
306
vendor/github.com/google/nftables/flowtable.go
generated
vendored
Normal file
306
vendor/github.com/google/nftables/flowtable.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// not in ztypes_linux.go, added here
|
||||
// https://cs.opensource.google/go/x/sys/+/c6bc011c:unix/ztypes_linux.go;l=1870-1892
|
||||
NFT_MSG_NEWFLOWTABLE = 0x16
|
||||
NFT_MSG_GETFLOWTABLE = 0x17
|
||||
NFT_MSG_DELFLOWTABLE = 0x18
|
||||
)
|
||||
|
||||
const (
|
||||
// not in ztypes_linux.go, added here
|
||||
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1634
|
||||
_ = iota
|
||||
NFTA_FLOWTABLE_TABLE
|
||||
NFTA_FLOWTABLE_NAME
|
||||
NFTA_FLOWTABLE_HOOK
|
||||
NFTA_FLOWTABLE_USE
|
||||
NFTA_FLOWTABLE_HANDLE
|
||||
NFTA_FLOWTABLE_PAD
|
||||
NFTA_FLOWTABLE_FLAGS
|
||||
)
|
||||
|
||||
const (
|
||||
// not in ztypes_linux.go, added here
|
||||
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1657
|
||||
_ = iota
|
||||
NFTA_FLOWTABLE_HOOK_NUM
|
||||
NFTA_FLOWTABLE_PRIORITY
|
||||
NFTA_FLOWTABLE_DEVS
|
||||
)
|
||||
|
||||
const (
|
||||
// not in ztypes_linux.go, added here, used for flowtable device name specification
|
||||
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1709
|
||||
NFTA_DEVICE_NAME = 1
|
||||
)
|
||||
|
||||
type FlowtableFlags uint32
|
||||
|
||||
const (
|
||||
_ FlowtableFlags = iota
|
||||
FlowtableFlagsHWOffload
|
||||
FlowtableFlagsCounter
|
||||
FlowtableFlagsMask = (FlowtableFlagsHWOffload | FlowtableFlagsCounter)
|
||||
)
|
||||
|
||||
type FlowtableHook uint32
|
||||
|
||||
func FlowtableHookRef(h FlowtableHook) *FlowtableHook {
|
||||
return &h
|
||||
}
|
||||
|
||||
var (
|
||||
// Only ingress is supported
|
||||
// https://github.com/torvalds/linux/blob/b72018ab8236c3ae427068adeb94bdd3f20454ec/net/netfilter/nf_tables_api.c#L7378-L7379
|
||||
FlowtableHookIngress *FlowtableHook = FlowtableHookRef(unix.NF_NETDEV_INGRESS)
|
||||
)
|
||||
|
||||
type FlowtablePriority int32
|
||||
|
||||
func FlowtablePriorityRef(p FlowtablePriority) *FlowtablePriority {
|
||||
return &p
|
||||
}
|
||||
|
||||
var (
|
||||
// As per man page:
|
||||
// The priority can be a signed integer or filter which stands for 0. Addition and subtraction can be used to set relative priority, e.g. filter + 5 equals to 5.
|
||||
// https://git.netfilter.org/nftables/tree/doc/nft.txt?id=8c600a843b7c0c1cc275ecc0603bd1fc57773e98#n712
|
||||
FlowtablePriorityFilter *FlowtablePriority = FlowtablePriorityRef(0)
|
||||
)
|
||||
|
||||
type Flowtable struct {
|
||||
Table *Table
|
||||
Name string
|
||||
Hooknum *FlowtableHook
|
||||
Priority *FlowtablePriority
|
||||
Devices []string
|
||||
Use uint32
|
||||
// Bitmask flags, can be HW_OFFLOAD or COUNTER
|
||||
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1621
|
||||
Flags FlowtableFlags
|
||||
Handle uint64
|
||||
}
|
||||
|
||||
func (cc *Conn) AddFlowtable(f *Flowtable) *Flowtable {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: NFTA_FLOWTABLE_TABLE, Data: []byte(f.Table.Name)},
|
||||
{Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)},
|
||||
{Type: NFTA_FLOWTABLE_FLAGS, Data: binaryutil.BigEndian.PutUint32(uint32(f.Flags))},
|
||||
})
|
||||
|
||||
if f.Hooknum == nil {
|
||||
f.Hooknum = FlowtableHookIngress
|
||||
}
|
||||
|
||||
if f.Priority == nil {
|
||||
f.Priority = FlowtablePriorityFilter
|
||||
}
|
||||
|
||||
hookAttr := []netlink.Attribute{
|
||||
{Type: NFTA_FLOWTABLE_HOOK_NUM, Data: binaryutil.BigEndian.PutUint32(uint32(*f.Hooknum))},
|
||||
{Type: NFTA_FLOWTABLE_PRIORITY, Data: binaryutil.BigEndian.PutUint32(uint32(*f.Priority))},
|
||||
}
|
||||
if len(f.Devices) > 0 {
|
||||
devs := make([]netlink.Attribute, len(f.Devices))
|
||||
for i, d := range f.Devices {
|
||||
devs[i] = netlink.Attribute{Type: NFTA_DEVICE_NAME, Data: []byte(d)}
|
||||
}
|
||||
hookAttr = append(hookAttr, netlink.Attribute{
|
||||
Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_DEVS,
|
||||
Data: cc.marshalAttr(devs),
|
||||
})
|
||||
}
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_HOOK, Data: cc.marshalAttr(hookAttr)},
|
||||
})...)
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: append(extraHeader(uint8(f.Table.Family), 0), data...),
|
||||
})
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (cc *Conn) DelFlowtable(f *Flowtable) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: NFTA_FLOWTABLE_TABLE, Data: []byte(f.Table.Name)},
|
||||
{Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)},
|
||||
})
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELFLOWTABLE),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(f.Table.Family), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
func (cc *Conn) ListFlowtables(t *Table) ([]*Flowtable, error) {
|
||||
reply, err := cc.getFlowtables(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var fts []*Flowtable
|
||||
for _, msg := range reply {
|
||||
f, err := ftsFromMsg(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Table = t
|
||||
fts = append(fts, f)
|
||||
}
|
||||
|
||||
return fts, nil
|
||||
}
|
||||
|
||||
func (cc *Conn) getFlowtables(t *Table) ([]netlink.Message, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: NFTA_FLOWTABLE_TABLE, Data: []byte(t.Name + "\x00")},
|
||||
}
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
message := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_GETFLOWTABLE),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
}
|
||||
|
||||
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
|
||||
return nil, fmt.Errorf("SendMessages: %v", err)
|
||||
}
|
||||
|
||||
reply, err := receiveAckAware(conn, message.Header.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Receive: %v", err)
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func ftsFromMsg(msg netlink.Message) (*Flowtable, error) {
|
||||
flowHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE)
|
||||
if got, want := msg.Header.Type, flowHeaderType; got != want {
|
||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
|
||||
}
|
||||
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
var ft Flowtable
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case NFTA_FLOWTABLE_NAME:
|
||||
ft.Name = ad.String()
|
||||
case NFTA_FLOWTABLE_USE:
|
||||
ft.Use = ad.Uint32()
|
||||
case NFTA_FLOWTABLE_HANDLE:
|
||||
ft.Handle = ad.Uint64()
|
||||
case NFTA_FLOWTABLE_FLAGS:
|
||||
ft.Flags = FlowtableFlags(ad.Uint32())
|
||||
case NFTA_FLOWTABLE_HOOK:
|
||||
ad.Do(func(b []byte) error {
|
||||
ft.Hooknum, ft.Priority, ft.Devices, err = ftsHookFromMsg(b)
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
return &ft, nil
|
||||
}
|
||||
|
||||
func ftsHookFromMsg(b []byte) (*FlowtableHook, *FlowtablePriority, []string, error) {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
var hooknum FlowtableHook
|
||||
var prio FlowtablePriority
|
||||
var devices []string
|
||||
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case NFTA_FLOWTABLE_HOOK_NUM:
|
||||
hooknum = FlowtableHook(ad.Uint32())
|
||||
case NFTA_FLOWTABLE_PRIORITY:
|
||||
prio = FlowtablePriority(ad.Uint32())
|
||||
case NFTA_FLOWTABLE_DEVS:
|
||||
ad.Do(func(b []byte) error {
|
||||
devices, err = devsFromMsg(b)
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &hooknum, &prio, devices, nil
|
||||
}
|
||||
|
||||
func devsFromMsg(b []byte) ([]string, error) {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
devs := make([]string, 0)
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case NFTA_DEVICE_NAME:
|
||||
devs = append(devs, ad.String())
|
||||
}
|
||||
}
|
||||
|
||||
return devs, nil
|
||||
}
|
||||
10
vendor/github.com/google/nftables/internal/parseexprfunc/parseexprfunc.go
generated
vendored
Normal file
10
vendor/github.com/google/nftables/internal/parseexprfunc/parseexprfunc.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package parseexprfunc
|
||||
|
||||
import (
|
||||
"github.com/mdlayher/netlink"
|
||||
)
|
||||
|
||||
var (
|
||||
ParseExprBytesFunc func(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]interface{}, error)
|
||||
ParseExprMsgFunc func(fam byte, b []byte) ([]interface{}, error)
|
||||
)
|
||||
319
vendor/github.com/google/nftables/monitor.go
generated
vendored
Normal file
319
vendor/github.com/google/nftables/monitor.go
generated
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type MonitorAction uint8
|
||||
|
||||
// Possible MonitorAction values.
|
||||
const (
|
||||
MonitorActionNew MonitorAction = 1 << iota
|
||||
MonitorActionDel
|
||||
MonitorActionMask MonitorAction = (1 << iota) - 1
|
||||
MonitorActionAny MonitorAction = MonitorActionMask
|
||||
)
|
||||
|
||||
type MonitorObject uint32
|
||||
|
||||
// Possible MonitorObject values.
|
||||
const (
|
||||
MonitorObjectTables MonitorObject = 1 << iota
|
||||
MonitorObjectChains
|
||||
MonitorObjectSets
|
||||
MonitorObjectRules
|
||||
MonitorObjectElements
|
||||
MonitorObjectRuleset
|
||||
MonitorObjectMask MonitorObject = (1 << iota) - 1
|
||||
MonitorObjectAny MonitorObject = MonitorObjectMask
|
||||
)
|
||||
|
||||
var (
|
||||
monitorFlags = map[MonitorAction]map[MonitorObject]uint32{
|
||||
MonitorActionAny: {
|
||||
MonitorObjectAny: 0xffffffff,
|
||||
MonitorObjectTables: 1<<unix.NFT_MSG_NEWTABLE | 1<<unix.NFT_MSG_DELTABLE,
|
||||
MonitorObjectChains: 1<<unix.NFT_MSG_NEWCHAIN | 1<<unix.NFT_MSG_DELCHAIN,
|
||||
MonitorObjectRules: 1<<unix.NFT_MSG_NEWRULE | 1<<unix.NFT_MSG_DELRULE,
|
||||
MonitorObjectSets: 1<<unix.NFT_MSG_NEWSET | 1<<unix.NFT_MSG_DELSET,
|
||||
MonitorObjectElements: 1<<unix.NFT_MSG_NEWSETELEM | 1<<unix.NFT_MSG_DELSETELEM,
|
||||
MonitorObjectRuleset: 1<<unix.NFT_MSG_NEWTABLE | 1<<unix.NFT_MSG_DELTABLE |
|
||||
1<<unix.NFT_MSG_NEWCHAIN | 1<<unix.NFT_MSG_DELCHAIN |
|
||||
1<<unix.NFT_MSG_NEWRULE | 1<<unix.NFT_MSG_DELRULE |
|
||||
1<<unix.NFT_MSG_NEWSET | 1<<unix.NFT_MSG_DELSET |
|
||||
1<<unix.NFT_MSG_NEWSETELEM | 1<<unix.NFT_MSG_DELSETELEM |
|
||||
1<<unix.NFT_MSG_NEWOBJ | 1<<unix.NFT_MSG_DELOBJ,
|
||||
},
|
||||
MonitorActionNew: {
|
||||
MonitorObjectAny: 1<<unix.NFT_MSG_NEWTABLE |
|
||||
1<<unix.NFT_MSG_NEWCHAIN |
|
||||
1<<unix.NFT_MSG_NEWRULE |
|
||||
1<<unix.NFT_MSG_NEWSET |
|
||||
1<<unix.NFT_MSG_NEWSETELEM,
|
||||
MonitorObjectTables: 1 << unix.NFT_MSG_NEWTABLE,
|
||||
MonitorObjectChains: 1 << unix.NFT_MSG_NEWCHAIN,
|
||||
MonitorObjectRules: 1 << unix.NFT_MSG_NEWRULE,
|
||||
MonitorObjectSets: 1 << unix.NFT_MSG_NEWSET,
|
||||
MonitorObjectRuleset: 1<<unix.NFT_MSG_NEWTABLE |
|
||||
1<<unix.NFT_MSG_NEWCHAIN |
|
||||
1<<unix.NFT_MSG_NEWRULE |
|
||||
1<<unix.NFT_MSG_NEWSET |
|
||||
1<<unix.NFT_MSG_NEWSETELEM |
|
||||
1<<unix.NFT_MSG_NEWOBJ,
|
||||
},
|
||||
MonitorActionDel: {
|
||||
MonitorObjectAny: 1<<unix.NFT_MSG_DELTABLE |
|
||||
1<<unix.NFT_MSG_DELCHAIN |
|
||||
1<<unix.NFT_MSG_DELRULE |
|
||||
1<<unix.NFT_MSG_DELSET |
|
||||
1<<unix.NFT_MSG_DELSETELEM |
|
||||
1<<unix.NFT_MSG_DELOBJ,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type MonitorEventType int
|
||||
|
||||
const (
|
||||
MonitorEventTypeNewTable MonitorEventType = unix.NFT_MSG_NEWTABLE
|
||||
MonitorEventTypeDelTable MonitorEventType = unix.NFT_MSG_DELTABLE
|
||||
MonitorEventTypeNewChain MonitorEventType = unix.NFT_MSG_NEWCHAIN
|
||||
MonitorEventTypeDelChain MonitorEventType = unix.NFT_MSG_DELCHAIN
|
||||
MonitorEventTypeNewRule MonitorEventType = unix.NFT_MSG_NEWRULE
|
||||
MonitorEventTypeDelRule MonitorEventType = unix.NFT_MSG_DELRULE
|
||||
MonitorEventTypeNewSet MonitorEventType = unix.NFT_MSG_NEWSET
|
||||
MonitorEventTypeDelSet MonitorEventType = unix.NFT_MSG_DELSET
|
||||
MonitorEventTypeNewSetElem MonitorEventType = unix.NFT_MSG_NEWSETELEM
|
||||
MonitorEventTypeDelSetElem MonitorEventType = unix.NFT_MSG_DELSETELEM
|
||||
MonitorEventTypeNewObj MonitorEventType = unix.NFT_MSG_NEWOBJ
|
||||
MonitorEventTypeDelObj MonitorEventType = unix.NFT_MSG_DELOBJ
|
||||
MonitorEventTypeOOB MonitorEventType = math.MaxInt // out of band event
|
||||
)
|
||||
|
||||
// A MonitorEvent represents a single change received via a [Monitor].
|
||||
//
|
||||
// Depending on the Type, the Data field can be type-asserted to the specific
|
||||
// data type for this event, e.g. when Type is
|
||||
// nftables.MonitorEventTypeNewTable, you can access the corresponding table
|
||||
// details via Data.(*nftables.Table).
|
||||
type MonitorEvent struct {
|
||||
Type MonitorEventType
|
||||
Data any
|
||||
Error error
|
||||
}
|
||||
|
||||
const (
|
||||
monitorOK = iota
|
||||
monitorClosed
|
||||
)
|
||||
|
||||
// A Monitor is an event-based nftables monitor that will receive one event per
|
||||
// new (or deleted) table, chain, rule, set, etc., depending on the monitor
|
||||
// configuration.
|
||||
type Monitor struct {
|
||||
action MonitorAction
|
||||
object MonitorObject
|
||||
monitorFlags uint32
|
||||
|
||||
conn *netlink.Conn
|
||||
closer netlinkCloser
|
||||
|
||||
// mu covers eventCh and status
|
||||
mu sync.Mutex
|
||||
eventCh chan *MonitorEvent
|
||||
status int
|
||||
}
|
||||
|
||||
type MonitorOption func(*Monitor)
|
||||
|
||||
func WithMonitorEventBuffer(size int) MonitorOption {
|
||||
return func(monitor *Monitor) {
|
||||
monitor.eventCh = make(chan *MonitorEvent, size)
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitorAction to set monitor actions like new, del or any.
|
||||
func WithMonitorAction(action MonitorAction) MonitorOption {
|
||||
return func(monitor *Monitor) {
|
||||
monitor.action = action
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitorObject to set monitor objects.
|
||||
func WithMonitorObject(object MonitorObject) MonitorOption {
|
||||
return func(monitor *Monitor) {
|
||||
monitor.object = object
|
||||
}
|
||||
}
|
||||
|
||||
// NewMonitor returns a Monitor with options to be started.
|
||||
//
|
||||
// Note that NewMonitor only prepares a Monitor. To install the monitor, call
|
||||
// [Conn.AddMonitor].
|
||||
func NewMonitor(opts ...MonitorOption) *Monitor {
|
||||
monitor := &Monitor{
|
||||
status: monitorOK,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(monitor)
|
||||
}
|
||||
if monitor.eventCh == nil {
|
||||
monitor.eventCh = make(chan *MonitorEvent)
|
||||
}
|
||||
objects, ok := monitorFlags[monitor.action]
|
||||
if !ok {
|
||||
objects = monitorFlags[MonitorActionAny]
|
||||
}
|
||||
flags, ok := objects[monitor.object]
|
||||
if !ok {
|
||||
flags = objects[MonitorObjectAny]
|
||||
}
|
||||
monitor.monitorFlags = flags
|
||||
return monitor
|
||||
}
|
||||
|
||||
func (monitor *Monitor) monitor() {
|
||||
for {
|
||||
msgs, err := monitor.conn.Receive()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "use of closed file") {
|
||||
// ignore the error that be closed
|
||||
break
|
||||
} else {
|
||||
// any other errors will be send to user, and then to close eventCh
|
||||
event := &MonitorEvent{
|
||||
Type: MonitorEventTypeOOB,
|
||||
Data: nil,
|
||||
Error: err,
|
||||
}
|
||||
monitor.eventCh <- event
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, msg := range msgs {
|
||||
if msg.Header.Type&0xff00>>8 != netlink.HeaderType(unix.NFNL_SUBSYS_NFTABLES) {
|
||||
continue
|
||||
}
|
||||
msgType := msg.Header.Type & 0x00ff
|
||||
if monitor.monitorFlags&1<<msgType == 0 {
|
||||
continue
|
||||
}
|
||||
switch msgType {
|
||||
case unix.NFT_MSG_NEWTABLE, unix.NFT_MSG_DELTABLE:
|
||||
table, err := tableFromMsg(msg)
|
||||
event := &MonitorEvent{
|
||||
Type: MonitorEventType(msgType),
|
||||
Data: table,
|
||||
Error: err,
|
||||
}
|
||||
monitor.eventCh <- event
|
||||
case unix.NFT_MSG_NEWCHAIN, unix.NFT_MSG_DELCHAIN:
|
||||
chain, err := chainFromMsg(msg)
|
||||
event := &MonitorEvent{
|
||||
Type: MonitorEventType(msgType),
|
||||
Data: chain,
|
||||
Error: err,
|
||||
}
|
||||
monitor.eventCh <- event
|
||||
case unix.NFT_MSG_NEWRULE, unix.NFT_MSG_DELRULE:
|
||||
rule, err := parseRuleFromMsg(msg)
|
||||
event := &MonitorEvent{
|
||||
Type: MonitorEventType(msgType),
|
||||
Data: rule,
|
||||
Error: err,
|
||||
}
|
||||
monitor.eventCh <- event
|
||||
case unix.NFT_MSG_NEWSET, unix.NFT_MSG_DELSET:
|
||||
set, err := setsFromMsg(msg)
|
||||
event := &MonitorEvent{
|
||||
Type: MonitorEventType(msgType),
|
||||
Data: set,
|
||||
Error: err,
|
||||
}
|
||||
monitor.eventCh <- event
|
||||
case unix.NFT_MSG_NEWSETELEM, unix.NFT_MSG_DELSETELEM:
|
||||
elems, err := elementsFromMsg(uint8(TableFamilyUnspecified), msg)
|
||||
event := &MonitorEvent{
|
||||
Type: MonitorEventType(msgType),
|
||||
Data: elems,
|
||||
Error: err,
|
||||
}
|
||||
monitor.eventCh <- event
|
||||
case unix.NFT_MSG_NEWOBJ, unix.NFT_MSG_DELOBJ:
|
||||
obj, err := objFromMsg(msg)
|
||||
event := &MonitorEvent{
|
||||
Type: MonitorEventType(msgType),
|
||||
Data: obj,
|
||||
Error: err,
|
||||
}
|
||||
monitor.eventCh <- event
|
||||
}
|
||||
}
|
||||
}
|
||||
monitor.mu.Lock()
|
||||
defer monitor.mu.Unlock()
|
||||
|
||||
if monitor.status != monitorClosed {
|
||||
monitor.status = monitorClosed
|
||||
}
|
||||
close(monitor.eventCh)
|
||||
}
|
||||
|
||||
func (monitor *Monitor) Close() error {
|
||||
monitor.mu.Lock()
|
||||
defer monitor.mu.Unlock()
|
||||
|
||||
if monitor.status != monitorClosed {
|
||||
monitor.status = monitorClosed
|
||||
return monitor.closer()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddMonitor to perform the monitor immediately. The channel will be closed after
|
||||
// calling Close on Monitor or encountering a netlink conn error while Receive.
|
||||
// Caller may receive a MonitorEventTypeOOB event which contains an error we didn't
|
||||
// handle, for now.
|
||||
func (cc *Conn) AddMonitor(monitor *Monitor) (chan *MonitorEvent, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
monitor.conn = conn
|
||||
monitor.closer = closer
|
||||
|
||||
if monitor.monitorFlags != 0 {
|
||||
if err = conn.JoinGroup(uint32(unix.NFNLGRP_NFTABLES)); err != nil {
|
||||
monitor.closer()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
go monitor.monitor()
|
||||
return monitor.eventCh, nil
|
||||
}
|
||||
|
||||
func parseRuleFromMsg(msg netlink.Message) (*Rule, error) {
|
||||
genmsg := &NFGenMsg{}
|
||||
genmsg.Decode(msg.Data[:4])
|
||||
return ruleFromMsg(TableFamily(genmsg.NFGenFamily), msg)
|
||||
}
|
||||
241
vendor/github.com/google/nftables/obj.go
generated
vendored
Normal file
241
vendor/github.com/google/nftables/obj.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
newObjHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWOBJ)
|
||||
delObjHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELOBJ)
|
||||
)
|
||||
|
||||
// Obj represents a netfilter stateful object. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Stateful_objects
|
||||
type Obj interface {
|
||||
table() *Table
|
||||
family() TableFamily
|
||||
unmarshal(*netlink.AttributeDecoder) error
|
||||
marshal(data bool) ([]byte, error)
|
||||
}
|
||||
|
||||
// AddObject adds the specified Obj. Alias of AddObj.
|
||||
func (cc *Conn) AddObject(o Obj) Obj {
|
||||
return cc.AddObj(o)
|
||||
}
|
||||
|
||||
// AddObj adds the specified Obj. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Stateful_objects
|
||||
func (cc *Conn) AddObj(o Obj) Obj {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data, err := o.marshal(true)
|
||||
if err != nil {
|
||||
cc.setErr(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWOBJ),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: append(extraHeader(uint8(o.family()), 0), data...),
|
||||
})
|
||||
return o
|
||||
}
|
||||
|
||||
// DeleteObject deletes the specified Obj
|
||||
func (cc *Conn) DeleteObject(o Obj) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data, err := o.marshal(false)
|
||||
if err != nil {
|
||||
cc.setErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA}})...)
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELOBJ),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(o.family()), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
// GetObj is a legacy method that return all Obj that belongs
|
||||
// to the same table as the given one
|
||||
func (cc *Conn) GetObj(o Obj) ([]Obj, error) {
|
||||
return cc.getObj(nil, o.table(), unix.NFT_MSG_GETOBJ)
|
||||
}
|
||||
|
||||
// GetObjReset is a legacy method that reset all Obj that belongs
|
||||
// the same table as the given one
|
||||
func (cc *Conn) GetObjReset(o Obj) ([]Obj, error) {
|
||||
return cc.getObj(nil, o.table(), unix.NFT_MSG_GETOBJ_RESET)
|
||||
}
|
||||
|
||||
// GetObject gets the specified Object
|
||||
func (cc *Conn) GetObject(o Obj) (Obj, error) {
|
||||
objs, err := cc.getObj(o, o.table(), unix.NFT_MSG_GETOBJ)
|
||||
|
||||
if len(objs) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return objs[0], err
|
||||
}
|
||||
|
||||
// GetObjects get all the Obj that belongs to the given table
|
||||
func (cc *Conn) GetObjects(t *Table) ([]Obj, error) {
|
||||
return cc.getObj(nil, t, unix.NFT_MSG_GETOBJ)
|
||||
}
|
||||
|
||||
// ResetObject reset the given Obj
|
||||
func (cc *Conn) ResetObject(o Obj) (Obj, error) {
|
||||
objs, err := cc.getObj(o, o.table(), unix.NFT_MSG_GETOBJ_RESET)
|
||||
|
||||
if len(objs) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return objs[0], err
|
||||
}
|
||||
|
||||
// ResetObjects reset all the Obj that belongs to the given table
|
||||
func (cc *Conn) ResetObjects(t *Table) ([]Obj, error) {
|
||||
return cc.getObj(nil, t, unix.NFT_MSG_GETOBJ_RESET)
|
||||
}
|
||||
|
||||
func objFromMsg(msg netlink.Message) (Obj, error) {
|
||||
if got, want1, want2 := msg.Header.Type, newObjHeaderType, delObjHeaderType; got != want1 && got != want2 {
|
||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
|
||||
}
|
||||
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
var (
|
||||
table *Table
|
||||
name string
|
||||
objectType uint32
|
||||
)
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_OBJ_TABLE:
|
||||
table = &Table{Name: ad.String(), Family: TableFamily(msg.Data[0])}
|
||||
case unix.NFTA_OBJ_NAME:
|
||||
name = ad.String()
|
||||
case unix.NFTA_OBJ_TYPE:
|
||||
objectType = ad.Uint32()
|
||||
case unix.NFTA_OBJ_DATA:
|
||||
switch objectType {
|
||||
case unix.NFT_OBJECT_COUNTER:
|
||||
o := CounterObj{
|
||||
Table: table,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
ad.Do(func(b []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
return o.unmarshal(ad)
|
||||
})
|
||||
return &o, ad.Err()
|
||||
case NFT_OBJECT_QUOTA:
|
||||
o := QuotaObj{
|
||||
Table: table,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
ad.Do(func(b []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
return o.unmarshal(ad)
|
||||
})
|
||||
return &o, ad.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := ad.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("malformed stateful object")
|
||||
}
|
||||
|
||||
func (cc *Conn) getObj(o Obj, t *Table, msgType uint16) ([]Obj, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
var data []byte
|
||||
var flags netlink.HeaderFlags
|
||||
|
||||
if o != nil {
|
||||
data, err = o.marshal(false)
|
||||
} else {
|
||||
flags = netlink.Dump
|
||||
data, err = netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")},
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
message := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | msgType),
|
||||
Flags: netlink.Request | netlink.Acknowledge | flags,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
}
|
||||
|
||||
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
|
||||
return nil, fmt.Errorf("SendMessages: %v", err)
|
||||
}
|
||||
|
||||
reply, err := receiveAckAware(conn, message.Header.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Receive: %v", err)
|
||||
}
|
||||
var objs []Obj
|
||||
for _, msg := range reply {
|
||||
o, err := objFromMsg(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objs = append(objs, o)
|
||||
}
|
||||
|
||||
return objs, nil
|
||||
}
|
||||
80
vendor/github.com/google/nftables/quota.go
generated
vendored
Normal file
80
vendor/github.com/google/nftables/quota.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
NFTA_OBJ_USERDATA = 8
|
||||
NFT_OBJECT_QUOTA = 2
|
||||
)
|
||||
|
||||
type QuotaObj struct {
|
||||
Table *Table
|
||||
Name string
|
||||
Bytes uint64
|
||||
Consumed uint64
|
||||
Over bool
|
||||
}
|
||||
|
||||
func (q *QuotaObj) unmarshal(ad *netlink.AttributeDecoder) error {
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_QUOTA_BYTES:
|
||||
q.Bytes = ad.Uint64()
|
||||
case unix.NFTA_QUOTA_CONSUMED:
|
||||
q.Consumed = ad.Uint64()
|
||||
case unix.NFTA_QUOTA_FLAGS:
|
||||
q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) == 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QuotaObj) marshal(data bool) ([]byte, error) {
|
||||
flags := uint32(0)
|
||||
if q.Over {
|
||||
flags = unix.NFT_QUOTA_F_INV
|
||||
}
|
||||
obj, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_QUOTA_BYTES, Data: binaryutil.BigEndian.PutUint64(q.Bytes)},
|
||||
{Type: unix.NFTA_QUOTA_CONSUMED, Data: binaryutil.BigEndian.PutUint64(q.Consumed)},
|
||||
{Type: unix.NFTA_QUOTA_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrs := []netlink.Attribute{
|
||||
{Type: unix.NFTA_OBJ_TABLE, Data: []byte(q.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_OBJ_NAME, Data: []byte(q.Name + "\x00")},
|
||||
{Type: unix.NFTA_OBJ_TYPE, Data: binaryutil.BigEndian.PutUint32(NFT_OBJECT_QUOTA)},
|
||||
}
|
||||
if data {
|
||||
attrs = append(attrs, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA, Data: obj})
|
||||
}
|
||||
return netlink.MarshalAttributes(attrs)
|
||||
}
|
||||
|
||||
func (q *QuotaObj) table() *Table {
|
||||
return q.Table
|
||||
}
|
||||
|
||||
func (q *QuotaObj) family() TableFamily {
|
||||
return q.Table.Family
|
||||
}
|
||||
270
vendor/github.com/google/nftables/rule.go
generated
vendored
Normal file
270
vendor/github.com/google/nftables/rule.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/google/nftables/internal/parseexprfunc"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
newRuleHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWRULE)
|
||||
delRuleHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE)
|
||||
)
|
||||
|
||||
type ruleOperation uint32
|
||||
|
||||
// Possible PayloadOperationType values.
|
||||
const (
|
||||
operationAdd ruleOperation = iota
|
||||
operationInsert
|
||||
operationReplace
|
||||
)
|
||||
|
||||
// A Rule does something with a packet. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management
|
||||
type Rule struct {
|
||||
Table *Table
|
||||
Chain *Chain
|
||||
Position uint64
|
||||
Handle uint64
|
||||
// The list of possible flags are specified by nftnl_rule_attr, see
|
||||
// https://git.netfilter.org/libnftnl/tree/include/libnftnl/rule.h#n21
|
||||
// Current nftables go implementation supports only
|
||||
// NFTNL_RULE_POSITION flag for setting rule at position 0
|
||||
Flags uint32
|
||||
Exprs []expr.Any
|
||||
UserData []byte
|
||||
}
|
||||
|
||||
// GetRule returns the rules in the specified table and chain.
|
||||
//
|
||||
// Deprecated: use GetRules instead.
|
||||
func (cc *Conn) GetRule(t *Table, c *Chain) ([]*Rule, error) {
|
||||
return cc.GetRules(t, c)
|
||||
}
|
||||
|
||||
// GetRules returns the rules in the specified table and chain.
|
||||
func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")},
|
||||
{Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
message := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETRULE),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Dump | unix.NLM_F_ECHO,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
}
|
||||
|
||||
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
|
||||
return nil, fmt.Errorf("SendMessages: %v", err)
|
||||
}
|
||||
|
||||
reply, err := receiveAckAware(conn, message.Header.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Receive: %v", err)
|
||||
}
|
||||
var rules []*Rule
|
||||
for _, msg := range reply {
|
||||
r, err := ruleFromMsg(t.Family, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules = append(rules, r)
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// AddRule adds the specified Rule
|
||||
func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
exprAttrs := make([]netlink.Attribute, len(r.Exprs))
|
||||
for idx, expr := range r.Exprs {
|
||||
exprAttrs[idx] = netlink.Attribute{
|
||||
Type: unix.NLA_F_NESTED | unix.NFTA_LIST_ELEM,
|
||||
Data: cc.marshalExpr(byte(r.Table.Family), expr),
|
||||
}
|
||||
}
|
||||
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_TABLE, Data: []byte(r.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_RULE_CHAIN, Data: []byte(r.Chain.Name + "\x00")},
|
||||
})
|
||||
|
||||
if r.Handle != 0 {
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
|
||||
})...)
|
||||
}
|
||||
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_RULE_EXPRESSIONS, Data: cc.marshalAttr(exprAttrs)},
|
||||
})...)
|
||||
|
||||
if compatPolicy, err := getCompatPolicy(r.Exprs); err != nil {
|
||||
cc.setErr(err)
|
||||
} else if compatPolicy != nil {
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NLA_F_NESTED | unix.NFTA_RULE_COMPAT, Data: cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_COMPAT_PROTO, Data: binaryutil.BigEndian.PutUint32(compatPolicy.Proto)},
|
||||
{Type: unix.NFTA_RULE_COMPAT_FLAGS, Data: binaryutil.BigEndian.PutUint32(compatPolicy.Flag & nft_RULE_COMPAT_F_MASK)},
|
||||
})},
|
||||
})...)
|
||||
}
|
||||
|
||||
msgData := []byte{}
|
||||
|
||||
msgData = append(msgData, data...)
|
||||
var flags netlink.HeaderFlags
|
||||
if r.UserData != nil {
|
||||
msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_USERDATA, Data: r.UserData},
|
||||
})...)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case operationAdd:
|
||||
flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO | unix.NLM_F_APPEND
|
||||
case operationInsert:
|
||||
flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO
|
||||
case operationReplace:
|
||||
flags = netlink.Request | netlink.Acknowledge | netlink.Replace | unix.NLM_F_ECHO | unix.NLM_F_REPLACE
|
||||
}
|
||||
|
||||
if r.Position != 0 || (r.Flags&(1<<unix.NFTA_RULE_POSITION)) != 0 {
|
||||
msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_POSITION, Data: binaryutil.BigEndian.PutUint64(r.Position)},
|
||||
})...)
|
||||
}
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: newRuleHeaderType,
|
||||
Flags: flags,
|
||||
},
|
||||
Data: append(extraHeader(uint8(r.Table.Family), 0), msgData...),
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (cc *Conn) ReplaceRule(r *Rule) *Rule {
|
||||
return cc.newRule(r, operationReplace)
|
||||
}
|
||||
|
||||
func (cc *Conn) AddRule(r *Rule) *Rule {
|
||||
if r.Handle != 0 {
|
||||
return cc.newRule(r, operationReplace)
|
||||
}
|
||||
|
||||
return cc.newRule(r, operationAdd)
|
||||
}
|
||||
|
||||
func (cc *Conn) InsertRule(r *Rule) *Rule {
|
||||
if r.Handle != 0 {
|
||||
return cc.newRule(r, operationReplace)
|
||||
}
|
||||
|
||||
return cc.newRule(r, operationInsert)
|
||||
}
|
||||
|
||||
// DelRule deletes the specified Rule, rule's handle cannot be 0
|
||||
func (cc *Conn) DelRule(r *Rule) error {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_TABLE, Data: []byte(r.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_RULE_CHAIN, Data: []byte(r.Chain.Name + "\x00")},
|
||||
})
|
||||
if r.Handle == 0 {
|
||||
return fmt.Errorf("rule's handle cannot be 0")
|
||||
}
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
|
||||
})...)
|
||||
flags := netlink.Request | netlink.Acknowledge
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: delRuleHeaderType,
|
||||
Flags: flags,
|
||||
},
|
||||
Data: append(extraHeader(uint8(r.Table.Family), 0), data...),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ruleFromMsg(fam TableFamily, msg netlink.Message) (*Rule, error) {
|
||||
if got, want1, want2 := msg.Header.Type, newRuleHeaderType, delRuleHeaderType; got != want1 && got != want2 {
|
||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", msg.Header.Type, want1, want2)
|
||||
}
|
||||
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
var r Rule
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_RULE_TABLE:
|
||||
r.Table = &Table{
|
||||
Name: ad.String(),
|
||||
Family: fam,
|
||||
}
|
||||
case unix.NFTA_RULE_CHAIN:
|
||||
r.Chain = &Chain{Name: ad.String()}
|
||||
case unix.NFTA_RULE_EXPRESSIONS:
|
||||
ad.Do(func(b []byte) error {
|
||||
exprs, err := parseexprfunc.ParseExprMsgFunc(byte(fam), b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Exprs = make([]expr.Any, len(exprs))
|
||||
for i := range exprs {
|
||||
r.Exprs[i] = exprs[i].(expr.Any)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
case unix.NFTA_RULE_POSITION:
|
||||
r.Position = ad.Uint64()
|
||||
case unix.NFTA_RULE_HANDLE:
|
||||
r.Handle = ad.Uint64()
|
||||
case unix.NFTA_RULE_USERDATA:
|
||||
r.UserData = ad.Bytes()
|
||||
}
|
||||
}
|
||||
return &r, ad.Err()
|
||||
}
|
||||
937
vendor/github.com/google/nftables/set.go
generated
vendored
Normal file
937
vendor/github.com/google/nftables/set.go
generated
vendored
Normal file
@@ -0,0 +1,937 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/google/nftables/internal/parseexprfunc"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SetConcatTypeBits defines concatination bits, originally defined in
|
||||
// https://git.netfilter.org/iptables/tree/iptables/nft.c?id=26753888720d8e7eb422ae4311348347f5a05cb4#n1002
|
||||
const (
|
||||
SetConcatTypeBits = 6
|
||||
SetConcatTypeMask = (1 << SetConcatTypeBits) - 1
|
||||
// below consts added because not found in go unix package
|
||||
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n306
|
||||
NFT_SET_CONCAT = 0x80
|
||||
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n330
|
||||
NFTA_SET_DESC_CONCAT = 2
|
||||
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n428
|
||||
NFTA_SET_ELEM_KEY_END = 10
|
||||
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n429
|
||||
NFTA_SET_ELEM_EXPRESSIONS = 0x11
|
||||
)
|
||||
|
||||
var allocSetID uint32
|
||||
|
||||
// SetDatatype represents a datatype declared by nft.
|
||||
type SetDatatype struct {
|
||||
Name string
|
||||
Bytes uint32
|
||||
|
||||
// nftMagic represents the magic value that nft uses for
|
||||
// certain types (ie: IP addresses). We populate SET_KEY_TYPE
|
||||
// identically, so `nft list ...` commands produce correct output.
|
||||
nftMagic uint32
|
||||
}
|
||||
|
||||
// GetNFTMagic returns a custom datatype based on user's parameters
|
||||
func (s *SetDatatype) GetNFTMagic() uint32 {
|
||||
return s.nftMagic
|
||||
}
|
||||
|
||||
// SetNFTMagic returns a custom datatype based on user's parameters
|
||||
func (s *SetDatatype) SetNFTMagic(nftMagic uint32) {
|
||||
s.nftMagic = nftMagic
|
||||
}
|
||||
|
||||
// NFT datatypes. See: https://git.netfilter.org/nftables/tree/include/datatype.h
|
||||
var (
|
||||
TypeInvalid = SetDatatype{Name: "invalid", nftMagic: 0}
|
||||
TypeVerdict = SetDatatype{Name: "verdict", Bytes: 0, nftMagic: 1}
|
||||
TypeNFProto = SetDatatype{Name: "nf_proto", Bytes: 1, nftMagic: 2}
|
||||
TypeBitmask = SetDatatype{Name: "bitmask", Bytes: 0, nftMagic: 3}
|
||||
TypeInteger = SetDatatype{Name: "integer", Bytes: 4, nftMagic: 4}
|
||||
TypeString = SetDatatype{Name: "string", Bytes: 0, nftMagic: 5}
|
||||
TypeLLAddr = SetDatatype{Name: "ll_addr", Bytes: 0, nftMagic: 6}
|
||||
TypeIPAddr = SetDatatype{Name: "ipv4_addr", Bytes: 4, nftMagic: 7}
|
||||
TypeIP6Addr = SetDatatype{Name: "ipv6_addr", Bytes: 16, nftMagic: 8}
|
||||
TypeEtherAddr = SetDatatype{Name: "ether_addr", Bytes: 6, nftMagic: 9}
|
||||
TypeEtherType = SetDatatype{Name: "ether_type", Bytes: 2, nftMagic: 10}
|
||||
TypeARPOp = SetDatatype{Name: "arp_op", Bytes: 2, nftMagic: 11}
|
||||
TypeInetProto = SetDatatype{Name: "inet_proto", Bytes: 1, nftMagic: 12}
|
||||
TypeInetService = SetDatatype{Name: "inet_service", Bytes: 2, nftMagic: 13}
|
||||
TypeICMPType = SetDatatype{Name: "icmp_type", Bytes: 1, nftMagic: 14}
|
||||
TypeTCPFlag = SetDatatype{Name: "tcp_flag", Bytes: 1, nftMagic: 15}
|
||||
TypeDCCPPktType = SetDatatype{Name: "dccp_pkttype", Bytes: 1, nftMagic: 16}
|
||||
TypeMHType = SetDatatype{Name: "mh_type", Bytes: 1, nftMagic: 17}
|
||||
TypeTime = SetDatatype{Name: "time", Bytes: 8, nftMagic: 18}
|
||||
TypeMark = SetDatatype{Name: "mark", Bytes: 4, nftMagic: 19}
|
||||
TypeIFIndex = SetDatatype{Name: "iface_index", Bytes: 4, nftMagic: 20}
|
||||
TypeARPHRD = SetDatatype{Name: "iface_type", Bytes: 2, nftMagic: 21}
|
||||
TypeRealm = SetDatatype{Name: "realm", Bytes: 4, nftMagic: 22}
|
||||
TypeClassID = SetDatatype{Name: "classid", Bytes: 4, nftMagic: 23}
|
||||
TypeUID = SetDatatype{Name: "uid", Bytes: sizeOfUIDT, nftMagic: 24}
|
||||
TypeGID = SetDatatype{Name: "gid", Bytes: sizeOfGIDT, nftMagic: 25}
|
||||
TypeCTState = SetDatatype{Name: "ct_state", Bytes: 4, nftMagic: 26}
|
||||
TypeCTDir = SetDatatype{Name: "ct_dir", Bytes: 1, nftMagic: 27}
|
||||
TypeCTStatus = SetDatatype{Name: "ct_status", Bytes: 4, nftMagic: 28}
|
||||
TypeICMP6Type = SetDatatype{Name: "icmpv6_type", Bytes: 1, nftMagic: 29}
|
||||
TypeCTLabel = SetDatatype{Name: "ct_label", Bytes: ctLabelBitSize / 8, nftMagic: 30}
|
||||
TypePktType = SetDatatype{Name: "pkt_type", Bytes: 1, nftMagic: 31}
|
||||
TypeICMPCode = SetDatatype{Name: "icmp_code", Bytes: 1, nftMagic: 32}
|
||||
TypeICMPV6Code = SetDatatype{Name: "icmpv6_code", Bytes: 1, nftMagic: 33}
|
||||
TypeICMPXCode = SetDatatype{Name: "icmpx_code", Bytes: 1, nftMagic: 34}
|
||||
TypeDevGroup = SetDatatype{Name: "devgroup", Bytes: 4, nftMagic: 35}
|
||||
TypeDSCP = SetDatatype{Name: "dscp", Bytes: 1, nftMagic: 36}
|
||||
TypeECN = SetDatatype{Name: "ecn", Bytes: 1, nftMagic: 37}
|
||||
TypeFIBAddr = SetDatatype{Name: "fib_addrtype", Bytes: 4, nftMagic: 38}
|
||||
TypeBoolean = SetDatatype{Name: "boolean", Bytes: 1, nftMagic: 39}
|
||||
TypeCTEventBit = SetDatatype{Name: "ct_event", Bytes: 4, nftMagic: 40}
|
||||
TypeIFName = SetDatatype{Name: "ifname", Bytes: ifNameSize, nftMagic: 41}
|
||||
TypeIGMPType = SetDatatype{Name: "igmp_type", Bytes: 1, nftMagic: 42}
|
||||
TypeTimeDate = SetDatatype{Name: "time", Bytes: 8, nftMagic: 43}
|
||||
TypeTimeHour = SetDatatype{Name: "hour", Bytes: 4, nftMagic: 44}
|
||||
TypeTimeDay = SetDatatype{Name: "day", Bytes: 1, nftMagic: 45}
|
||||
TypeCGroupV2 = SetDatatype{Name: "cgroupsv2", Bytes: 8, nftMagic: 46}
|
||||
|
||||
nftDatatypes = []SetDatatype{
|
||||
TypeVerdict,
|
||||
TypeNFProto,
|
||||
TypeBitmask,
|
||||
TypeInteger,
|
||||
TypeString,
|
||||
TypeLLAddr,
|
||||
TypeIPAddr,
|
||||
TypeIP6Addr,
|
||||
TypeEtherAddr,
|
||||
TypeEtherType,
|
||||
TypeARPOp,
|
||||
TypeInetProto,
|
||||
TypeInetService,
|
||||
TypeICMPType,
|
||||
TypeTCPFlag,
|
||||
TypeDCCPPktType,
|
||||
TypeMHType,
|
||||
TypeTime,
|
||||
TypeMark,
|
||||
TypeIFIndex,
|
||||
TypeARPHRD,
|
||||
TypeRealm,
|
||||
TypeClassID,
|
||||
TypeUID,
|
||||
TypeGID,
|
||||
TypeCTState,
|
||||
TypeCTDir,
|
||||
TypeCTStatus,
|
||||
TypeICMP6Type,
|
||||
TypeCTLabel,
|
||||
TypePktType,
|
||||
TypeICMPCode,
|
||||
TypeICMPV6Code,
|
||||
TypeICMPXCode,
|
||||
TypeDevGroup,
|
||||
TypeDSCP,
|
||||
TypeECN,
|
||||
TypeFIBAddr,
|
||||
TypeBoolean,
|
||||
TypeCTEventBit,
|
||||
TypeIFName,
|
||||
TypeIGMPType,
|
||||
TypeTimeDate,
|
||||
TypeTimeHour,
|
||||
TypeTimeDay,
|
||||
TypeCGroupV2,
|
||||
}
|
||||
|
||||
// ctLabelBitSize is defined in https://git.netfilter.org/nftables/tree/src/ct.c.
|
||||
ctLabelBitSize uint32 = 128
|
||||
|
||||
// ifNameSize is called IFNAMSIZ in linux/if.h.
|
||||
ifNameSize uint32 = 16
|
||||
|
||||
// bits/typesizes.h
|
||||
sizeOfUIDT uint32 = 4
|
||||
sizeOfGIDT uint32 = 4
|
||||
)
|
||||
|
||||
var nftDatatypesByName map[string]SetDatatype
|
||||
var nftDatatypesByMagic map[uint32]SetDatatype
|
||||
|
||||
// Create maps for efficient datatype lookup.
|
||||
func init() {
|
||||
nftDatatypesByName = make(map[string]SetDatatype, len(nftDatatypes))
|
||||
nftDatatypesByMagic = make(map[uint32]SetDatatype, len(nftDatatypes))
|
||||
for _, dt := range nftDatatypes {
|
||||
nftDatatypesByName[dt.Name] = dt
|
||||
nftDatatypesByMagic[dt.nftMagic] = dt
|
||||
}
|
||||
}
|
||||
|
||||
// ErrTooManyTypes is the error returned by ConcatSetType, if nftMagic would overflow.
|
||||
var ErrTooManyTypes = errors.New("too many types to concat")
|
||||
|
||||
// MustConcatSetType does the same as ConcatSetType, but panics instead of an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustConcatSetType(types ...SetDatatype) SetDatatype {
|
||||
t, err := ConcatSetType(types...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ConcatSetType constructs a new SetDatatype which consists of a concatenation
|
||||
// of the passed types. It returns ErrTooManyTypes, if nftMagic would overflow
|
||||
// (more than 5 types).
|
||||
func ConcatSetType(types ...SetDatatype) (SetDatatype, error) {
|
||||
if len(types) > 32/SetConcatTypeBits {
|
||||
return SetDatatype{}, ErrTooManyTypes
|
||||
}
|
||||
|
||||
var magic, bytes uint32
|
||||
names := make([]string, len(types))
|
||||
for i, t := range types {
|
||||
bytes += t.Bytes
|
||||
// concatenated types pad the length to multiples of the register size (4 bytes)
|
||||
// see https://git.netfilter.org/nftables/tree/src/datatype.c?id=488356b895024d0944b20feb1f930558726e0877#n1162
|
||||
if t.Bytes%4 != 0 {
|
||||
bytes += 4 - (t.Bytes % 4)
|
||||
}
|
||||
names[i] = t.Name
|
||||
|
||||
magic <<= SetConcatTypeBits
|
||||
magic |= t.nftMagic & SetConcatTypeMask
|
||||
}
|
||||
return SetDatatype{Name: strings.Join(names, " . "), Bytes: bytes, nftMagic: magic}, nil
|
||||
}
|
||||
|
||||
// ConcatSetTypeElements uses the ConcatSetType name to calculate and return
|
||||
// a list of base types which were used to construct the concatenated type
|
||||
func ConcatSetTypeElements(t SetDatatype) []SetDatatype {
|
||||
names := strings.Split(t.Name, " . ")
|
||||
types := make([]SetDatatype, len(names))
|
||||
for i, n := range names {
|
||||
types[i] = nftDatatypesByName[n]
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
// Set represents an nftables set. Anonymous sets are only valid within the
|
||||
// context of a single batch.
|
||||
type Set struct {
|
||||
Table *Table
|
||||
ID uint32
|
||||
Name string
|
||||
Anonymous bool
|
||||
Constant bool
|
||||
Interval bool
|
||||
IsMap bool
|
||||
HasTimeout bool
|
||||
Counter bool
|
||||
// Can be updated per evaluation path, per `nft list ruleset`
|
||||
// indicates that set contains "flags dynamic"
|
||||
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n298
|
||||
Dynamic bool
|
||||
// Indicates that the set contains a concatenation
|
||||
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n306
|
||||
Concatenation bool
|
||||
Timeout time.Duration
|
||||
KeyType SetDatatype
|
||||
DataType SetDatatype
|
||||
// Either host (binaryutil.NativeEndian) or big (binaryutil.BigEndian) endian as per
|
||||
// https://git.netfilter.org/nftables/tree/include/datatype.h?id=d486c9e626405e829221b82d7355558005b26d8a#n109
|
||||
KeyByteOrder binaryutil.ByteOrder
|
||||
}
|
||||
|
||||
// SetElement represents a data point within a set.
|
||||
type SetElement struct {
|
||||
Key []byte
|
||||
Val []byte
|
||||
// Field used for definition of ending interval value in concatenated types
|
||||
// https://git.netfilter.org/libnftnl/tree/include/set_elem.h?id=e2514c0eff4da7e8e0aabd410f7b7d0b7564c880#n11
|
||||
KeyEnd []byte
|
||||
IntervalEnd bool
|
||||
// To support vmap, a caller must be able to pass Verdict type of data.
|
||||
// If IsMap is true and VerdictData is not nil, then Val of SetElement will be ignored
|
||||
// and VerdictData will be wrapped into Attribute data.
|
||||
VerdictData *expr.Verdict
|
||||
// To support aging of set elements
|
||||
Timeout time.Duration
|
||||
|
||||
// Life left of the "timeout" elements
|
||||
Expires time.Duration
|
||||
|
||||
Counter *expr.Counter
|
||||
}
|
||||
|
||||
func (s *SetElement) decode(fam byte) func(b []byte) error {
|
||||
return func(b []byte) error {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create nested attribute decoder: %v", err)
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_SET_ELEM_KEY:
|
||||
s.Key, err = decodeElement(ad.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case NFTA_SET_ELEM_KEY_END:
|
||||
s.KeyEnd, err = decodeElement(ad.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case unix.NFTA_SET_ELEM_DATA:
|
||||
s.Val, err = decodeElement(ad.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case unix.NFTA_SET_ELEM_FLAGS:
|
||||
flags := ad.Uint32()
|
||||
s.IntervalEnd = (flags & unix.NFT_SET_ELEM_INTERVAL_END) != 0
|
||||
case unix.NFTA_SET_ELEM_TIMEOUT:
|
||||
s.Timeout = time.Millisecond * time.Duration(ad.Uint64())
|
||||
case unix.NFTA_SET_ELEM_EXPIRATION:
|
||||
s.Expires = time.Millisecond * time.Duration(ad.Uint64())
|
||||
case unix.NFTA_SET_ELEM_EXPR:
|
||||
elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad, ad.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, elem := range elems {
|
||||
switch item := elem.(type) {
|
||||
case *expr.Counter:
|
||||
s.Counter = item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func decodeElement(d []byte) ([]byte, error) {
|
||||
ad, err := netlink.NewAttributeDecoder(d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create nested attribute decoder: %v", err)
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
var b []byte
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_SET_ELEM_KEY:
|
||||
fallthrough
|
||||
case unix.NFTA_SET_ELEM_DATA:
|
||||
b = ad.Bytes()
|
||||
}
|
||||
}
|
||||
if err := ad.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// SetAddElements applies data points to an nftables set.
|
||||
func (cc *Conn) SetAddElements(s *Set, vals []SetElement) error {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if s.Anonymous {
|
||||
return errors.New("anonymous sets cannot be updated")
|
||||
}
|
||||
|
||||
elements, err := s.makeElemList(vals, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, error) {
|
||||
var elements []netlink.Attribute
|
||||
|
||||
for i, v := range vals {
|
||||
item := make([]netlink.Attribute, 0)
|
||||
var flags uint32
|
||||
if v.IntervalEnd {
|
||||
flags |= unix.NFT_SET_ELEM_INTERVAL_END
|
||||
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_FLAGS | unix.NLA_F_NESTED, Data: binaryutil.BigEndian.PutUint32(flags)})
|
||||
}
|
||||
|
||||
encodedKey, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.Key}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal key %d: %v", i, err)
|
||||
}
|
||||
|
||||
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_KEY | unix.NLA_F_NESTED, Data: encodedKey})
|
||||
if len(v.KeyEnd) > 0 {
|
||||
encodedKeyEnd, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.KeyEnd}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal key end %d: %v", i, err)
|
||||
}
|
||||
item = append(item, netlink.Attribute{Type: NFTA_SET_ELEM_KEY_END | unix.NLA_F_NESTED, Data: encodedKeyEnd})
|
||||
}
|
||||
if s.HasTimeout && v.Timeout != 0 {
|
||||
// Set has Timeout flag set, which means an individual element can specify its own timeout.
|
||||
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(v.Timeout.Milliseconds()))})
|
||||
}
|
||||
// The following switch statement deal with 3 different types of elements.
|
||||
// 1. v is an element of vmap
|
||||
// 2. v is an element of a regular map
|
||||
// 3. v is an element of a regular set (default)
|
||||
switch {
|
||||
case v.VerdictData != nil:
|
||||
// Since VerdictData is not nil, v is vmap element, need to add to the attributes
|
||||
encodedVal := []byte{}
|
||||
encodedKind, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(uint32(v.VerdictData.Kind))},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal item %d: %v", i, err)
|
||||
}
|
||||
encodedVal = append(encodedVal, encodedKind...)
|
||||
if len(v.VerdictData.Chain) != 0 {
|
||||
encodedChain, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_ELEM_DATA, Data: []byte(v.VerdictData.Chain + "\x00")},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal item %d: %v", i, err)
|
||||
}
|
||||
encodedVal = append(encodedVal, encodedChain...)
|
||||
}
|
||||
encodedVerdict, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal item %d: %v", i, err)
|
||||
}
|
||||
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVerdict})
|
||||
case len(v.Val) > 0:
|
||||
// Since v.Val's length is not 0 then, v is a regular map element, need to add to the attributes
|
||||
encodedVal, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.Val}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal item %d: %v", i, err)
|
||||
}
|
||||
|
||||
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal})
|
||||
default:
|
||||
// If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes
|
||||
}
|
||||
|
||||
encodedItem, err := netlink.MarshalAttributes(item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal item %d: %v", i, err)
|
||||
}
|
||||
elements = append(elements, netlink.Attribute{Type: uint16(i+1) | unix.NLA_F_NESTED, Data: encodedItem})
|
||||
}
|
||||
|
||||
encodedElem, err := netlink.MarshalAttributes(elements)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal elements: %v", err)
|
||||
}
|
||||
|
||||
return []netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
|
||||
{Type: unix.NFTA_LOOKUP_SET_ID, Data: binaryutil.BigEndian.PutUint32(id)},
|
||||
{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_SET_ELEM_LIST_ELEMENTS | unix.NLA_F_NESTED, Data: encodedElem},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddSet adds the specified Set.
|
||||
func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
// Based on nft implementation & linux source.
|
||||
// Link: https://github.com/torvalds/linux/blob/49a57857aeea06ca831043acbb0fa5e0f50602fd/net/netfilter/nf_tables_api.c#L3395
|
||||
// Another reference: https://git.netfilter.org/nftables/tree/src
|
||||
|
||||
if s.Anonymous && !s.Constant {
|
||||
return errors.New("anonymous structs must be constant")
|
||||
}
|
||||
|
||||
if s.ID == 0 {
|
||||
allocSetID++
|
||||
s.ID = allocSetID
|
||||
if s.Anonymous {
|
||||
s.Name = "__set%d"
|
||||
if s.IsMap {
|
||||
s.Name = "__map%d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var flags uint32
|
||||
if s.Anonymous {
|
||||
flags |= unix.NFT_SET_ANONYMOUS
|
||||
}
|
||||
if s.Constant {
|
||||
flags |= unix.NFT_SET_CONSTANT
|
||||
}
|
||||
if s.Interval {
|
||||
flags |= unix.NFT_SET_INTERVAL
|
||||
}
|
||||
if s.IsMap {
|
||||
flags |= unix.NFT_SET_MAP
|
||||
}
|
||||
if s.HasTimeout {
|
||||
flags |= unix.NFT_SET_TIMEOUT
|
||||
}
|
||||
if s.Dynamic {
|
||||
flags |= unix.NFT_SET_EVAL
|
||||
}
|
||||
if s.Concatenation {
|
||||
flags |= NFT_SET_CONCAT
|
||||
}
|
||||
tableInfo := []netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
|
||||
{Type: unix.NFTA_SET_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)},
|
||||
{Type: unix.NFTA_SET_KEY_TYPE, Data: binaryutil.BigEndian.PutUint32(s.KeyType.nftMagic)},
|
||||
{Type: unix.NFTA_SET_KEY_LEN, Data: binaryutil.BigEndian.PutUint32(s.KeyType.Bytes)},
|
||||
{Type: unix.NFTA_SET_ID, Data: binaryutil.BigEndian.PutUint32(s.ID)},
|
||||
}
|
||||
if s.IsMap {
|
||||
// Check if it is vmap case
|
||||
if s.DataType.nftMagic == 1 {
|
||||
// For Verdict data type, the expected magic is 0xfffff0
|
||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_DATA_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(unix.NFT_DATA_VERDICT))},
|
||||
netlink.Attribute{Type: unix.NFTA_SET_DATA_LEN, Data: binaryutil.BigEndian.PutUint32(s.DataType.Bytes)})
|
||||
} else {
|
||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_DATA_TYPE, Data: binaryutil.BigEndian.PutUint32(s.DataType.nftMagic)},
|
||||
netlink.Attribute{Type: unix.NFTA_SET_DATA_LEN, Data: binaryutil.BigEndian.PutUint32(s.DataType.Bytes)})
|
||||
}
|
||||
}
|
||||
if s.HasTimeout && s.Timeout != 0 {
|
||||
// If Set's global timeout is specified, add it to set's attributes
|
||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(s.Timeout.Milliseconds()))})
|
||||
}
|
||||
if s.Constant {
|
||||
// nft cli tool adds the number of elements to set/map's descriptor
|
||||
// It make sense to do only if a set or map are constant, otherwise skip NFTA_SET_DESC attribute
|
||||
numberOfElements, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(uint32(len(vals)))},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to marshal number of elements %d: %v", len(vals), err)
|
||||
}
|
||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: numberOfElements})
|
||||
}
|
||||
if s.Concatenation {
|
||||
// Length of concatenated types is a must, otherwise segfaults when executing nft list ruleset
|
||||
var concatDefinition []byte
|
||||
elements := ConcatSetTypeElements(s.KeyType)
|
||||
for i, v := range elements {
|
||||
// Marshal base type size value
|
||||
valData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(v.Bytes)},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to marshal element key size %d: %v", i, err)
|
||||
}
|
||||
// Marshal base type size description
|
||||
descSize, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_DESC_SIZE, Data: valData},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to marshal base type size description: %w", err)
|
||||
}
|
||||
concatDefinition = append(concatDefinition, descSize...)
|
||||
}
|
||||
// Marshal all base type descriptions into concatenation size description
|
||||
concatBytes, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NLA_F_NESTED | NFTA_SET_DESC_CONCAT, Data: concatDefinition}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to marshal concat definition %v", err)
|
||||
}
|
||||
// Marshal concat size description as set description
|
||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: concatBytes})
|
||||
}
|
||||
if s.Anonymous || s.Constant || s.Interval || s.KeyByteOrder == binaryutil.BigEndian {
|
||||
tableInfo = append(tableInfo,
|
||||
// Semantically useless - kept for binary compatability with nft
|
||||
netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: []byte("\x00\x04\x02\x00\x00\x00")})
|
||||
} else if s.KeyByteOrder == binaryutil.NativeEndian {
|
||||
// Per https://git.netfilter.org/nftables/tree/src/mnl.c?id=187c6d01d35722618c2711bbc49262c286472c8f#n1165
|
||||
tableInfo = append(tableInfo,
|
||||
netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: []byte("\x00\x04\x01\x00\x00\x00")})
|
||||
}
|
||||
if s.Counter {
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_LIST_ELEM, Data: []byte("counter\x00")},
|
||||
{Type: unix.NFTA_SET_ELEM_PAD | unix.NFTA_SET_ELEM_DATA, Data: []byte{}},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | NFTA_SET_ELEM_EXPRESSIONS, Data: data})
|
||||
}
|
||||
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(tableInfo)...),
|
||||
})
|
||||
|
||||
// Set the values of the set if initial values were provided.
|
||||
if len(vals) > 0 {
|
||||
hdrType := unix.NFT_MSG_NEWSETELEM
|
||||
elements, err := s.makeElemList(vals, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | hdrType),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DelSet deletes a specific set, along with all elements it contains.
|
||||
func (cc *Conn) DelSet(s *Set) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
|
||||
})
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(s.Table.Family), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
// SetDeleteElements deletes data points from an nftables set.
|
||||
func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if s.Anonymous {
|
||||
return errors.New("anonymous sets cannot be updated")
|
||||
}
|
||||
|
||||
elements, err := s.makeElemList(vals, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
|
||||
},
|
||||
Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushSet deletes all data points from an nftables set.
|
||||
func (cc *Conn) FlushSet(s *Set) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
|
||||
})
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(s.Table.Family), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
newSetHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET)
|
||||
delSetHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET)
|
||||
)
|
||||
|
||||
func setsFromMsg(msg netlink.Message) (*Set, error) {
|
||||
if got, want1, want2 := msg.Header.Type, newSetHeaderType, delSetHeaderType; got != want1 && got != want2 {
|
||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
|
||||
}
|
||||
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
var set Set
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_SET_NAME:
|
||||
set.Name = ad.String()
|
||||
case unix.NFTA_SET_ID:
|
||||
set.ID = binary.BigEndian.Uint32(ad.Bytes())
|
||||
case unix.NFTA_SET_TIMEOUT:
|
||||
set.Timeout = time.Duration(time.Millisecond * time.Duration(binary.BigEndian.Uint64(ad.Bytes())))
|
||||
set.HasTimeout = true
|
||||
case unix.NFTA_SET_FLAGS:
|
||||
flags := ad.Uint32()
|
||||
set.Constant = (flags & unix.NFT_SET_CONSTANT) != 0
|
||||
set.Anonymous = (flags & unix.NFT_SET_ANONYMOUS) != 0
|
||||
set.Interval = (flags & unix.NFT_SET_INTERVAL) != 0
|
||||
set.IsMap = (flags & unix.NFT_SET_MAP) != 0
|
||||
set.HasTimeout = (flags & unix.NFT_SET_TIMEOUT) != 0
|
||||
set.Concatenation = (flags & NFT_SET_CONCAT) != 0
|
||||
case unix.NFTA_SET_KEY_TYPE:
|
||||
nftMagic := ad.Uint32()
|
||||
dt, err := parseSetDatatype(nftMagic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine data type: %w", err)
|
||||
}
|
||||
set.KeyType = dt
|
||||
case unix.NFTA_SET_KEY_LEN:
|
||||
set.KeyType.Bytes = binary.BigEndian.Uint32(ad.Bytes())
|
||||
case unix.NFTA_SET_DATA_TYPE:
|
||||
nftMagic := ad.Uint32()
|
||||
// Special case for the data type verdict, in the message it is stored as 0xffffff00 but it is defined as 1
|
||||
if nftMagic == 0xffffff00 {
|
||||
set.KeyType = TypeVerdict
|
||||
break
|
||||
}
|
||||
dt, err := parseSetDatatype(nftMagic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine data type: %w", err)
|
||||
}
|
||||
set.DataType = dt
|
||||
case unix.NFTA_SET_DATA_LEN:
|
||||
set.DataType.Bytes = binary.BigEndian.Uint32(ad.Bytes())
|
||||
}
|
||||
}
|
||||
return &set, nil
|
||||
}
|
||||
|
||||
func parseSetDatatype(magic uint32) (SetDatatype, error) {
|
||||
types := make([]SetDatatype, 0, 32/SetConcatTypeBits)
|
||||
for magic != 0 {
|
||||
t := magic & SetConcatTypeMask
|
||||
magic = magic >> SetConcatTypeBits
|
||||
dt, ok := nftDatatypesByMagic[t]
|
||||
if !ok {
|
||||
return TypeInvalid, fmt.Errorf("could not determine data type %+v", dt)
|
||||
}
|
||||
// Because we start with the last type, we insert the later types at the front.
|
||||
types = append([]SetDatatype{dt}, types...)
|
||||
}
|
||||
|
||||
dt, err := ConcatSetType(types...)
|
||||
if err != nil {
|
||||
return TypeInvalid, fmt.Errorf("could not create data type: %w", err)
|
||||
}
|
||||
return dt, nil
|
||||
}
|
||||
|
||||
var (
|
||||
newElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM)
|
||||
delElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM)
|
||||
)
|
||||
|
||||
func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) {
|
||||
if got, want1, want2 := msg.Header.Type, newElemHeaderType, delElemHeaderType; got != want1 && got != want2 {
|
||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
|
||||
}
|
||||
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
var elements []SetElement
|
||||
for ad.Next() {
|
||||
b := ad.Bytes()
|
||||
if ad.Type() == unix.NFTA_SET_ELEM_LIST_ELEMENTS {
|
||||
ad, err := netlink.NewAttributeDecoder(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
|
||||
for ad.Next() {
|
||||
var elem SetElement
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_LIST_ELEM:
|
||||
ad.Do(elem.decode(fam))
|
||||
}
|
||||
elements = append(elements, elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return elements, nil
|
||||
}
|
||||
|
||||
// GetSets returns the sets in the specified table.
|
||||
func (cc *Conn) GetSets(t *Table) ([]*Set, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_TABLE, Data: []byte(t.Name + "\x00")},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
message := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSET),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
}
|
||||
|
||||
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
|
||||
return nil, fmt.Errorf("SendMessages: %v", err)
|
||||
}
|
||||
|
||||
reply, err := receiveAckAware(conn, message.Header.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Receive: %v", err)
|
||||
}
|
||||
var sets []*Set
|
||||
for _, msg := range reply {
|
||||
s, err := setsFromMsg(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Table = &Table{Name: t.Name, Use: t.Use, Flags: t.Flags, Family: t.Family}
|
||||
sets = append(sets, s)
|
||||
}
|
||||
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
// GetSetByName returns the set in the specified table if matching name is found.
|
||||
func (cc *Conn) GetSetByName(t *Table, name string) (*Set, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_TABLE, Data: []byte(t.Name + "\x00")},
|
||||
{Type: unix.NFTA_SET_NAME, Data: []byte(name + "\x00")},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
message := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSET),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
}
|
||||
|
||||
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
|
||||
return nil, fmt.Errorf("SendMessages: %w", err)
|
||||
}
|
||||
|
||||
reply, err := receiveAckAware(conn, message.Header.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Receive: %w", err)
|
||||
}
|
||||
|
||||
if len(reply) != 1 {
|
||||
return nil, fmt.Errorf("Receive: expected to receive 1 message but got %d", len(reply))
|
||||
}
|
||||
rs, err := setsFromMsg(reply[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs.Table = &Table{Name: t.Name, Use: t.Use, Flags: t.Flags, Family: t.Family}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// GetSetElements returns the elements in the specified set.
|
||||
func (cc *Conn) GetSetElements(s *Set) ([]SetElement, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||
{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
|
||||
{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
message := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSETELEM),
|
||||
Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
|
||||
},
|
||||
Data: append(extraHeader(uint8(s.Table.Family), 0), data...),
|
||||
}
|
||||
|
||||
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
|
||||
return nil, fmt.Errorf("SendMessages: %v", err)
|
||||
}
|
||||
|
||||
reply, err := receiveAckAware(conn, message.Header.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Receive: %v", err)
|
||||
}
|
||||
var elems []SetElement
|
||||
for _, msg := range reply {
|
||||
s, err := elementsFromMsg(uint8(s.Table.Family), msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems = append(elems, s...)
|
||||
}
|
||||
|
||||
return elems, nil
|
||||
}
|
||||
212
vendor/github.com/google/nftables/table.go
generated
vendored
Normal file
212
vendor/github.com/google/nftables/table.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
newTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE)
|
||||
delTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE)
|
||||
)
|
||||
|
||||
// TableFamily specifies the address family for this table.
|
||||
type TableFamily byte
|
||||
|
||||
// Possible TableFamily values.
|
||||
const (
|
||||
TableFamilyUnspecified TableFamily = unix.NFPROTO_UNSPEC
|
||||
TableFamilyINet TableFamily = unix.NFPROTO_INET
|
||||
TableFamilyIPv4 TableFamily = unix.NFPROTO_IPV4
|
||||
TableFamilyIPv6 TableFamily = unix.NFPROTO_IPV6
|
||||
TableFamilyARP TableFamily = unix.NFPROTO_ARP
|
||||
TableFamilyNetdev TableFamily = unix.NFPROTO_NETDEV
|
||||
TableFamilyBridge TableFamily = unix.NFPROTO_BRIDGE
|
||||
)
|
||||
|
||||
// A Table contains Chains. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_tables
|
||||
type Table struct {
|
||||
Name string // NFTA_TABLE_NAME
|
||||
Use uint32 // NFTA_TABLE_USE (Number of chains in table)
|
||||
Flags uint32 // NFTA_TABLE_FLAGS
|
||||
Family TableFamily
|
||||
}
|
||||
|
||||
// DelTable deletes a specific table, along with all chains/rules it contains.
|
||||
func (cc *Conn) DelTable(t *Table) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")},
|
||||
{Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}},
|
||||
})
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
func (cc *Conn) addTable(t *Table, flag netlink.HeaderFlags) *Table {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")},
|
||||
{Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}},
|
||||
})
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE),
|
||||
Flags: netlink.Request | netlink.Acknowledge | flag,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
})
|
||||
return t
|
||||
}
|
||||
|
||||
// AddTable adds the specified Table, just like `nft add table ...`.
|
||||
// See also https://wiki.nftables.org/wiki-nftables/index.php/Configuring_tables
|
||||
func (cc *Conn) AddTable(t *Table) *Table {
|
||||
return cc.addTable(t, netlink.Create)
|
||||
}
|
||||
|
||||
// CreateTable create the specified Table if it do not existed.
|
||||
// just like `nft create table ...`.
|
||||
func (cc *Conn) CreateTable(t *Table) *Table {
|
||||
return cc.addTable(t, netlink.Excl)
|
||||
}
|
||||
|
||||
// FlushTable removes all rules in all chains within the specified Table. See also
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_tables#Flushing_tables
|
||||
func (cc *Conn) FlushTable(t *Table) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
data := cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")},
|
||||
})
|
||||
cc.messages = append(cc.messages, netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE),
|
||||
Flags: netlink.Request | netlink.Acknowledge,
|
||||
},
|
||||
Data: append(extraHeader(uint8(t.Family), 0), data...),
|
||||
})
|
||||
}
|
||||
|
||||
// ListTable returns table found for the specified name. Searches for
|
||||
// the table under IPv4 family. As per nft man page: "When no address
|
||||
// family is specified, ip is used by default."
|
||||
func (cc *Conn) ListTable(name string) (*Table, error) {
|
||||
return cc.ListTableOfFamily(name, TableFamilyIPv4)
|
||||
}
|
||||
|
||||
// ListTableOfFamily returns table found for the specified name and table family
|
||||
func (cc *Conn) ListTableOfFamily(name string, family TableFamily) (*Table, error) {
|
||||
t, err := cc.listTablesOfNameAndFamily(name, family)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if got, want := len(t), 1; got != want {
|
||||
return nil, fmt.Errorf("expected table count %d, got %d", want, got)
|
||||
}
|
||||
return t[0], nil
|
||||
}
|
||||
|
||||
// ListTables returns currently configured tables in the kernel
|
||||
func (cc *Conn) ListTables() ([]*Table, error) {
|
||||
return cc.ListTablesOfFamily(TableFamilyUnspecified)
|
||||
}
|
||||
|
||||
// ListTablesOfFamily returns currently configured tables for the specified table family
|
||||
// in the kernel. It lists all tables if family is TableFamilyUnspecified.
|
||||
func (cc *Conn) ListTablesOfFamily(family TableFamily) ([]*Table, error) {
|
||||
return cc.listTablesOfNameAndFamily("", family)
|
||||
}
|
||||
|
||||
func (cc *Conn) listTablesOfNameAndFamily(name string, family TableFamily) ([]*Table, error) {
|
||||
conn, closer, err := cc.netlinkConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = closer() }()
|
||||
|
||||
data := extraHeader(uint8(family), 0)
|
||||
flags := netlink.Request | netlink.Dump
|
||||
if name != "" {
|
||||
data = append(data, cc.marshalAttr([]netlink.Attribute{
|
||||
{Type: unix.NFTA_TABLE_NAME, Data: []byte(name + "\x00")},
|
||||
})...)
|
||||
flags = netlink.Request
|
||||
}
|
||||
|
||||
msg := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETTABLE),
|
||||
Flags: flags,
|
||||
},
|
||||
Data: data,
|
||||
}
|
||||
|
||||
response, err := conn.Execute(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tables []*Table
|
||||
for _, m := range response {
|
||||
t, err := tableFromMsg(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables = append(tables, t)
|
||||
}
|
||||
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func tableFromMsg(msg netlink.Message) (*Table, error) {
|
||||
if got, want1, want2 := msg.Header.Type, newTableHeaderType, delTableHeaderType; got != want1 && got != want2 {
|
||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
|
||||
}
|
||||
|
||||
var t Table
|
||||
t.Family = TableFamily(msg.Data[0])
|
||||
|
||||
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
case unix.NFTA_TABLE_NAME:
|
||||
t.Name = ad.String()
|
||||
case unix.NFTA_TABLE_USE:
|
||||
t.Use = ad.Uint32()
|
||||
case unix.NFTA_TABLE_FLAGS:
|
||||
t.Flags = ad.Uint32()
|
||||
}
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
89
vendor/github.com/google/nftables/util.go
generated
vendored
Normal file
89
vendor/github.com/google/nftables/util.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func extraHeader(family uint8, resID uint16) []byte {
|
||||
return append([]byte{
|
||||
family,
|
||||
unix.NFNETLINK_V0,
|
||||
}, binaryutil.BigEndian.PutUint16(resID)...)
|
||||
}
|
||||
|
||||
// General form of address family dependent message, see
|
||||
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nfnetlink.h#29
|
||||
type NFGenMsg struct {
|
||||
NFGenFamily uint8
|
||||
Version uint8
|
||||
ResourceID uint16
|
||||
}
|
||||
|
||||
func (genmsg *NFGenMsg) Decode(b []byte) {
|
||||
if len(b) < 16 {
|
||||
return
|
||||
}
|
||||
genmsg.NFGenFamily = b[0]
|
||||
genmsg.Version = b[1]
|
||||
genmsg.ResourceID = binary.BigEndian.Uint16(b[2:])
|
||||
}
|
||||
|
||||
// NetFirstAndLastIP takes the beginning address of an entire network in CIDR
|
||||
// notation (e.g. 192.168.1.0/24) and returns the first and last IP addresses
|
||||
// within the network (e.g. first 192.168.1.0, last 192.168.1.255).
|
||||
//
|
||||
// Note that these are the first and last IP addresses, not the first and last
|
||||
// *usable* IP addresses (which would be 192.168.1.1 and 192.168.1.254,
|
||||
// respectively, for 192.168.1.0/24).
|
||||
func NetFirstAndLastIP(networkCIDR string) (first, last net.IP, err error) {
|
||||
_, subnet, err := net.ParseCIDR(networkCIDR)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
first = make(net.IP, len(subnet.IP))
|
||||
last = make(net.IP, len(subnet.IP))
|
||||
|
||||
switch len(subnet.IP) {
|
||||
case net.IPv4len:
|
||||
mask := binary.BigEndian.Uint32(subnet.Mask)
|
||||
ip := binary.BigEndian.Uint32(subnet.IP)
|
||||
// To achieve the first IP address, we need to AND the IP with the mask.
|
||||
// The AND operation will set all bits in the host part to 0.
|
||||
binary.BigEndian.PutUint32(first, ip&mask)
|
||||
// To achieve the last IP address, we need to OR the IP network with the inverted mask.
|
||||
// The AND between the IP and the mask will set all bits in the host part to 0, keeping the network part.
|
||||
// The XOR between the mask and 0xffffffff will set all bits in the host part to 1, and the network part to 0.
|
||||
// The OR operation will keep the host part unchanged, and sets the host part to all 1.
|
||||
binary.BigEndian.PutUint32(last, (ip&mask)|(mask^0xffffffff))
|
||||
case net.IPv6len:
|
||||
mask1 := binary.BigEndian.Uint64(subnet.Mask[:8])
|
||||
mask2 := binary.BigEndian.Uint64(subnet.Mask[8:])
|
||||
ip1 := binary.BigEndian.Uint64(subnet.IP[:8])
|
||||
ip2 := binary.BigEndian.Uint64(subnet.IP[8:])
|
||||
binary.BigEndian.PutUint64(first[:8], ip1&mask1)
|
||||
binary.BigEndian.PutUint64(first[8:], ip2&mask2)
|
||||
binary.BigEndian.PutUint64(last[:8], (ip1&mask1)|(mask1^0xffffffffffffffff))
|
||||
binary.BigEndian.PutUint64(last[8:], (ip2&mask2)|(mask2^0xffffffffffffffff))
|
||||
}
|
||||
|
||||
return first, last, nil
|
||||
}
|
||||
94
vendor/github.com/google/nftables/xt/info.go
generated
vendored
Normal file
94
vendor/github.com/google/nftables/xt/info.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// TableFamily specifies the address family of the table Match or Target Info
|
||||
// data is contained in. On purpose, we don't import the expr package here in
|
||||
// order to keep the option open to import this package instead into expr.
|
||||
type TableFamily byte
|
||||
|
||||
// InfoAny is a (un)marshaling implemented by any info type.
|
||||
type InfoAny interface {
|
||||
marshal(fam TableFamily, rev uint32) ([]byte, error)
|
||||
unmarshal(fam TableFamily, rev uint32, data []byte) error
|
||||
}
|
||||
|
||||
// Marshal a Match or Target Info type into its binary representation.
|
||||
func Marshal(fam TableFamily, rev uint32, info InfoAny) ([]byte, error) {
|
||||
return info.marshal(fam, rev)
|
||||
}
|
||||
|
||||
// Unmarshal Info binary payload into its corresponding dedicated type as
|
||||
// indicated by the name argument. In several cases, unmarshalling depends on
|
||||
// the specific table family the Target or Match expression with the info
|
||||
// payload belongs to, as well as the specific info structure revision.
|
||||
func Unmarshal(name string, fam TableFamily, rev uint32, data []byte) (InfoAny, error) {
|
||||
var i InfoAny
|
||||
switch name {
|
||||
case "addrtype":
|
||||
switch rev {
|
||||
case 0:
|
||||
i = &AddrType{}
|
||||
case 1:
|
||||
i = &AddrTypeV1{}
|
||||
}
|
||||
case "conntrack":
|
||||
switch rev {
|
||||
case 1:
|
||||
i = &ConntrackMtinfo1{}
|
||||
case 2:
|
||||
i = &ConntrackMtinfo2{}
|
||||
case 3:
|
||||
i = &ConntrackMtinfo3{}
|
||||
}
|
||||
case "tcp":
|
||||
i = &Tcp{}
|
||||
case "udp":
|
||||
i = &Udp{}
|
||||
case "SNAT":
|
||||
if fam == unix.NFPROTO_IPV4 {
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
}
|
||||
case "DNAT":
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
if rev == 0 {
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case unix.NFPROTO_IPV6:
|
||||
switch rev {
|
||||
case 1:
|
||||
i = &NatRange{}
|
||||
case 2:
|
||||
i = &NatRange2{}
|
||||
}
|
||||
}
|
||||
case "MASQUERADE":
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
}
|
||||
case "REDIRECT":
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
if rev == 0 {
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case unix.NFPROTO_IPV6:
|
||||
i = &NatRange{}
|
||||
}
|
||||
}
|
||||
if i == nil {
|
||||
i = &Unknown{}
|
||||
}
|
||||
if err := i.unmarshal(fam, rev, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
89
vendor/github.com/google/nftables/xt/match_addrtype.go
generated
vendored
Normal file
89
vendor/github.com/google/nftables/xt/match_addrtype.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// Rev. 0, see https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_addrtype.h#L38
|
||||
type AddrType struct {
|
||||
Source uint16
|
||||
Dest uint16
|
||||
InvertSource bool
|
||||
InvertDest bool
|
||||
}
|
||||
|
||||
type AddrTypeFlags uint32
|
||||
|
||||
const (
|
||||
AddrTypeUnspec AddrTypeFlags = 1 << iota
|
||||
AddrTypeUnicast
|
||||
AddrTypeLocal
|
||||
AddrTypeBroadcast
|
||||
AddrTypeAnycast
|
||||
AddrTypeMulticast
|
||||
AddrTypeBlackhole
|
||||
AddrTypeUnreachable
|
||||
AddrTypeProhibit
|
||||
AddrTypeThrow
|
||||
AddrTypeNat
|
||||
AddrTypeXresolve
|
||||
)
|
||||
|
||||
// See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_addrtype.h#L31
|
||||
type AddrTypeV1 struct {
|
||||
Source uint16
|
||||
Dest uint16
|
||||
Flags AddrTypeFlags
|
||||
}
|
||||
|
||||
func (x *AddrType) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.Source)
|
||||
ab.PutUint16(x.Dest)
|
||||
putBool32(&ab, x.InvertSource)
|
||||
putBool32(&ab, x.InvertDest)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *AddrType) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.Source, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.Dest, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.InvertSource, err = bool32(&ab); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.InvertDest, err = bool32(&ab); err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AddrTypeV1) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.Source)
|
||||
ab.PutUint16(x.Dest)
|
||||
ab.PutUint32(uint32(x.Flags))
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *AddrTypeV1) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.Source, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.Dest, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
var flags uint32
|
||||
if flags, err = ab.Uint32(); err != nil {
|
||||
return nil
|
||||
}
|
||||
x.Flags = AddrTypeFlags(flags)
|
||||
return nil
|
||||
}
|
||||
260
vendor/github.com/google/nftables/xt/match_conntrack.go
generated
vendored
Normal file
260
vendor/github.com/google/nftables/xt/match_conntrack.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
type ConntrackFlags uint16
|
||||
|
||||
const (
|
||||
ConntrackState ConntrackFlags = 1 << iota
|
||||
ConntrackProto
|
||||
ConntrackOrigSrc
|
||||
ConntrackOrigDst
|
||||
ConntrackReplSrc
|
||||
ConntrackReplDst
|
||||
ConntrackStatus
|
||||
ConntrackExpires
|
||||
ConntrackOrigSrcPort
|
||||
ConntrackOrigDstPort
|
||||
ConntrackReplSrcPort
|
||||
ConntrackReplDstPrt
|
||||
ConntrackDirection
|
||||
ConntrackStateAlias
|
||||
)
|
||||
|
||||
type ConntrackMtinfoBase struct {
|
||||
OrigSrcAddr net.IP
|
||||
OrigSrcMask net.IPMask
|
||||
OrigDstAddr net.IP
|
||||
OrigDstMask net.IPMask
|
||||
ReplSrcAddr net.IP
|
||||
ReplSrcMask net.IPMask
|
||||
ReplDstAddr net.IP
|
||||
ReplDstMask net.IPMask
|
||||
ExpiresMin uint32
|
||||
ExpiresMax uint32
|
||||
L4Proto uint16
|
||||
OrigSrcPort uint16
|
||||
OrigDstPort uint16
|
||||
ReplSrcPort uint16
|
||||
ReplDstPort uint16
|
||||
MatchFlags uint16
|
||||
InvertFlags uint16
|
||||
}
|
||||
|
||||
// See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_conntrack.h#L38
|
||||
type ConntrackMtinfo1 struct {
|
||||
ConntrackMtinfoBase
|
||||
StateMask uint8
|
||||
StatusMask uint8
|
||||
}
|
||||
|
||||
// See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_conntrack.h#L51
|
||||
type ConntrackMtinfo2 struct {
|
||||
ConntrackMtinfoBase
|
||||
StateMask uint16
|
||||
StatusMask uint16
|
||||
}
|
||||
|
||||
// See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_conntrack.h#L64
|
||||
type ConntrackMtinfo3 struct {
|
||||
ConntrackMtinfo2
|
||||
OrigSrcPortHigh uint16
|
||||
OrigDstPortHigh uint16
|
||||
ReplSrcPortHigh uint16
|
||||
ReplDstPortHigh uint16
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfoBase) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
if err := putIPv46(ab, fam, x.OrigSrcAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.OrigSrcMask); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.OrigDstAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.OrigDstMask); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.ReplSrcAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.ReplSrcMask); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.ReplDstAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.ReplDstMask); err != nil {
|
||||
return err
|
||||
}
|
||||
ab.PutUint32(x.ExpiresMin)
|
||||
ab.PutUint32(x.ExpiresMax)
|
||||
ab.PutUint16(x.L4Proto)
|
||||
ab.PutUint16(x.OrigSrcPort)
|
||||
ab.PutUint16(x.OrigDstPort)
|
||||
ab.PutUint16(x.ReplSrcPort)
|
||||
ab.PutUint16(x.ReplDstPort)
|
||||
ab.PutUint16(x.MatchFlags)
|
||||
ab.PutUint16(x.InvertFlags)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfoBase) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if x.OrigSrcAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigSrcMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ExpiresMin, err = ab.Uint32(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ExpiresMax, err = ab.Uint32(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.L4Proto, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigSrcPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MatchFlags, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.InvertFlags, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo1) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.ConntrackMtinfoBase.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ab.PutUint8(x.StateMask)
|
||||
ab.PutUint8(x.StatusMask)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo1) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.ConntrackMtinfoBase.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StateMask, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StatusMask, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
if err := x.ConntrackMtinfoBase.marshalAB(fam, rev, ab); err != nil {
|
||||
return err
|
||||
}
|
||||
ab.PutUint16(x.StateMask)
|
||||
ab.PutUint16(x.StatusMask)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if err = x.ConntrackMtinfoBase.unmarshalAB(fam, rev, ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StateMask, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StatusMask, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo3) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.ConntrackMtinfo2.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ab.PutUint16(x.OrigSrcPortHigh)
|
||||
ab.PutUint16(x.OrigDstPortHigh)
|
||||
ab.PutUint16(x.ReplSrcPortHigh)
|
||||
ab.PutUint16(x.ReplDstPortHigh)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo3) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.ConntrackMtinfo2.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigSrcPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
74
vendor/github.com/google/nftables/xt/match_tcp.go
generated
vendored
Normal file
74
vendor/github.com/google/nftables/xt/match_tcp.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// Tcp is the Match.Info payload for the tcp xtables extension
|
||||
// (https://wiki.nftables.org/wiki-nftables/index.php/Supported_features_compared_to_xtables#tcp).
|
||||
//
|
||||
// See
|
||||
// https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_tcpudp.h#L8
|
||||
type Tcp struct {
|
||||
SrcPorts [2]uint16 // min, max source port range
|
||||
DstPorts [2]uint16 // min, max destination port range
|
||||
Option uint8 // TCP option if non-zero
|
||||
FlagsMask uint8 // TCP flags mask
|
||||
FlagsCmp uint8 // TCP flags compare
|
||||
InvFlags TcpInvFlagset // Inverse flags
|
||||
}
|
||||
|
||||
type TcpInvFlagset uint8
|
||||
|
||||
const (
|
||||
TcpInvSrcPorts TcpInvFlagset = 1 << iota
|
||||
TcpInvDestPorts
|
||||
TcpInvFlags
|
||||
TcpInvOption
|
||||
TcpInvMask TcpInvFlagset = (1 << iota) - 1
|
||||
)
|
||||
|
||||
func (x *Tcp) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.SrcPorts[0])
|
||||
ab.PutUint16(x.SrcPorts[1])
|
||||
ab.PutUint16(x.DstPorts[0])
|
||||
ab.PutUint16(x.DstPorts[1])
|
||||
ab.PutUint8(x.Option)
|
||||
ab.PutUint8(x.FlagsMask)
|
||||
ab.PutUint8(x.FlagsCmp)
|
||||
ab.PutUint8(byte(x.InvFlags))
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *Tcp) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.SrcPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.SrcPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.Option, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.FlagsMask, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.FlagsCmp, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
var invFlags uint8
|
||||
if invFlags, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
x.InvFlags = TcpInvFlagset(invFlags)
|
||||
return nil
|
||||
}
|
||||
57
vendor/github.com/google/nftables/xt/match_udp.go
generated
vendored
Normal file
57
vendor/github.com/google/nftables/xt/match_udp.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// Tcp is the Match.Info payload for the tcp xtables extension
|
||||
// (https://wiki.nftables.org/wiki-nftables/index.php/Supported_features_compared_to_xtables#tcp).
|
||||
//
|
||||
// See
|
||||
// https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/xt_tcpudp.h#L25
|
||||
type Udp struct {
|
||||
SrcPorts [2]uint16 // min, max source port range
|
||||
DstPorts [2]uint16 // min, max destination port range
|
||||
InvFlags UdpInvFlagset // Inverse flags
|
||||
}
|
||||
|
||||
type UdpInvFlagset uint8
|
||||
|
||||
const (
|
||||
UdpInvSrcPorts UdpInvFlagset = 1 << iota
|
||||
UdpInvDestPorts
|
||||
UdpInvMask UdpInvFlagset = (1 << iota) - 1
|
||||
)
|
||||
|
||||
func (x *Udp) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.SrcPorts[0])
|
||||
ab.PutUint16(x.SrcPorts[1])
|
||||
ab.PutUint16(x.DstPorts[0])
|
||||
ab.PutUint16(x.DstPorts[1])
|
||||
ab.PutUint8(byte(x.InvFlags))
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *Udp) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.SrcPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.SrcPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
var invFlags uint8
|
||||
if invFlags, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
x.InvFlags = UdpInvFlagset(invFlags)
|
||||
return nil
|
||||
}
|
||||
106
vendor/github.com/google/nftables/xt/target_dnat.go
generated
vendored
Normal file
106
vendor/github.com/google/nftables/xt/target_dnat.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
type NatRangeFlags uint
|
||||
|
||||
// See: https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L8
|
||||
const (
|
||||
NatRangeMapIPs NatRangeFlags = (1 << iota)
|
||||
NatRangeProtoSpecified
|
||||
NatRangeProtoRandom
|
||||
NatRangePersistent
|
||||
NatRangeProtoRandomFully
|
||||
NatRangeProtoOffset
|
||||
NatRangeNetmap
|
||||
|
||||
NatRangeMask NatRangeFlags = (1 << iota) - 1
|
||||
|
||||
NatRangeProtoRandomAll = NatRangeProtoRandom | NatRangeProtoRandomFully
|
||||
)
|
||||
|
||||
// see: https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L38
|
||||
type NatRange struct {
|
||||
Flags uint // sic! platform/arch/compiler-dependent uint size
|
||||
MinIP net.IP // always taking up space for an IPv6 address
|
||||
MaxIP net.IP // dito
|
||||
MinPort uint16
|
||||
MaxPort uint16
|
||||
}
|
||||
|
||||
// see: https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L46
|
||||
type NatRange2 struct {
|
||||
NatRange
|
||||
BasePort uint16
|
||||
}
|
||||
|
||||
func (x *NatRange) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *NatRange) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
ab.PutUint(x.Flags)
|
||||
if err := putIPv46(ab, fam, x.MinIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.MaxIP); err != nil {
|
||||
return err
|
||||
}
|
||||
ab.PutUint16BE(x.MinPort)
|
||||
ab.PutUint16BE(x.MaxPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatRange) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
return x.unmarshalAB(fam, rev, &ab)
|
||||
}
|
||||
|
||||
func (x *NatRange) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if x.Flags, err = ab.Uint(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MinIP, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MaxIP, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MinPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MaxPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatRange2) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.NatRange.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ab.PutUint16BE(x.BasePort)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *NatRange2) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.NatRange.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.BasePort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
86
vendor/github.com/google/nftables/xt/target_masquerade_ip.go
generated
vendored
Normal file
86
vendor/github.com/google/nftables/xt/target_masquerade_ip.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L25
|
||||
type NatIPv4Range struct {
|
||||
Flags uint // sic!
|
||||
MinIP net.IP
|
||||
MaxIP net.IP
|
||||
MinPort uint16
|
||||
MaxPort uint16
|
||||
}
|
||||
|
||||
// NatIPv4MultiRangeCompat despite being a slice of NAT IPv4 ranges is currently allowed to
|
||||
// only hold exactly one element.
|
||||
//
|
||||
// See https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/nf_nat.h#L33
|
||||
type NatIPv4MultiRangeCompat []NatIPv4Range
|
||||
|
||||
func (x *NatIPv4MultiRangeCompat) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if len(*x) != 1 {
|
||||
return nil, errors.New("MasqueradeIp must contain exactly one NatIPv4Range")
|
||||
}
|
||||
ab.PutUint(uint(len(*x)))
|
||||
for _, nat := range *x {
|
||||
if err := nat.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *NatIPv4MultiRangeCompat) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
l, err := ab.Uint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nats := make(NatIPv4MultiRangeCompat, l)
|
||||
for l > 0 {
|
||||
l--
|
||||
if err := nats[l].unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*x = nats
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatIPv4Range) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
ab.PutUint(x.Flags)
|
||||
ab.PutBytesAligned32(x.MinIP.To4(), 4)
|
||||
ab.PutBytesAligned32(x.MaxIP.To4(), 4)
|
||||
ab.PutUint16BE(x.MinPort)
|
||||
ab.PutUint16BE(x.MaxPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatIPv4Range) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if x.Flags, err = ab.Uint(); err != nil {
|
||||
return err
|
||||
}
|
||||
var ip []byte
|
||||
if ip, err = ab.BytesAligned32(4); err != nil {
|
||||
return err
|
||||
}
|
||||
x.MinIP = net.IP(ip)
|
||||
if ip, err = ab.BytesAligned32(4); err != nil {
|
||||
return err
|
||||
}
|
||||
x.MaxIP = net.IP(ip)
|
||||
if x.MinPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MaxPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
17
vendor/github.com/google/nftables/xt/unknown.go
generated
vendored
Normal file
17
vendor/github.com/google/nftables/xt/unknown.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package xt
|
||||
|
||||
// Unknown represents the bytes Info payload for unknown Info types where no
|
||||
// dedicated match/target info type has (yet) been defined.
|
||||
type Unknown []byte
|
||||
|
||||
func (x *Unknown) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
// In case of unknown payload we assume its creator knows what she/he does
|
||||
// and thus we don't do any alignment padding. Just take the payload "as
|
||||
// is".
|
||||
return *x, nil
|
||||
}
|
||||
|
||||
func (x *Unknown) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
*x = data
|
||||
return nil
|
||||
}
|
||||
64
vendor/github.com/google/nftables/xt/util.go
generated
vendored
Normal file
64
vendor/github.com/google/nftables/xt/util.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package xt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func bool32(ab *alignedbuff.AlignedBuff) (bool, error) {
|
||||
v, err := ab.Uint32()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if v != 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func putBool32(ab *alignedbuff.AlignedBuff, b bool) {
|
||||
if b {
|
||||
ab.PutUint32(1)
|
||||
return
|
||||
}
|
||||
ab.PutUint32(0)
|
||||
}
|
||||
|
||||
func iPv46(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IP, error) {
|
||||
ip, err := ab.BytesAligned32(16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
return net.IP(ip[:4]), nil
|
||||
case unix.NFPROTO_IPV6:
|
||||
return net.IP(ip), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unmarshal IP: unsupported table family %d", fam)
|
||||
}
|
||||
}
|
||||
|
||||
func iPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IPMask, error) {
|
||||
v, err := iPv46(ab, fam)
|
||||
return net.IPMask(v), err
|
||||
}
|
||||
|
||||
func putIPv46(ab *alignedbuff.AlignedBuff, fam TableFamily, ip net.IP) error {
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
ab.PutBytesAligned32(ip.To4(), 16)
|
||||
case unix.NFPROTO_IPV6:
|
||||
ab.PutBytesAligned32(ip.To16(), 16)
|
||||
default:
|
||||
return fmt.Errorf("marshal IP: unsupported table family %d", fam)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func putIPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily, mask net.IPMask) error {
|
||||
return putIPv46(ab, fam, net.IP(mask))
|
||||
}
|
||||
48
vendor/github.com/google/nftables/xt/xt.go
generated
vendored
Normal file
48
vendor/github.com/google/nftables/xt/xt.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Package xt implements dedicated types for (some) of the "Info" payload in Match
|
||||
and Target expressions that bridge between the nftables and xtables worlds.
|
||||
|
||||
Bridging between the more unified world of nftables and the slightly
|
||||
heterogenous world of xtables comes with some caveats. Unmarshalling the
|
||||
extension/translation information in Match and Target expressions requires
|
||||
information about the table family the information belongs to, as well as type
|
||||
and type revision information. In consequence, unmarshalling the Match and
|
||||
Target Info field payloads often (but not necessarily always) require the table
|
||||
family and revision information, so it gets passed to the type-specific
|
||||
unmarshallers.
|
||||
|
||||
To complicate things more, even marshalling requires knowledge about the
|
||||
enclosing table family. The NatRange/NatRange2 types are an example, where it is
|
||||
necessary to differentiate between IPv4 and IPv6 address marshalling. Due to
|
||||
Go's net.IP habit to normally store IPv4 addresses as IPv4-compatible IPv6
|
||||
addresses (see also RFC 4291, section 2.5.5.1) marshalling must be handled
|
||||
differently in the context of an IPv6 table compared to an IPv4 table. In an
|
||||
IPv4 table, an IPv4-compatible IPv6 address must be marshalled as a 32bit
|
||||
address, whereas in an IPv6 table the IPv4 address must be marshalled as an
|
||||
128bit IPv4-compatible IPv6 address. Not relying on heuristics here we avoid
|
||||
behavior unexpected and most probably unknown to our API users. The net.IP habit
|
||||
of storing IPv4 addresses in two different storage formats is already a source
|
||||
for trouble, especially when comparing net.IPs from different Go module sources.
|
||||
We won't add to this confusion. (...or maybe we can, because of it?)
|
||||
|
||||
An important property of all types of Info extension/translation payloads is
|
||||
that their marshalling and unmarshalling doesn't follow netlink's TLV
|
||||
(tag-length-value) architecture. Instead, Info payloads a basically plain binary
|
||||
blobs of their respective type-specific data structures, so host
|
||||
platform/architecture alignment and data type sizes apply. The alignedbuff
|
||||
package implements the different required data types alignments.
|
||||
|
||||
Please note that Info payloads are always padded at their end to the next uint64
|
||||
alignment. Kernel code is checking for the padded payload size and will reject
|
||||
payloads not correctly padded at their ends.
|
||||
|
||||
Most of the time, we find explifcitly sized (unsigned integer) data types.
|
||||
However, there are notable exceptions where "unsigned int" is used: on 64bit
|
||||
platforms this mostly translates into 32bit(!). This differs from Go mapping
|
||||
uint to uint64 instead. This package currently clamps its mapping of C's
|
||||
"unsigned int" to Go's uint32 for marshalling and unmarshalling. If in the
|
||||
future 128bit platforms with a differently sized C unsigned int should come into
|
||||
production, then the alignedbuff package will need to be adapted accordingly, as
|
||||
it abstracts away this data type handling.
|
||||
*/
|
||||
package xt
|
||||
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
|
||||
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
|
||||
|
||||
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
|
||||
|
||||
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
|
||||
|
||||
### Fixes
|
||||
|
||||
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
|
||||
|
||||
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
|
||||
|
||||
## Changelog
|
||||
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Tips
|
||||
|
||||
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
|
||||
|
||||
Always try to include a test case! If it is not possible or not necessary,
|
||||
please explain why in the pull request description.
|
||||
|
||||
### Releasing
|
||||
|
||||
Commits that would precipitate a SemVer change, as described in the Conventional
|
||||
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
|
||||
to create a release candidate pull request. Once submitted, `release-please`
|
||||
will create a release.
|
||||
|
||||
For tips on how to work with `release-please`, see its documentation.
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
||||
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
||||
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009,2014 Google Inc. 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.
|
||||
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# uuid
|
||||
The uuid package generates and inspects UUIDs based on
|
||||
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
|
||||
and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
This package is based on the github.com/pborman/uuid package (previously named
|
||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||
|
||||
###### Install
|
||||
```sh
|
||||
go get github.com/google/uuid
|
||||
```
|
||||
|
||||
###### Documentation
|
||||
[](https://pkg.go.dev/github.com/google/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://pkg.go.dev/github.com/google/uuid
|
||||
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err == nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() (UUID, error) {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() (UUID, error) {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||
// for Version 2 UUIDs.
|
||||
func (uuid UUID) Domain() Domain {
|
||||
return Domain(uuid[9])
|
||||
}
|
||||
|
||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||
// UUIDs.
|
||||
func (uuid UUID) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(uuid[0:4])
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
||||
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package uuid generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||
// Services.
|
||||
//
|
||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||
// maps or compared directly.
|
||||
package uuid
|
||||
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known namespace IDs and UUIDs
|
||||
var (
|
||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
Nil UUID // empty UUID, all zeros
|
||||
|
||||
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
|
||||
Max = UUID{
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
}
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:]) //nolint:errcheck
|
||||
h.Write(data) //nolint:errcheck
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
||||
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||
var js [36]byte
|
||||
encodeHex(js[:], uuid)
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*uuid = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||
return uuid[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(uuid[:], data)
|
||||
return nil
|
||||
}
|
||||
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
ifname string // name of interface being used
|
||||
nodeID [6]byte // hardware for version 1 UUIDs
|
||||
zeroID [6]byte // nodeID with only 0's
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||
if iname != "" && addr != nil {
|
||||
ifname = iname
|
||||
copy(nodeID[:], addr)
|
||||
return true
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
ifname = "random"
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := nodeID
|
||||
return nid[:]
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
copy(nodeID[:], id)
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
var node [6]byte
|
||||
copy(node[:], uuid[10:])
|
||||
return node[:]
|
||||
}
|
||||
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build js
|
||||
|
||||
package uuid
|
||||
|
||||
// getHardwareInterface returns nil values for the JS version of the code.
|
||||
// This removes the "net" dependency, because it is not used in the browser.
|
||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
||||
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !js
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var interfaces []net.Interface // cached list of interfaces
|
||||
|
||||
// getHardwareInterface returns the name and hardware address of interface name.
|
||||
// If name is "" then the name and hardware address of one of the system's
|
||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||
// there are no interfaces) then "", nil is returned.
|
||||
//
|
||||
// Only addresses of at least 6 bytes are returned.
|
||||
func getHardwareInterface(name string) (string, []byte) {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
return ifs.Name, ifs.HardwareAddr
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
// NullUUID represents a UUID that may be null.
|
||||
// NullUUID implements the SQL driver.Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var u uuid.NullUUID
|
||||
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
||||
// ...
|
||||
// if u.Valid {
|
||||
// // use u.UUID
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool // Valid is true if UUID is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the SQL driver.Scanner interface.
|
||||
func (nu *NullUUID) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
nu.UUID, nu.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
err := nu.UUID.Scan(value)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nu NullUUID) Value() (driver.Value, error) {
|
||||
if !nu.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return nu.UUID.Value()
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID[:], nil
|
||||
}
|
||||
|
||||
return []byte(nil), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(nu.UUID[:], data)
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (nu NullUUID) MarshalText() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID.MarshalText()
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
nu.UUID = id
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return json.Marshal(nu.UUID)
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, jsonNull) {
|
||||
*nu = NullUUID{}
|
||||
return nil // valid null UUID
|
||||
}
|
||||
err := json.Unmarshal(data, &nu.UUID)
|
||||
nu.Valid = err == nil
|
||||
return err
|
||||
}
|
||||
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see Parse for required string format
|
||||
u, err := Parse(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Scan: %v", err)
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
|
||||
case []byte:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(src) != 16 {
|
||||
return uuid.Scan(string(src))
|
||||
}
|
||||
copy((*uuid)[:], src)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
||||
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clockSeq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clockSeq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||
// random clock sequence is generated the first time a clock sequence is
|
||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clockSeq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
oldSeq := clockSeq
|
||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if oldSeq != clockSeq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
|
||||
func (uuid UUID) Time() Time {
|
||||
var t Time
|
||||
switch uuid.Version() {
|
||||
case 6:
|
||||
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
|
||||
t = Time(time)
|
||||
case 7:
|
||||
time := binary.BigEndian.Uint64(uuid[:8])
|
||||
t = Time((time>>16)*10000 + g1582ns100)
|
||||
default: // forward compatible
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
t = Time(time)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid.
|
||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() int {
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user