Compare commits
71 Commits
v0.6.0
...
v0.7.0-rc2
Author | SHA1 | Date | |
---|---|---|---|
136399f078 | |||
596480eadc | |||
61c3c42107 | |||
b522ed6aae | |||
26ef6e312d | |||
461d433911 | |||
84a01001be | |||
e2f063b534 | |||
9e5836047c | |||
808d4e20ae | |||
3468364f7e | |||
412b6d3128 | |||
9604565b22 | |||
2a0736c748 | |||
2eba56ad52 | |||
d228f980e1 | |||
6aa21c431e | |||
c42470bc79 | |||
ffc591e242 | |||
59f9976017 | |||
c26961a990 | |||
8ebea58550 | |||
97664d8a6a | |||
5c7e7c0913 | |||
b03d23a4fa | |||
03e316b07b | |||
ecdd827d3a | |||
1f02326d56 | |||
92c634042c | |||
2c05055101 | |||
5e830efb20 | |||
73fdc87395 | |||
d07d2aaf71 | |||
6c2ef734c2 | |||
47668f6d64 | |||
fbced0cccb | |||
99f6be0319 | |||
b09e0d28a7 | |||
5576f3120e | |||
449700f7ea | |||
4779f1d2bf | |||
7f98c94613 | |||
596b44301b | |||
0063a1b9d0 | |||
cc71426592 | |||
e256564546 | |||
c238c93b5e | |||
25ca6ccb52 | |||
5e46a66c89 | |||
6be2e8a0e2 | |||
b24225fc17 | |||
d8f2fd7a3c | |||
1396ab0bab | |||
92babd4a3d | |||
7a62515407 | |||
008024125a | |||
556e509097 | |||
dda9c2b1b0 | |||
0e3df2961c | |||
e1ea7f5ecb | |||
8fe8460c72 | |||
92e62b9f4d | |||
2f957864ea | |||
b49379d284 | |||
9769434a13 | |||
a124fb36e6 | |||
9fb22524a1 | |||
2d7d680874 | |||
ca3f28fa9e | |||
f2faf549b4 | |||
6099d8c84c |
28
.appveyor.yml
Normal file
28
.appveyor.yml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
clone_folder: c:\gopath\src\github.com\containernetworking\plugins
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
|
||||||
|
install:
|
||||||
|
- echo %PATH%
|
||||||
|
- echo %GOPATH%
|
||||||
|
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||||
|
- go version
|
||||||
|
- go env
|
||||||
|
|
||||||
|
build: off
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- ps: |
|
||||||
|
go list ./... | Select-String -Pattern (Get-Content "./plugins/linux_only.txt") -NotMatch > "to_test.txt"
|
||||||
|
echo "Will test:"
|
||||||
|
Get-Content "to_test.txt"
|
||||||
|
foreach ($pkg in Get-Content "to_test.txt") {
|
||||||
|
if ($pkg) {
|
||||||
|
echo $pkg
|
||||||
|
go test -v $pkg
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "test failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,12 @@ sudo: required
|
|||||||
dist: trusty
|
dist: trusty
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.7.x
|
|
||||||
- 1.8.x
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- PATH=$GOROOT/bin:$PATH
|
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
|
||||||
matrix:
|
matrix:
|
||||||
- TARGET=amd64
|
- TARGET=amd64
|
||||||
- TARGET=arm
|
- TARGET=arm
|
||||||
@ -19,6 +19,9 @@ env:
|
|||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get github.com/onsi/ginkgo/ginkgo
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [ "${TARGET}" == "amd64" ]; then
|
if [ "${TARGET}" == "amd64" ]; then
|
||||||
|
20
Godeps/Godeps.json
generated
20
Godeps/Godeps.json
generated
@ -6,6 +6,10 @@
|
|||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/alexflint/go-filemutex",
|
||||||
|
"Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/libcni",
|
"ImportPath": "github.com/containernetworking/cni/libcni",
|
||||||
"Comment": "v0.6.0-rc1",
|
"Comment": "v0.6.0-rc1",
|
||||||
@ -59,6 +63,10 @@
|
|||||||
"ImportPath": "github.com/d2g/dhcp4client",
|
"ImportPath": "github.com/d2g/dhcp4client",
|
||||||
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
|
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/d2g/dhcp4server",
|
||||||
|
"Rev": "1b74244053681c90de5cf1af3d6b5c93b74e3abb"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/j-keck/arping",
|
"ImportPath": "github.com/j-keck/arping",
|
||||||
"Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
|
"Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
|
||||||
@ -230,6 +238,18 @@
|
|||||||
"ImportPath": "github.com/vishvananda/netns",
|
"ImportPath": "github.com/vishvananda/netns",
|
||||||
"Rev": "54f0e4339ce73702a0607f49922aaa1e749b418d"
|
"Rev": "54f0e4339ce73702a0607f49922aaa1e749b418d"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/bpf",
|
||||||
|
"Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/internal/iana",
|
||||||
|
"Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/ipv4",
|
||||||
|
"Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/sys/unix",
|
"ImportPath": "golang.org/x/sys/unix",
|
||||||
"Rev": "076b546753157f758b316e59bcb51e6807c04057"
|
"Rev": "076b546753157f758b316e59bcb51e6807c04057"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
[](https://travis-ci.org/containernetworking/plugins)
|
[](https://travis-ci.org/containernetworking/plugins)
|
||||||
|
[](https://ci.appveyor.com/project/cni-bot/plugins/branch/master)
|
||||||
|
|
||||||
# plugins
|
# plugins
|
||||||
Some CNI network plugins, maintained by the containernetworking team. For more information, see the individual READMEs.
|
Some CNI network plugins, maintained by the containernetworking team. For more information, see the individual READMEs.
|
||||||
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@ -12,7 +12,7 @@ Vagrant.configure(2) do |config|
|
|||||||
apt-get update -y || (sleep 40 && apt-get update -y)
|
apt-get update -y || (sleep 40 && apt-get update -y)
|
||||||
apt-get install -y git
|
apt-get install -y git
|
||||||
|
|
||||||
wget -qO- https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz | tar -C /usr/local -xz
|
wget -qO- https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||||
|
|
||||||
echo 'export GOPATH=/go' >> /root/.bashrc
|
echo 'export GOPATH=/go' >> /root/.bashrc
|
||||||
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
|
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package ip
|
package ip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
@ -51,5 +52,10 @@ func EnableForward(ips []*current.IPConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func echo1(f string) error {
|
func echo1(f string) error {
|
||||||
|
if content, err := ioutil.ReadFile(f); err == nil {
|
||||||
|
if bytes.Equal(bytes.TrimSpace(content), []byte("1")) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return ioutil.WriteFile(f, []byte("1"), 0644)
|
return ioutil.WriteFile(f, []byte("1"), 0644)
|
||||||
}
|
}
|
31
pkg/ip/ipforward_linux_test.go
Normal file
31
pkg/ip/ipforward_linux_test.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package ip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("IpforwardLinux", func() {
|
||||||
|
It("echo1 must not write the file if content is 1", func() {
|
||||||
|
file, err := ioutil.TempFile(os.TempDir(), "containernetworking")
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
err = echo1(file.Name())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
statBefore, err := file.Stat()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// take a duration here, otherwise next file modification operation time
|
||||||
|
// will be same as previous one.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
err = echo1(file.Name())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
statAfter, err := file.Stat()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(statBefore.ModTime()).To(Equal(statAfter.ModTime()))
|
||||||
|
})
|
||||||
|
})
|
@ -75,7 +75,16 @@ func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
|||||||
|
|
||||||
// TeardownIPMasq undoes the effects of SetupIPMasq
|
// TeardownIPMasq undoes the effects of SetupIPMasq
|
||||||
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||||
ipt, err := iptables.New()
|
isV6 := ipn.IP.To4() == nil
|
||||||
|
|
||||||
|
var ipt *iptables.IPTables
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if isV6 {
|
||||||
|
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
} else {
|
||||||
|
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||||
}
|
}
|
@ -158,6 +158,9 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
|
|||||||
func DelLinkByName(ifName string) error {
|
func DelLinkByName(ifName string) error {
|
||||||
iface, err := netlink.LinkByName(ifName)
|
iface, err := netlink.LinkByName(ifName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err.Error() == "Link not found" {
|
||||||
|
return ErrLinkNotFound
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,9 +171,8 @@ func DelLinkByName(ifName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelLinkByNameAddr remove an interface returns its IP address
|
// DelLinkByNameAddr remove an interface and returns its addresses
|
||||||
// of the specified family
|
func DelLinkByNameAddr(ifName string) ([]*net.IPNet, error) {
|
||||||
func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
|
|
||||||
iface, err := netlink.LinkByName(ifName)
|
iface, err := netlink.LinkByName(ifName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != nil && err.Error() == "Link not found" {
|
if err != nil && err.Error() == "Link not found" {
|
||||||
@ -179,8 +181,8 @@ func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
|
|||||||
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs, err := netlink.AddrList(iface, family)
|
addrs, err := netlink.AddrList(iface, netlink.FAMILY_ALL)
|
||||||
if err != nil || len(addrs) == 0 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get IP addresses for %q: %v", ifName, err)
|
return nil, fmt.Errorf("failed to get IP addresses for %q: %v", ifName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +190,14 @@ func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
|
|||||||
return nil, fmt.Errorf("failed to delete %q: %v", ifName, err)
|
return nil, fmt.Errorf("failed to delete %q: %v", ifName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return addrs[0].IPNet, nil
|
out := []*net.IPNet{}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if addr.IP.IsGlobalUnicast() {
|
||||||
|
out = append(out, addr.IPNet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetHWAddrByIP(ifName string, ip4 net.IP, ip6 net.IP) error {
|
func SetHWAddrByIP(ifName string, ip4 net.IP, ip6 net.IP) error {
|
@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"github.com/vishvananda/netlink/nl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHwAddr(linkname string) string {
|
func getHwAddr(linkname string) string {
|
||||||
@ -133,7 +132,7 @@ var _ = Describe("Link", func() {
|
|||||||
defer GinkgoRecover()
|
defer GinkgoRecover()
|
||||||
|
|
||||||
// This string should match the expected error codes in the cmdDel functions of some of the plugins
|
// This string should match the expected error codes in the cmdDel functions of some of the plugins
|
||||||
_, err := ip.DelLinkByNameAddr("THIS_DONT_EXIST", netlink.FAMILY_V4)
|
_, err := ip.DelLinkByNameAddr("THIS_DONT_EXIST")
|
||||||
Expect(err).To(Equal(ip.ErrLinkNotFound))
|
Expect(err).To(Equal(ip.ErrLinkNotFound))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -220,16 +219,14 @@ var _ = Describe("Link", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
It("DelLinkByNameAddr must throw an error for configured interfaces", func() {
|
It("DelLinkByNameAddr should return no IPs when no IPs are configured", func() {
|
||||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||||
defer GinkgoRecover()
|
defer GinkgoRecover()
|
||||||
|
|
||||||
// this will delete the host endpoint too
|
// this will delete the host endpoint too
|
||||||
addr, err := ip.DelLinkByNameAddr(containerVethName, nl.FAMILY_V4)
|
addr, err := ip.DelLinkByNameAddr(containerVethName)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(addr).To(HaveLen(0))
|
||||||
var ipNetNil *net.IPNet
|
|
||||||
Expect(addr).To(Equal(ipNetNil))
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -39,3 +39,9 @@ func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
|||||||
Gw: gw,
|
Gw: gw,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddDefaultRoute sets the default route on the given gateway.
|
||||||
|
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
||||||
|
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
|
||||||
|
return AddRoute(defNet, gw, dev)
|
||||||
|
}
|
||||||
|
@ -15,16 +15,8 @@
|
|||||||
package ipam
|
package ipam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/invoke"
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
"github.com/containernetworking/cni/pkg/types/current"
|
|
||||||
"github.com/containernetworking/plugins/pkg/ip"
|
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
|
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
|
||||||
@ -34,66 +26,3 @@ func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
|
|||||||
func ExecDel(plugin string, netconf []byte) error {
|
func ExecDel(plugin string, netconf []byte) error {
|
||||||
return invoke.DelegateDel(plugin, netconf)
|
return invoke.DelegateDel(plugin, netconf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureIface takes the result of IPAM plugin and
|
|
||||||
// applies to the ifName interface
|
|
||||||
func ConfigureIface(ifName string, res *current.Result) error {
|
|
||||||
if len(res.Interfaces) == 0 {
|
|
||||||
return fmt.Errorf("no interfaces to configure")
|
|
||||||
}
|
|
||||||
|
|
||||||
link, err := netlink.LinkByName(ifName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.LinkSetUp(link); err != nil {
|
|
||||||
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var v4gw, v6gw net.IP
|
|
||||||
for _, ipc := range res.IPs {
|
|
||||||
if ipc.Interface == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
intIdx := *ipc.Interface
|
|
||||||
if intIdx < 0 || intIdx >= len(res.Interfaces) || res.Interfaces[intIdx].Name != ifName {
|
|
||||||
// IP address is for a different interface
|
|
||||||
return fmt.Errorf("failed to add IP addr %v to %q: invalid interface index", ipc, ifName)
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}
|
|
||||||
if err = netlink.AddrAdd(link, addr); err != nil {
|
|
||||||
return fmt.Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gwIsV4 := ipc.Gateway.To4() != nil
|
|
||||||
if gwIsV4 && v4gw == nil {
|
|
||||||
v4gw = ipc.Gateway
|
|
||||||
} else if !gwIsV4 && v6gw == nil {
|
|
||||||
v6gw = ipc.Gateway
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ip.SettleAddresses(ifName, 10)
|
|
||||||
|
|
||||||
for _, r := range res.Routes {
|
|
||||||
routeIsV4 := r.Dst.IP.To4() != nil
|
|
||||||
gw := r.GW
|
|
||||||
if gw == nil {
|
|
||||||
if routeIsV4 && v4gw != nil {
|
|
||||||
gw = v4gw
|
|
||||||
} else if !routeIsV4 && v6gw != nil {
|
|
||||||
gw = v6gw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
|
|
||||||
// we skip over duplicate routes as we assume the first one wins
|
|
||||||
if !os.IsExist(err) {
|
|
||||||
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
91
pkg/ipam/ipam_linux.go
Normal file
91
pkg/ipam/ipam_linux.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2015 CNI authors
|
||||||
|
//
|
||||||
|
// 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 ipam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/plugins/pkg/ip"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigureIface takes the result of IPAM plugin and
|
||||||
|
// applies to the ifName interface
|
||||||
|
func ConfigureIface(ifName string, res *current.Result) error {
|
||||||
|
if len(res.Interfaces) == 0 {
|
||||||
|
return fmt.Errorf("no interfaces to configure")
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(link); err != nil {
|
||||||
|
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v4gw, v6gw net.IP
|
||||||
|
for _, ipc := range res.IPs {
|
||||||
|
if ipc.Interface == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
intIdx := *ipc.Interface
|
||||||
|
if intIdx < 0 || intIdx >= len(res.Interfaces) || res.Interfaces[intIdx].Name != ifName {
|
||||||
|
// IP address is for a different interface
|
||||||
|
return fmt.Errorf("failed to add IP addr %v to %q: invalid interface index", ipc, ifName)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}
|
||||||
|
if err = netlink.AddrAdd(link, addr); err != nil {
|
||||||
|
return fmt.Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gwIsV4 := ipc.Gateway.To4() != nil
|
||||||
|
if gwIsV4 && v4gw == nil {
|
||||||
|
v4gw = ipc.Gateway
|
||||||
|
} else if !gwIsV4 && v6gw == nil {
|
||||||
|
v6gw = ipc.Gateway
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v6gw != nil {
|
||||||
|
ip.SettleAddresses(ifName, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range res.Routes {
|
||||||
|
routeIsV4 := r.Dst.IP.To4() != nil
|
||||||
|
gw := r.GW
|
||||||
|
if gw == nil {
|
||||||
|
if routeIsV4 && v4gw != nil {
|
||||||
|
gw = v4gw
|
||||||
|
} else if !routeIsV4 && v6gw != nil {
|
||||||
|
gw = v6gw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
|
||||||
|
// we skip over duplicate routes as we assume the first one wins
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -39,7 +39,7 @@ func ipNetEqual(a, b *net.IPNet) bool {
|
|||||||
return a.IP.Equal(b.IP)
|
return a.IP.Equal(b.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Describe("IPAM Operations", func() {
|
var _ = Describe("ConfigureIface", func() {
|
||||||
var originalNS ns.NetNS
|
var originalNS ns.NetNS
|
||||||
var ipv4, ipv6, routev4, routev6 *net.IPNet
|
var ipv4, ipv6, routev4, routev6 *net.IPNet
|
||||||
var ipgw4, ipgw6, routegwv4, routegwv6 net.IP
|
var ipgw4, ipgw6, routegwv4, routegwv6 net.IP
|
178
pkg/ns/ns.go
178
pkg/ns/ns.go
@ -1,178 +0,0 @@
|
|||||||
// Copyright 2015 CNI authors
|
|
||||||
//
|
|
||||||
// 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 ns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NetNS interface {
|
|
||||||
// Executes the passed closure in this object's network namespace,
|
|
||||||
// attempting to restore the original namespace before returning.
|
|
||||||
// However, since each OS thread can have a different network namespace,
|
|
||||||
// and Go's thread scheduling is highly variable, callers cannot
|
|
||||||
// guarantee any specific namespace is set unless operations that
|
|
||||||
// require that namespace are wrapped with Do(). Also, no code called
|
|
||||||
// from Do() should call runtime.UnlockOSThread(), or the risk
|
|
||||||
// of executing code in an incorrect namespace will be greater. See
|
|
||||||
// https://github.com/golang/go/wiki/LockOSThread for further details.
|
|
||||||
Do(toRun func(NetNS) error) error
|
|
||||||
|
|
||||||
// Sets the current network namespace to this object's network namespace.
|
|
||||||
// Note that since Go's thread scheduling is highly variable, callers
|
|
||||||
// cannot guarantee the requested namespace will be the current namespace
|
|
||||||
// after this function is called; to ensure this wrap operations that
|
|
||||||
// require the namespace with Do() instead.
|
|
||||||
Set() error
|
|
||||||
|
|
||||||
// Returns the filesystem path representing this object's network namespace
|
|
||||||
Path() string
|
|
||||||
|
|
||||||
// Returns a file descriptor representing this object's network namespace
|
|
||||||
Fd() uintptr
|
|
||||||
|
|
||||||
// Cleans up this instance of the network namespace; if this instance
|
|
||||||
// is the last user the namespace will be destroyed
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type netNS struct {
|
|
||||||
file *os.File
|
|
||||||
mounted bool
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// netNS implements the NetNS interface
|
|
||||||
var _ NetNS = &netNS{}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
|
|
||||||
NSFS_MAGIC = 0x6e736673
|
|
||||||
PROCFS_MAGIC = 0x9fa0
|
|
||||||
)
|
|
||||||
|
|
||||||
type NSPathNotExistErr struct{ msg string }
|
|
||||||
|
|
||||||
func (e NSPathNotExistErr) Error() string { return e.msg }
|
|
||||||
|
|
||||||
type NSPathNotNSErr struct{ msg string }
|
|
||||||
|
|
||||||
func (e NSPathNotNSErr) Error() string { return e.msg }
|
|
||||||
|
|
||||||
func IsNSorErr(nspath string) error {
|
|
||||||
stat := syscall.Statfs_t{}
|
|
||||||
if err := syscall.Statfs(nspath, &stat); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch stat.Type {
|
|
||||||
case PROCFS_MAGIC, NSFS_MAGIC:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an object representing the namespace referred to by @path
|
|
||||||
func GetNS(nspath string) (NetNS, error) {
|
|
||||||
err := IsNSorErr(nspath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := os.Open(nspath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &netNS{file: fd}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *netNS) Path() string {
|
|
||||||
return ns.file.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *netNS) Fd() uintptr {
|
|
||||||
return ns.file.Fd()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *netNS) errorIfClosed() error {
|
|
||||||
if ns.closed {
|
|
||||||
return fmt.Errorf("%q has already been closed", ns.file.Name())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *netNS) Do(toRun func(NetNS) error) error {
|
|
||||||
if err := ns.errorIfClosed(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
containedCall := func(hostNS NetNS) error {
|
|
||||||
threadNS, err := GetCurrentNS()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open current netns: %v", err)
|
|
||||||
}
|
|
||||||
defer threadNS.Close()
|
|
||||||
|
|
||||||
// switch to target namespace
|
|
||||||
if err = ns.Set(); err != nil {
|
|
||||||
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
|
|
||||||
}
|
|
||||||
defer threadNS.Set() // switch back
|
|
||||||
|
|
||||||
return toRun(hostNS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// save a handle to current network namespace
|
|
||||||
hostNS, err := GetCurrentNS()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to open current namespace: %v", err)
|
|
||||||
}
|
|
||||||
defer hostNS.Close()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
var innerError error
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
runtime.LockOSThread()
|
|
||||||
innerError = containedCall(hostNS)
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return innerError
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNetNSPath executes the passed closure under the given network
|
|
||||||
// namespace, restoring the original namespace afterwards.
|
|
||||||
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
|
|
||||||
ns, err := GetNS(nspath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ns.Close()
|
|
||||||
return ns.Do(toRun)
|
|
||||||
}
|
|
@ -21,6 +21,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@ -147,3 +148,158 @@ func (ns *netNS) Set() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NetNS interface {
|
||||||
|
// Executes the passed closure in this object's network namespace,
|
||||||
|
// attempting to restore the original namespace before returning.
|
||||||
|
// However, since each OS thread can have a different network namespace,
|
||||||
|
// and Go's thread scheduling is highly variable, callers cannot
|
||||||
|
// guarantee any specific namespace is set unless operations that
|
||||||
|
// require that namespace are wrapped with Do(). Also, no code called
|
||||||
|
// from Do() should call runtime.UnlockOSThread(), or the risk
|
||||||
|
// of executing code in an incorrect namespace will be greater. See
|
||||||
|
// https://github.com/golang/go/wiki/LockOSThread for further details.
|
||||||
|
Do(toRun func(NetNS) error) error
|
||||||
|
|
||||||
|
// Sets the current network namespace to this object's network namespace.
|
||||||
|
// Note that since Go's thread scheduling is highly variable, callers
|
||||||
|
// cannot guarantee the requested namespace will be the current namespace
|
||||||
|
// after this function is called; to ensure this wrap operations that
|
||||||
|
// require the namespace with Do() instead.
|
||||||
|
Set() error
|
||||||
|
|
||||||
|
// Returns the filesystem path representing this object's network namespace
|
||||||
|
Path() string
|
||||||
|
|
||||||
|
// Returns a file descriptor representing this object's network namespace
|
||||||
|
Fd() uintptr
|
||||||
|
|
||||||
|
// Cleans up this instance of the network namespace; if this instance
|
||||||
|
// is the last user the namespace will be destroyed
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type netNS struct {
|
||||||
|
file *os.File
|
||||||
|
mounted bool
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// netNS implements the NetNS interface
|
||||||
|
var _ NetNS = &netNS{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
|
||||||
|
NSFS_MAGIC = 0x6e736673
|
||||||
|
PROCFS_MAGIC = 0x9fa0
|
||||||
|
)
|
||||||
|
|
||||||
|
type NSPathNotExistErr struct{ msg string }
|
||||||
|
|
||||||
|
func (e NSPathNotExistErr) Error() string { return e.msg }
|
||||||
|
|
||||||
|
type NSPathNotNSErr struct{ msg string }
|
||||||
|
|
||||||
|
func (e NSPathNotNSErr) Error() string { return e.msg }
|
||||||
|
|
||||||
|
func IsNSorErr(nspath string) error {
|
||||||
|
stat := syscall.Statfs_t{}
|
||||||
|
if err := syscall.Statfs(nspath, &stat); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch stat.Type {
|
||||||
|
case PROCFS_MAGIC, NSFS_MAGIC:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an object representing the namespace referred to by @path
|
||||||
|
func GetNS(nspath string) (NetNS, error) {
|
||||||
|
err := IsNSorErr(nspath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Open(nspath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &netNS{file: fd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *netNS) Path() string {
|
||||||
|
return ns.file.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *netNS) Fd() uintptr {
|
||||||
|
return ns.file.Fd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *netNS) errorIfClosed() error {
|
||||||
|
if ns.closed {
|
||||||
|
return fmt.Errorf("%q has already been closed", ns.file.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||||
|
if err := ns.errorIfClosed(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
containedCall := func(hostNS NetNS) error {
|
||||||
|
threadNS, err := GetCurrentNS()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open current netns: %v", err)
|
||||||
|
}
|
||||||
|
defer threadNS.Close()
|
||||||
|
|
||||||
|
// switch to target namespace
|
||||||
|
if err = ns.Set(); err != nil {
|
||||||
|
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
|
||||||
|
}
|
||||||
|
defer threadNS.Set() // switch back
|
||||||
|
|
||||||
|
return toRun(hostNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save a handle to current network namespace
|
||||||
|
hostNS, err := GetCurrentNS()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to open current namespace: %v", err)
|
||||||
|
}
|
||||||
|
defer hostNS.Close()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
var innerError error
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
runtime.LockOSThread()
|
||||||
|
innerError = containedCall(hostNS)
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return innerError
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNetNSPath executes the passed closure under the given network
|
||||||
|
// namespace, restoring the original namespace afterwards.
|
||||||
|
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
|
||||||
|
ns, err := GetNS(nspath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ns.Close()
|
||||||
|
return ns.Do(toRun)
|
||||||
|
}
|
||||||
|
74
pkg/testutils/echosvr/echosvr_test.go
Normal file
74
pkg/testutils/echosvr/echosvr_test.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/onsi/gomega/gexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var binaryPath string
|
||||||
|
|
||||||
|
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||||
|
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return []byte(binaryPath)
|
||||||
|
}, func(data []byte) {
|
||||||
|
binaryPath = string(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||||
|
gexec.CleanupBuildArtifacts()
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = Describe("Echosvr", func() {
|
||||||
|
var session *gexec.Session
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
cmd := exec.Command(binaryPath)
|
||||||
|
session, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
session.Kill().Wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("starts and doesn't terminate immediately", func() {
|
||||||
|
Consistently(session).ShouldNot(gexec.Exit())
|
||||||
|
})
|
||||||
|
|
||||||
|
tryConnect := func() (net.Conn, error) {
|
||||||
|
programOutput := session.Out.Contents()
|
||||||
|
addr := strings.TrimSpace(string(programOutput))
|
||||||
|
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
It("prints its listening address to stdout", func() {
|
||||||
|
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||||
|
conn, err := tryConnect()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
conn.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("will echo data back to us", func() {
|
||||||
|
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||||
|
conn, err := tryConnect()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
fmt.Fprintf(conn, "hello")
|
||||||
|
Expect(ioutil.ReadAll(conn)).To(Equal([]byte("hello")))
|
||||||
|
})
|
||||||
|
})
|
13
pkg/testutils/echosvr/init_test.go
Normal file
13
pkg/testutils/echosvr/init_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEchosvr(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Testutils Echosvr Suite")
|
||||||
|
}
|
38
pkg/testutils/echosvr/main.go
Normal file
38
pkg/testutils/echosvr/main.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Echosvr is a simple TCP echo server
|
||||||
|
//
|
||||||
|
// It prints its listen address on stdout
|
||||||
|
// 127.0.0.1:xxxxx
|
||||||
|
// A test should wait for this line, parse it
|
||||||
|
// and may then attempt to connect.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
listener, err := net.Listen("tcp", ":")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, port, err := net.SplitHostPort(listener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("127.0.0.1:%s\n", port)
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go handleConnection(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleConnection(conn net.Conn) {
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
nBytesRead, _ := conn.Read(buf)
|
||||||
|
conn.Write(buf[0:nBytesRead])
|
||||||
|
conn.Close()
|
||||||
|
}
|
@ -18,6 +18,7 @@ $ ./dhcp daemon
|
|||||||
|
|
||||||
If given `-pidfile <path>` arguments after 'daemon', the dhcp plugin will write
|
If given `-pidfile <path>` arguments after 'daemon', the dhcp plugin will write
|
||||||
its PID to the given file.
|
its PID to the given file.
|
||||||
|
If given `-hostprefix <prefix>` arguments after 'daemon', the dhcp plugin will use this prefix for netns as `<prefix>/<original netns>`. It could be used in case of running dhcp daemon as container.
|
||||||
|
|
||||||
Alternatively, you can use systemd socket activation protocol.
|
Alternatively, you can use systemd socket activation protocol.
|
||||||
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.
|
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.
|
||||||
|
@ -39,8 +39,9 @@ const resendCount = 3
|
|||||||
var errNoMoreTries = errors.New("no more tries")
|
var errNoMoreTries = errors.New("no more tries")
|
||||||
|
|
||||||
type DHCP struct {
|
type DHCP struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
leases map[string]*DHCPLease
|
leases map[string]*DHCPLease
|
||||||
|
hostNetnsPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDHCP() *DHCP {
|
func newDHCP() *DHCP {
|
||||||
@ -58,7 +59,8 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clientID := args.ContainerID + "/" + conf.Name
|
clientID := args.ContainerID + "/" + conf.Name
|
||||||
l, err := AcquireLease(clientID, args.Netns, args.IfName)
|
hostNetns := d.hostNetnsPrefix + args.Netns
|
||||||
|
l, err := AcquireLease(clientID, hostNetns, args.IfName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -91,10 +93,10 @@ func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error {
|
|||||||
|
|
||||||
if l := d.getLease(args.ContainerID, conf.Name); l != nil {
|
if l := d.getLease(args.ContainerID, conf.Name); l != nil {
|
||||||
l.Stop()
|
l.Stop()
|
||||||
return nil
|
d.clearLease(args.ContainerID, conf.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("lease not found: %v/%v", args.ContainerID, conf.Name)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DHCP) getLease(contID, netName string) *DHCPLease {
|
func (d *DHCP) getLease(contID, netName string) *DHCPLease {
|
||||||
@ -117,6 +119,14 @@ func (d *DHCP) setLease(contID, netName string, l *DHCPLease) {
|
|||||||
d.leases[contID+netName] = l
|
d.leases[contID+netName] = l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DHCP) clearLease(contID, netName string) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
// TODO(eyakubovich): hash it to avoid collisions
|
||||||
|
delete(d.leases, contID+netName)
|
||||||
|
}
|
||||||
|
|
||||||
func getListener() (net.Listener, error) {
|
func getListener() (net.Listener, error) {
|
||||||
l, err := activation.Listeners(true)
|
l, err := activation.Listeners(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -141,7 +151,7 @@ func getListener() (net.Listener, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDaemon(pidfilePath string) error {
|
func runDaemon(pidfilePath string, hostPrefix string) error {
|
||||||
// since other goroutines (on separate threads) will change namespaces,
|
// since other goroutines (on separate threads) will change namespaces,
|
||||||
// ensure the RPC server does not get scheduled onto those
|
// ensure the RPC server does not get scheduled onto those
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@ -162,6 +172,7 @@ func runDaemon(pidfilePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dhcp := newDHCP()
|
dhcp := newDHCP()
|
||||||
|
dhcp.hostNetnsPrefix = hostPrefix
|
||||||
rpc.Register(dhcp)
|
rpc.Register(dhcp)
|
||||||
rpc.HandleHTTP()
|
rpc.HandleHTTP()
|
||||||
http.Serve(l, nil)
|
http.Serve(l, nil)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 CNI authors
|
// Copyright 2016 CNI authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,16 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package ip
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddDefaultRoute sets the default route on the given gateway.
|
func TestDHCP(t *testing.T) {
|
||||||
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
RegisterFailHandler(Fail)
|
||||||
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
|
RunSpecs(t, "DHCP Suite")
|
||||||
return AddRoute(defNet, gw, dev)
|
|
||||||
}
|
}
|
319
plugins/ipam/dhcp/dhcp_test.go
Normal file
319
plugins/ipam/dhcp/dhcp_test.go
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
// Copyright 2015 CNI authors
|
||||||
|
//
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
"github.com/containernetworking/plugins/pkg/testutils"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
"github.com/d2g/dhcp4server"
|
||||||
|
"github.com/d2g/dhcp4server/leasepool"
|
||||||
|
"github.com/d2g/dhcp4server/leasepool/memorypool"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, stopCh <-chan bool) (*sync.WaitGroup, error) {
|
||||||
|
// Add the expected IP to the pool
|
||||||
|
lp := memorypool.MemoryPool{}
|
||||||
|
err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0)})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dhcpServer, err := dhcp4server.New(
|
||||||
|
net.IPv4(192, 168, 1, 1),
|
||||||
|
&lp,
|
||||||
|
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}),
|
||||||
|
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4bcast, Port: 68}),
|
||||||
|
dhcp4server.LeaseDuration(time.Minute*15),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create DHCP server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWg := sync.WaitGroup{}
|
||||||
|
stopWg.Add(2)
|
||||||
|
startWg := sync.WaitGroup{}
|
||||||
|
startWg.Add(2)
|
||||||
|
|
||||||
|
// Run DHCP server in a goroutine so it doesn't block the main thread
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err = netns.Do(func(ns.NetNS) error {
|
||||||
|
startWg.Done()
|
||||||
|
if err := dhcpServer.ListenAndServe(); err != nil {
|
||||||
|
// Log, but don't trap errors; the server will
|
||||||
|
// always report an error when stopped
|
||||||
|
GinkgoT().Logf("DHCP server finished with error: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
stopWg.Done()
|
||||||
|
// Trap any errors after the Done, to allow the main test thread
|
||||||
|
// to continue and clean up. Otherwise the test hangs.
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Stop DHCP server in another goroutine for the same reason
|
||||||
|
go func() {
|
||||||
|
startWg.Done()
|
||||||
|
<-stopCh
|
||||||
|
dhcpServer.Shutdown()
|
||||||
|
stopWg.Done()
|
||||||
|
}()
|
||||||
|
startWg.Wait()
|
||||||
|
|
||||||
|
return &stopWg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
hostVethName string = "dhcp0"
|
||||||
|
contVethName string = "eth0"
|
||||||
|
pidfilePath string = "/var/run/cni/dhcp-client.pid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
os.Remove(socketPath)
|
||||||
|
os.Remove(pidfilePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
os.Remove(socketPath)
|
||||||
|
os.Remove(pidfilePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = Describe("DHCP Operations", func() {
|
||||||
|
var originalNS, targetNS ns.NetNS
|
||||||
|
var dhcpServerStopCh chan bool
|
||||||
|
var dhcpServerDone *sync.WaitGroup
|
||||||
|
var clientCmd *exec.Cmd
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
dhcpServerStopCh = make(chan bool)
|
||||||
|
|
||||||
|
// Create a new NetNS so we don't modify the host
|
||||||
|
var err error
|
||||||
|
originalNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
targetNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
serverIP := net.IPNet{
|
||||||
|
IP: net.IPv4(192, 168, 1, 1),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a veth pair in the "host" (original) NS
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err = netlink.LinkAdd(&netlink.Veth{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: hostVethName,
|
||||||
|
},
|
||||||
|
PeerName: contVethName,
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
host, err := netlink.LinkByName(hostVethName)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = netlink.LinkSetUp(host)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = netlink.AddrAdd(host, &netlink.Addr{IPNet: &serverIP})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = netlink.RouteAdd(&netlink.Route{
|
||||||
|
LinkIndex: host.Attrs().Index,
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
Dst: &net.IPNet{
|
||||||
|
IP: net.IPv4(0, 0, 0, 0),
|
||||||
|
Mask: net.IPv4Mask(0, 0, 0, 0),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
cont, err := netlink.LinkByName(contVethName)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = netlink.LinkSetNsFd(cont, int(targetNS.Fd()))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Move the container side to the container's NS
|
||||||
|
err = targetNS.Do(func(_ ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(contVethName)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = netlink.LinkSetUp(link)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start the DHCP server
|
||||||
|
dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, dhcpServerStopCh)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Start the DHCP client daemon
|
||||||
|
os.MkdirAll(pidfilePath, 0755)
|
||||||
|
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
clientCmd = exec.Command(dhcpPluginPath, "daemon")
|
||||||
|
err = clientCmd.Start()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(clientCmd.Process).NotTo(BeNil())
|
||||||
|
|
||||||
|
// Wait up to 15 seconds for the client socket
|
||||||
|
Eventually(func() bool {
|
||||||
|
_, err := os.Stat(socketPath)
|
||||||
|
return err == nil
|
||||||
|
}, time.Second*15, time.Second/4).Should(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
dhcpServerStopCh <- true
|
||||||
|
dhcpServerDone.Wait()
|
||||||
|
clientCmd.Process.Kill()
|
||||||
|
clientCmd.Wait()
|
||||||
|
|
||||||
|
Expect(originalNS.Close()).To(Succeed())
|
||||||
|
Expect(targetNS.Close()).To(Succeed())
|
||||||
|
os.Remove(socketPath)
|
||||||
|
os.Remove(pidfilePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("configures and deconfigures a link with ADD/DEL", func() {
|
||||||
|
conf := `{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"ipam": {
|
||||||
|
"type": "dhcp"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNS.Path(),
|
||||||
|
IfName: contVethName,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
var addResult *current.Result
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
r, _, err := testutils.CmdAddWithResult(targetNS.Path(), contVethName, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
addResult, err = current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addResult.IPs)).To(Equal(1))
|
||||||
|
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
return testutils.CmdDelWithResult(targetNS.Path(), contVethName, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("correctly handles multiple DELs for the same container", func() {
|
||||||
|
conf := `{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"ipam": {
|
||||||
|
"type": "dhcp"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNS.Path(),
|
||||||
|
IfName: contVethName,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
var addResult *current.Result
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
r, _, err := testutils.CmdAddWithResult(targetNS.Path(), contVethName, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
addResult, err = current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addResult.IPs)).To(Equal(1))
|
||||||
|
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(3)
|
||||||
|
started := sync.WaitGroup{}
|
||||||
|
started.Add(3)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
// Wait until all goroutines are running
|
||||||
|
started.Done()
|
||||||
|
started.Wait()
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
return testutils.CmdDelWithResult(targetNS.Path(), contVethName, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
return testutils.CmdDelWithResult(targetNS.Path(), contVethName, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
@ -20,6 +20,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/d2g/dhcp4"
|
"github.com/d2g/dhcp4"
|
||||||
@ -55,6 +56,7 @@ type DHCPLease struct {
|
|||||||
renewalTime time.Time
|
renewalTime time.Time
|
||||||
rebindingTime time.Time
|
rebindingTime time.Time
|
||||||
expireTime time.Time
|
expireTime time.Time
|
||||||
|
stopping uint32
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
@ -106,7 +108,9 @@ func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
|
|||||||
// Stop terminates the background task that maintains the lease
|
// Stop terminates the background task that maintains the lease
|
||||||
// and issues a DHCP Release
|
// and issues a DHCP Release
|
||||||
func (l *DHCPLease) Stop() {
|
func (l *DHCPLease) Stop() {
|
||||||
close(l.stop)
|
if atomic.CompareAndSwapUint32(&l.stopping, 0, 1) {
|
||||||
|
close(l.stop)
|
||||||
|
}
|
||||||
l.wg.Wait()
|
l.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,8 +296,26 @@ func (l *DHCPLease) Gateway() net.IP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *DHCPLease) Routes() []*types.Route {
|
func (l *DHCPLease) Routes() []*types.Route {
|
||||||
routes := parseRoutes(l.opts)
|
routes := []*types.Route{}
|
||||||
return append(routes, parseCIDRRoutes(l.opts)...)
|
|
||||||
|
// RFC 3442 states that if Classless Static Routes (option 121)
|
||||||
|
// exist, we ignore Static Routes (option 33) and the Router/Gateway.
|
||||||
|
opt121_routes := parseCIDRRoutes(l.opts)
|
||||||
|
if len(opt121_routes) > 0 {
|
||||||
|
return append(routes, opt121_routes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append Static Routes
|
||||||
|
routes = append(routes, parseRoutes(l.opts)...)
|
||||||
|
|
||||||
|
// The CNI spec says even if there is a gateway specified, we must
|
||||||
|
// add a default route in the routes section.
|
||||||
|
if gw := l.Gateway(); gw != nil {
|
||||||
|
_, defaultRoute, _ := net.ParseCIDR("0.0.0.0/0")
|
||||||
|
routes = append(routes, &types.Route{Dst: *defaultRoute, GW: gw})
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
// jitter returns a random value within [-span, span) range
|
// jitter returns a random value within [-span, span) range
|
||||||
|
@ -33,11 +33,13 @@ const socketPath = "/run/cni/dhcp.sock"
|
|||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "daemon" {
|
if len(os.Args) > 1 && os.Args[1] == "daemon" {
|
||||||
var pidfilePath string
|
var pidfilePath string
|
||||||
|
var hostPrefix string
|
||||||
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
|
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
|
||||||
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
|
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
|
||||||
|
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to netns")
|
||||||
daemonFlags.Parse(os.Args[2:])
|
daemonFlags.Parse(os.Args[2:])
|
||||||
|
|
||||||
if err := runDaemon(pidfilePath); err != nil {
|
if err := runDaemon(pidfilePath, hostPrefix); err != nil {
|
||||||
log.Printf(err.Error())
|
log.Printf(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,10 @@ The following [args conventions](https://github.com/containernetworking/cni/blob
|
|||||||
|
|
||||||
* `ips` (array of strings): A list of custom IPs to attempt to allocate
|
* `ips` (array of strings): A list of custom IPs to attempt to allocate
|
||||||
|
|
||||||
|
The following [Capability Args](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md) are supported:
|
||||||
|
|
||||||
|
* `ipRanges`: The exact same as the `ranges` array - a list of address pools
|
||||||
|
|
||||||
### Custom IP allocation
|
### Custom IP allocation
|
||||||
For every requested custom IP, the `host-local` allocator will request that IP
|
For every requested custom IP, the `host-local` allocator will request that IP
|
||||||
if it falls within one of the `range` objects. Thus it is possible to specify
|
if it falls within one of the `range` objects. Thus it is possible to specify
|
||||||
|
@ -23,12 +23,16 @@ import (
|
|||||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The top-level network config, just so we can get the IPAM block
|
// The top-level network config - IPAM plugins are passed the full configuration
|
||||||
|
// of the calling plugin, not just the IPAM section.
|
||||||
type Net struct {
|
type Net struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
CNIVersion string `json:"cniVersion"`
|
CNIVersion string `json:"cniVersion"`
|
||||||
IPAM *IPAMConfig `json:"ipam"`
|
IPAM *IPAMConfig `json:"ipam"`
|
||||||
Args *struct {
|
RuntimeConfig struct { // The capability arg
|
||||||
|
IPRanges []RangeSet `json:"ipRanges,omitempty"`
|
||||||
|
} `json:"runtimeConfig,omitempty"`
|
||||||
|
Args *struct {
|
||||||
A *IPAMArgs `json:"cni"`
|
A *IPAMArgs `json:"cni"`
|
||||||
} `json:"args"`
|
} `json:"args"`
|
||||||
}
|
}
|
||||||
@ -106,6 +110,11 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
|||||||
}
|
}
|
||||||
n.IPAM.Range = nil
|
n.IPAM.Range = nil
|
||||||
|
|
||||||
|
// If a range is supplied as a runtime config, prepend it to the Ranges
|
||||||
|
if len(n.RuntimeConfig.IPRanges) > 0 {
|
||||||
|
n.IPAM.Ranges = append(n.RuntimeConfig.IPRanges, n.IPAM.Ranges...)
|
||||||
|
}
|
||||||
|
|
||||||
if len(n.IPAM.Ranges) == 0 {
|
if len(n.IPAM.Ranges) == 0 {
|
||||||
return nil, "", fmt.Errorf("no IP ranges specified")
|
return nil, "", fmt.Errorf("no IP ranges specified")
|
||||||
}
|
}
|
||||||
|
@ -132,12 +132,18 @@ var _ = Describe("IPAM config", func() {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should parse a mixed config", func() {
|
It("Should parse a mixed config with runtime args", func() {
|
||||||
input := `{
|
input := `{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
|
"runtimeConfig": {
|
||||||
|
"irrelevant": "a",
|
||||||
|
"ipRanges": [
|
||||||
|
[{ "subnet": "12.1.3.0/24" }]
|
||||||
|
]
|
||||||
|
},
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
@ -162,6 +168,17 @@ var _ = Describe("IPAM config", func() {
|
|||||||
Name: "mynet",
|
Name: "mynet",
|
||||||
Type: "host-local",
|
Type: "host-local",
|
||||||
Ranges: []RangeSet{
|
Ranges: []RangeSet{
|
||||||
|
{ // The RuntimeConfig should always be first
|
||||||
|
{
|
||||||
|
RangeStart: net.IP{12, 1, 3, 1},
|
||||||
|
RangeEnd: net.IP{12, 1, 3, 254},
|
||||||
|
Gateway: net.IP{12, 1, 3, 1},
|
||||||
|
Subnet: types.IPNet{
|
||||||
|
IP: net.IP{12, 1, 3, 0},
|
||||||
|
Mask: net.CIDRMask(24, 32),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
RangeStart: net.IP{10, 1, 2, 9},
|
RangeStart: net.IP{10, 1, 2, 9},
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastIPFilePrefix = "last_reserved_ip."
|
const lastIPFilePrefix = "last_reserved_ip."
|
||||||
@ -31,7 +32,7 @@ var defaultDataDir = "/var/lib/cni/networks"
|
|||||||
// Store is a simple disk-backed store that creates one file per IP
|
// Store is a simple disk-backed store that creates one file per IP
|
||||||
// address in a given directory. The contents of the file are the container ID.
|
// address in a given directory. The contents of the file are the container ID.
|
||||||
type Store struct {
|
type Store struct {
|
||||||
FileLock
|
*FileLock
|
||||||
dataDir string
|
dataDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,11 +52,12 @@ func New(network, dataDir string) (*Store, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Store{*lk, dir}, nil
|
return &Store{lk, dir}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) {
|
func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) {
|
||||||
fname := filepath.Join(s.dataDir, ip.String())
|
fname := GetEscapedPath(s.dataDir, ip.String())
|
||||||
|
|
||||||
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644)
|
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644)
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -73,7 +75,7 @@ func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
// store the reserved ip in lastIPFile
|
// store the reserved ip in lastIPFile
|
||||||
ipfile := filepath.Join(s.dataDir, lastIPFilePrefix+rangeID)
|
ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID)
|
||||||
err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644)
|
err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -83,7 +85,7 @@ func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) {
|
|||||||
|
|
||||||
// LastReservedIP returns the last reserved IP if exists
|
// LastReservedIP returns the last reserved IP if exists
|
||||||
func (s *Store) LastReservedIP(rangeID string) (net.IP, error) {
|
func (s *Store) LastReservedIP(rangeID string) (net.IP, error) {
|
||||||
ipfile := filepath.Join(s.dataDir, lastIPFilePrefix+rangeID)
|
ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID)
|
||||||
data, err := ioutil.ReadFile(ipfile)
|
data, err := ioutil.ReadFile(ipfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -92,7 +94,7 @@ func (s *Store) LastReservedIP(rangeID string) (net.IP, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Release(ip net.IP) error {
|
func (s *Store) Release(ip net.IP) error {
|
||||||
return os.Remove(filepath.Join(s.dataDir, ip.String()))
|
return os.Remove(GetEscapedPath(s.dataDir, ip.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// N.B. This function eats errors to be tolerant and
|
// N.B. This function eats errors to be tolerant and
|
||||||
@ -115,3 +117,10 @@ func (s *Store) ReleaseByID(id string) error {
|
|||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetEscapedPath(dataDir string, fname string) string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
fname = strings.Replace(fname, ":", "_", -1)
|
||||||
|
}
|
||||||
|
return filepath.Join(dataDir, fname)
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015-2017 CNI authors
|
// Copyright 2016 CNI authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,23 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// +build !linux
|
package disk
|
||||||
|
|
||||||
package ip
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"testing"
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddRoute adds a universally-scoped route to a device.
|
func TestLock(t *testing.T) {
|
||||||
func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
RegisterFailHandler(Fail)
|
||||||
return types.NotImplementedError
|
RunSpecs(t, "Disk Suite")
|
||||||
}
|
|
||||||
|
|
||||||
// AddHostRoute adds a host-scoped route to a device.
|
|
||||||
func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
|
||||||
return types.NotImplementedError
|
|
||||||
}
|
}
|
@ -15,18 +15,28 @@
|
|||||||
package disk
|
package disk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alexflint/go-filemutex"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileLock wraps os.File to be used as a lock using flock
|
// FileLock wraps os.File to be used as a lock using flock
|
||||||
type FileLock struct {
|
type FileLock struct {
|
||||||
f *os.File
|
f *filemutex.FileMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileLock opens file/dir at path and returns unlocked FileLock object
|
// NewFileLock opens file/dir at path and returns unlocked FileLock object
|
||||||
func NewFileLock(path string) (*FileLock, error) {
|
func NewFileLock(lockPath string) (*FileLock, error) {
|
||||||
f, err := os.Open(path)
|
fi, err := os.Stat(lockPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.IsDir() {
|
||||||
|
lockPath = path.Join(lockPath, "lock")
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := filemutex.New(lockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -34,17 +44,16 @@ func NewFileLock(path string) (*FileLock, error) {
|
|||||||
return &FileLock{f}, nil
|
return &FileLock{f}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes underlying file
|
|
||||||
func (l *FileLock) Close() error {
|
func (l *FileLock) Close() error {
|
||||||
return l.f.Close()
|
return l.f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock acquires an exclusive lock
|
// Lock acquires an exclusive lock
|
||||||
func (l *FileLock) Lock() error {
|
func (l *FileLock) Lock() error {
|
||||||
return syscall.Flock(int(l.f.Fd()), syscall.LOCK_EX)
|
return l.f.Lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock releases the lock
|
// Unlock releases the lock
|
||||||
func (l *FileLock) Unlock() error {
|
func (l *FileLock) Unlock() error {
|
||||||
return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN)
|
return l.f.Unlock()
|
||||||
}
|
}
|
||||||
|
63
plugins/ipam/host-local/backend/disk/lock_test.go
Normal file
63
plugins/ipam/host-local/backend/disk/lock_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2016 CNI authors
|
||||||
|
//
|
||||||
|
// 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 disk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Lock Operations", func() {
|
||||||
|
It("locks a file path", func() {
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// create a dummy file to lock
|
||||||
|
path := filepath.Join(dir, "x")
|
||||||
|
f, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0666)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
err = f.Close()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// now use it to lock
|
||||||
|
m, err := NewFileLock(path)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = m.Lock()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
err = m.Unlock()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("locks a folder path", func() {
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// use the folder to lock
|
||||||
|
m, err := NewFileLock(dir)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = m.Lock()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
err = m.Unlock()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
"github.com/containernetworking/plugins/pkg/testutils"
|
"github.com/containernetworking/plugins/pkg/testutils"
|
||||||
|
|
||||||
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
@ -37,7 +38,7 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
@ -45,26 +46,26 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"resolvConf": "%s/resolv.conf",
|
"resolvConf": "%s/resolv.conf",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
[{ "subnet": "10.1.2.0/24" }, {"subnet": "10.2.2.0/24"}],
|
[{ "subnet": "10.1.2.0/24" }, {"subnet": "10.2.2.0/24"}],
|
||||||
[{ "subnet": "2001:db8:1::0/64" }]
|
[{ "subnet": "2001:db8:1::0/64" }]
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{"dst": "0.0.0.0/0"},
|
{"dst": "0.0.0.0/0"},
|
||||||
{"dst": "::/0"},
|
{"dst": "::/0"},
|
||||||
{"dst": "192.168.0.0/16", "gw": "1.1.1.1"},
|
{"dst": "192.168.0.0/16", "gw": "1.1.1.1"},
|
||||||
{"dst": "2001:db8:2::0/64", "gw": "2001:db8:3::1"}
|
{"dst": "2001:db8:2::0/64", "gw": "2001:db8:3::1"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`, tmpDir, tmpDir)
|
}`, tmpDir, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -112,7 +113,7 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("dummy"))
|
Expect(string(contents)).To(Equal("dummy"))
|
||||||
|
|
||||||
ipFilePath2 := filepath.Join(tmpDir, "mynet", "2001:db8:1::2")
|
ipFilePath2 := filepath.Join(tmpDir, disk.GetEscapedPath("mynet", "2001:db8:1::2"))
|
||||||
contents, err = ioutil.ReadFile(ipFilePath2)
|
contents, err = ioutil.ReadFile(ipFilePath2)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("dummy"))
|
Expect(string(contents)).To(Equal("dummy"))
|
||||||
@ -142,21 +143,21 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.3.0",
|
"cniVersion": "0.3.0",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"dataDir": "%s"
|
"dataDir": "%s"
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -176,7 +177,7 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
@ -184,17 +185,17 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.1.0",
|
"cniVersion": "0.1.0",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"resolvConf": "%s/resolv.conf"
|
"resolvConf": "%s/resolv.conf"
|
||||||
}
|
}
|
||||||
}`, tmpDir, tmpDir)
|
}`, tmpDir, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -245,21 +246,21 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"dataDir": "%s"
|
"dataDir": "%s"
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: " dummy\n ",
|
ContainerID: " dummy\n ",
|
||||||
@ -296,21 +297,21 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.2.0",
|
"cniVersion": "0.2.0",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"dataDir": "%s"
|
"dataDir": "%s"
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "testing",
|
ContainerID: "testing",
|
||||||
@ -331,28 +332,28 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
[{ "subnet": "10.1.2.0/24" }]
|
[{ "subnet": "10.1.2.0/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
"cni": {
|
"cni": {
|
||||||
"ips": ["10.1.2.88"]
|
"ips": ["10.1.2.88"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -376,7 +377,7 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
@ -384,24 +385,24 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
[{ "subnet": "10.1.2.0/24" }],
|
[{ "subnet": "10.1.2.0/24" }],
|
||||||
[{ "subnet": "10.1.3.0/24" }]
|
[{ "subnet": "10.1.3.0/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
"cni": {
|
"cni": {
|
||||||
"ips": ["10.1.2.88", "10.1.3.77"]
|
"ips": ["10.1.2.88", "10.1.3.77"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -426,7 +427,7 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
@ -434,24 +435,24 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
[{"subnet":"172.16.1.0/24"}, { "subnet": "10.1.2.0/24" }],
|
[{"subnet":"172.16.1.0/24"}, { "subnet": "10.1.2.0/24" }],
|
||||||
[{ "subnet": "2001:db8:1::/24" }]
|
[{ "subnet": "2001:db8:1::/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
"cni": {
|
"cni": {
|
||||||
"ips": ["10.1.2.88", "2001:db8:1::999"]
|
"ips": ["10.1.2.88", "2001:db8:1::999"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -476,29 +477,29 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
const ifname string = "eth0"
|
const ifname string = "eth0"
|
||||||
const nspath string = "/some/where"
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
tmpDir, err := getTmpDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
conf := fmt.Sprintf(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
"type": "ipvlan",
|
"type": "ipvlan",
|
||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
[{ "subnet": "10.1.2.0/24" }],
|
[{ "subnet": "10.1.2.0/24" }],
|
||||||
[{ "subnet": "10.1.3.0/24" }]
|
[{ "subnet": "10.1.3.0/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
"cni": {
|
"cni": {
|
||||||
"ips": ["10.1.2.88", "10.1.2.77"]
|
"ips": ["10.1.2.88", "10.1.2.77"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -517,6 +518,15 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func getTmpDir() (string, error) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
||||||
|
if err == nil {
|
||||||
|
tmpDir = filepath.ToSlash(tmpDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpDir, err
|
||||||
|
}
|
||||||
|
|
||||||
func mustCIDR(s string) net.IPNet {
|
func mustCIDR(s string) net.IPNet {
|
||||||
ip, n, err := net.ParseCIDR(s)
|
ip, n, err := net.ParseCIDR(s)
|
||||||
n.IP = ip
|
n.IP = ip
|
||||||
|
10
plugins/linux_only.txt
Normal file
10
plugins/linux_only.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
plugins/ipam/dhcp
|
||||||
|
plugins/main/bridge
|
||||||
|
plugins/main/host-device
|
||||||
|
plugins/main/ipvlan
|
||||||
|
plugins/main/loopback
|
||||||
|
plugins/main/macvlan
|
||||||
|
plugins/main/ptp
|
||||||
|
plugins/main/vlan
|
||||||
|
plugins/meta/portmap
|
||||||
|
plugins/meta/tuning
|
@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/containernetworking/plugins/pkg/ipam"
|
"github.com/containernetworking/plugins/pkg/ipam"
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
"github.com/containernetworking/plugins/pkg/utils"
|
"github.com/containernetworking/plugins/pkg/utils"
|
||||||
|
"github.com/j-keck/arping"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -172,6 +173,13 @@ func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddre
|
|||||||
if err := netlink.AddrAdd(br, addr); err != nil {
|
if err := netlink.AddrAdd(br, addr); err != nil {
|
||||||
return fmt.Errorf("could not add IP address to %q: %v", br.Name, err)
|
return fmt.Errorf("could not add IP address to %q: %v", br.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the bridge's MAC to itself. Otherwise, the bridge will take the
|
||||||
|
// lowest-numbered mac on the bridge, and will change as ifs churn
|
||||||
|
if err := netlink.LinkSetHardwareAddr(br, br.HardwareAddr); err != nil {
|
||||||
|
return fmt.Errorf("could not set bridge's mac: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,8 +302,15 @@ func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// disableIPV6DAD disables IPv6 Duplicate Address Detection (DAD)
|
// disableIPV6DAD disables IPv6 Duplicate Address Detection (DAD)
|
||||||
// for an interface.
|
// for an interface, if the interface does not support enhanced_dad.
|
||||||
|
// We do this because interfaces with hairpin mode will see their own DAD packets
|
||||||
func disableIPV6DAD(ifName string) error {
|
func disableIPV6DAD(ifName string) error {
|
||||||
|
// ehanced_dad sends a nonce with the DAD packets, so that we can safely
|
||||||
|
// ignore ourselves
|
||||||
|
enh, err := ioutil.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/enhanced_dad", ifName))
|
||||||
|
if err == nil && string(enh) == "1\n" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
f := fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/accept_dad", ifName)
|
f := fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/accept_dad", ifName)
|
||||||
return ioutil.WriteFile(f, []byte("0"), 0644)
|
return ioutil.WriteFile(f, []byte("0"), 0644)
|
||||||
}
|
}
|
||||||
@ -363,32 +378,34 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
|
|
||||||
// Configure the container hardware address and IP address(es)
|
// Configure the container hardware address and IP address(es)
|
||||||
if err := netns.Do(func(_ ns.NetNS) error {
|
if err := netns.Do(func(_ ns.NetNS) error {
|
||||||
// Disable IPv6 DAD just in case hairpin mode is enabled on the
|
contVeth, err := net.InterfaceByName(args.IfName)
|
||||||
// bridge. Hairpin mode causes echos of neighbor solicitation
|
if err != nil {
|
||||||
// packets, which causes DAD failures.
|
|
||||||
// TODO: (short term) Disable DAD conditional on actual hairpin mode
|
|
||||||
// TODO: (long term) Use enhanced DAD when that becomes available in kernels.
|
|
||||||
if err := disableIPV6DAD(args.IfName); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable IPv6 DAD just in case hairpin mode is enabled on the
|
||||||
|
// bridge. Hairpin mode causes echos of neighbor solicitation
|
||||||
|
// packets, which causes DAD failures.
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
if ipc.Version == "6" && (n.HairpinMode || n.PromiscMode) {
|
||||||
|
if err := disableIPV6DAD(args.IfName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the IP to the interface
|
||||||
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
|
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.IPs[0].Address.IP.To4() != nil {
|
// Send a gratuitous arp
|
||||||
if err := ip.SetHWAddrByIP(args.IfName, result.IPs[0].Address.IP, nil /* TODO IPv6 */); err != nil {
|
for _, ipc := range result.IPs {
|
||||||
return err
|
if ipc.Version == "4" {
|
||||||
|
_ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refetch the veth since its MAC address may changed
|
|
||||||
link, err := netlink.LinkByName(args.IfName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not lookup %q: %v", args.IfName, err)
|
|
||||||
}
|
|
||||||
containerInterface.Mac = link.Attrs().HardwareAddr.String()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -415,12 +432,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if firstV4Addr != nil {
|
|
||||||
if err := ip.SetHWAddrByIP(n.BrName, firstV4Addr, nil /* TODO IPv6 */); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.IPMasq {
|
if n.IPMasq {
|
||||||
@ -463,10 +474,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
// There is a netns so try to clean up. Delete can be called multiple times
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
// so don't return an error if the device is already removed.
|
// so don't return an error if the device is already removed.
|
||||||
// If the device isn't there then don't try to clean up IP masq either.
|
// If the device isn't there then don't try to clean up IP masq either.
|
||||||
var ipn *net.IPNet
|
var ipnets []*net.IPNet
|
||||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
var err error
|
var err error
|
||||||
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_ALL)
|
ipnets, err = ip.DelLinkByNameAddr(args.IfName)
|
||||||
if err != nil && err == ip.ErrLinkNotFound {
|
if err != nil && err == ip.ErrLinkNotFound {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -477,10 +488,14 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipn != nil && n.IPMasq {
|
if n.IPMasq {
|
||||||
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||||
comment := utils.FormatComment(n.Name, args.ContainerID)
|
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||||
err = ip.TeardownIPMasq(ipn, chain, comment)
|
for _, ipn := range ipnets {
|
||||||
|
if err := ip.TeardownIPMasq(ipn, chain, comment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
"github.com/containernetworking/plugins/pkg/testutils"
|
"github.com/containernetworking/plugins/pkg/testutils"
|
||||||
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
|
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
@ -266,6 +265,7 @@ func (tester *testerV03x) cmdAddTest(tc testCase) {
|
|||||||
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
||||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
|
||||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
|
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
|
||||||
|
bridgeMAC := link.Attrs().HardwareAddr.String()
|
||||||
|
|
||||||
// Ensure bridge has expected gateway address(es)
|
// Ensure bridge has expected gateway address(es)
|
||||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
||||||
@ -274,10 +274,6 @@ func (tester *testerV03x) cmdAddTest(tc testCase) {
|
|||||||
for _, cidr := range tc.expGWCIDRs {
|
for _, cidr := range tc.expGWCIDRs {
|
||||||
ip, subnet, err := net.ParseCIDR(cidr)
|
ip, subnet, err := net.ParseCIDR(cidr)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
if ip.To4() != nil {
|
|
||||||
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
|
||||||
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
subnetPrefix, subnetBits := subnet.Mask.Size()
|
subnetPrefix, subnetBits := subnet.Mask.Size()
|
||||||
@ -300,6 +296,12 @@ func (tester *testerV03x) cmdAddTest(tc testCase) {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||||
tester.vethName = result.Interfaces[1].Name
|
tester.vethName = result.Interfaces[1].Name
|
||||||
|
|
||||||
|
// Check that the bridge has a different mac from the veth
|
||||||
|
// If not, it means the bridge has an unstable mac and will change
|
||||||
|
// as ifs are added and removed
|
||||||
|
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
@ -314,14 +316,11 @@ func (tester *testerV03x) cmdAddTest(tc testCase) {
|
|||||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||||
|
|
||||||
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
|
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
|
||||||
if expCIDRsV4 != nil {
|
|
||||||
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
|
||||||
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
|
||||||
}
|
|
||||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
|
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
|
||||||
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
||||||
|
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
// Ignore link local address which may or may not be
|
// Ignore link local address which may or may not be
|
||||||
// ready when we read addresses.
|
// ready when we read addresses.
|
||||||
@ -442,10 +441,6 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase) {
|
|||||||
for _, cidr := range tc.expGWCIDRs {
|
for _, cidr := range tc.expGWCIDRs {
|
||||||
ip, subnet, err := net.ParseCIDR(cidr)
|
ip, subnet, err := net.ParseCIDR(cidr)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
if ip.To4() != nil {
|
|
||||||
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
|
||||||
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
subnetPrefix, subnetBits := subnet.Mask.Size()
|
subnetPrefix, subnetBits := subnet.Mask.Size()
|
||||||
@ -479,10 +474,6 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase) {
|
|||||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||||
|
|
||||||
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
|
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
|
||||||
if expCIDRsV4 != nil {
|
|
||||||
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
|
||||||
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
|
||||||
}
|
|
||||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
|
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
|
||||||
@ -892,4 +883,28 @@ var _ = Describe("bridge Operations", func() {
|
|||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
It("creates a bridge with a stable MAC addresses", func() {
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
subnet: "10.1.2.0/24",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subnet: "2001:db8:42::/64",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc.cniVersion = "0.3.1"
|
||||||
|
_, _, err := setupBridge(tc.netConf())
|
||||||
|
link, err := netlink.LinkByName(BRNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
origMac := link.Attrs().HardwareAddr
|
||||||
|
|
||||||
|
cmdAddDelTest(originalNS, tc)
|
||||||
|
|
||||||
|
link, err = netlink.LinkByName(BRNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().HardwareAddr).To(Equal(origMac))
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
21
plugins/main/host-device/README.md
Normal file
21
plugins/main/host-device/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# host-device
|
||||||
|
Move an already-existing device in to a container.
|
||||||
|
|
||||||
|
This simple plugin will move the requested device from the host's network namespace
|
||||||
|
to the container's. Nothing else will be done - no IPAM, no addresses.
|
||||||
|
|
||||||
|
The device can be specified with any one of three properties:
|
||||||
|
* `device`: The device name, e.g. `eth0`, `can0`
|
||||||
|
* `hwaddr`: A MAC address
|
||||||
|
* `kernelpath`: The kernel device kobj, e.g. `/sys/devices/pci0000:00/0000:00:1f.6`
|
||||||
|
|
||||||
|
For this plugin, `CNI_IFNAME` will be ignored. Upon DEL, the device will be moved back.
|
||||||
|
|
||||||
|
A sample configuration might look like:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"device": "enp0s1"
|
||||||
|
}
|
||||||
|
```
|
216
plugins/main/host-device/host-device.go
Normal file
216
plugins/main/host-device/host-device.go
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
// Copyright 2015 CNI authors
|
||||||
|
//
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetConf struct {
|
||||||
|
types.NetConf
|
||||||
|
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
|
||||||
|
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
|
||||||
|
KernelPath string `json:"kernelpath"` // Kernelpath of the device
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// this ensures that main runs only on main thread (thread group leader).
|
||||||
|
// since namespace ops (unshare, setns) are done for a single thread, we
|
||||||
|
// must ensure that the goroutine does not jump from OS thread to thread
|
||||||
|
runtime.LockOSThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConf(bytes []byte) (*NetConf, error) {
|
||||||
|
n := &NetConf{}
|
||||||
|
if err := json.Unmarshal(bytes, n); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" {
|
||||||
|
return nil, fmt.Errorf(`specify either "device", "hwaddr" or "kernelpath"`)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
cfg, err := loadConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containerNs, err := ns.GetNS(args.Netns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||||
|
}
|
||||||
|
defer containerNs.Close()
|
||||||
|
|
||||||
|
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find host device: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contDev, err := moveLinkIn(hostDev, containerNs, args.IfName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to move link %v", err)
|
||||||
|
}
|
||||||
|
return printLink(contDev, cfg.CNIVersion, containerNs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
_, err := loadConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containerNs, err := ns.GetNS(args.Netns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||||
|
}
|
||||||
|
defer containerNs.Close()
|
||||||
|
|
||||||
|
if err := moveLinkOut(containerNs, args.IfName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) {
|
||||||
|
if err := netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var contDev netlink.Link
|
||||||
|
if err := containerNs.Do(func(_ ns.NetNS) error {
|
||||||
|
var err error
|
||||||
|
contDev, err = netlink.LinkByName(hostDev.Attrs().Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
// Save host device name into the container device's alias property
|
||||||
|
if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
|
||||||
|
return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
// Rename container device to respect args.IfName
|
||||||
|
if err := netlink.LinkSetName(contDev, ifName); err != nil {
|
||||||
|
return fmt.Errorf("failed to rename device %q to %q: %v", hostDev.Attrs().Name, ifName, err)
|
||||||
|
}
|
||||||
|
// Retrieve link again to get up-to-date name and attributes
|
||||||
|
contDev, err = netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return contDev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveLinkOut(containerNs ns.NetNS, ifName string) error {
|
||||||
|
defaultNs, err := ns.GetCurrentNS()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer defaultNs.Close()
|
||||||
|
|
||||||
|
return containerNs.Do(func(_ ns.NetNS) error {
|
||||||
|
dev, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
// Rename device to it's original name
|
||||||
|
if err := netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
|
||||||
|
return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
|
||||||
|
}
|
||||||
|
if err := netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
|
||||||
|
return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error {
|
||||||
|
result := current.Result{
|
||||||
|
CNIVersion: current.ImplementedSpecVersion,
|
||||||
|
Interfaces: []*current.Interface{
|
||||||
|
{
|
||||||
|
Name: dev.Attrs().Name,
|
||||||
|
Mac: dev.Attrs().HardwareAddr.String(),
|
||||||
|
Sandbox: containerNs.Path(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return types.PrintResult(&result, cniVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
|
||||||
|
links, err := netlink.LinkList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list node links: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(devname) > 0 {
|
||||||
|
return netlink.LinkByName(devname)
|
||||||
|
} else if len(hwaddr) > 0 {
|
||||||
|
hwAddr, err := net.ParseMAC(hwaddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse MAC address %q: %v", hwaddr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, link := range links {
|
||||||
|
if bytes.Equal(link.Attrs().HardwareAddr, hwAddr) {
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(kernelpath) > 0 {
|
||||||
|
if !filepath.IsAbs(kernelpath) || !strings.HasPrefix(kernelpath, "/sys/devices/") {
|
||||||
|
return nil, fmt.Errorf("kernel device path %q must be absolute and begin with /sys/devices/", kernelpath)
|
||||||
|
}
|
||||||
|
netDir := filepath.Join(kernelpath, "net")
|
||||||
|
files, err := ioutil.ReadDir(netDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find network devices at %q", netDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the first device from eg /sys/devices/pci0000:00/0000:00:19.0/net
|
||||||
|
for _, file := range files {
|
||||||
|
// Make sure it's really an interface
|
||||||
|
for _, l := range links {
|
||||||
|
if file.Name() == l.Attrs().Name {
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("failed to find physical interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015-2017 CNI authors
|
// Copyright 2016 CNI authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,25 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// +build !linux
|
package main
|
||||||
|
|
||||||
package ns
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
import "github.com/containernetworking/cni/pkg/types"
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
// Returns an object representing the current OS thread's network namespace
|
func TestVlan(t *testing.T) {
|
||||||
func GetCurrentNS() (NetNS, error) {
|
RegisterFailHandler(Fail)
|
||||||
return nil, types.NotImplementedError
|
RunSpecs(t, "host-device Suite")
|
||||||
}
|
|
||||||
|
|
||||||
func NewNS() (NetNS, error) {
|
|
||||||
return nil, types.NotImplementedError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *netNS) Close() error {
|
|
||||||
return types.NotImplementedError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *netNS) Set() error {
|
|
||||||
return types.NotImplementedError
|
|
||||||
}
|
}
|
155
plugins/main/host-device/host-device_test.go
Normal file
155
plugins/main/host-device/host-device_test.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2017 CNI authors
|
||||||
|
//
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
"github.com/containernetworking/plugins/pkg/testutils"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("base functionality", func() {
|
||||||
|
var originalNS ns.NetNS
|
||||||
|
var ifname string
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
originalNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
ifname = fmt.Sprintf("dummy-%x", rand.Int31())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
originalNS.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Works with a valid config", func() {
|
||||||
|
var origLink netlink.Link
|
||||||
|
|
||||||
|
// prepare ifname in original namespace
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
err := netlink.LinkAdd(&netlink.Dummy{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: ifname,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
origLink, err = netlink.LinkByName(ifname)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = netlink.LinkSetUp(origLink)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// call CmdAdd
|
||||||
|
targetNS, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
CNI_IFNAME := "eth0"
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.0",
|
||||||
|
"name": "cni-plugin-host-device-test",
|
||||||
|
"type": "host-device",
|
||||||
|
"device": %q
|
||||||
|
}`, ifname)
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNS.Path(),
|
||||||
|
IfName: CNI_IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
var resI types.Result
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
var err error
|
||||||
|
resI, _, err = testutils.CmdAddWithResult(targetNS.Path(), CNI_IFNAME, []byte(conf), func() error { return cmdAdd(args) })
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// check that the result was sane
|
||||||
|
res, err := current.NewResultFromResult(resI)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res.Interfaces).To(Equal([]*current.Interface{
|
||||||
|
{
|
||||||
|
Name: CNI_IFNAME,
|
||||||
|
Mac: origLink.Attrs().HardwareAddr.String(),
|
||||||
|
Sandbox: targetNS.Path(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// assert that dummy0 is now in the target namespace
|
||||||
|
err = targetNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
link, err := netlink.LinkByName(CNI_IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// assert that dummy0 is now NOT in the original namespace anymore
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
_, err := netlink.LinkByName(ifname)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Check that deleting the device moves it back and restores the name
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
err = testutils.CmdDelWithResult(targetNS.Path(), CNI_IFNAME, func() error { return cmdDel(args) })
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err := netlink.LinkByName(ifname)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("fails an invalid config", func() {
|
||||||
|
conf := `{
|
||||||
|
"cniVersion": "0.3.0",
|
||||||
|
"name": "cni-plugin-host-device-test",
|
||||||
|
"type": "host-device"
|
||||||
|
}`
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: originalNS.Path(),
|
||||||
|
IfName: ifname,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
_, _, err := testutils.CmdAddWithResult(originalNS.Path(), ifname, []byte(conf), func() error { return cmdAdd(args) })
|
||||||
|
Expect(err).To(MatchError(`specify either "device", "hwaddr" or "kernelpath"`))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
@ -27,10 +27,10 @@ Because all ipvlan interfaces share the MAC address with the host interface, DHC
|
|||||||
|
|
||||||
* `name` (string, required): the name of the network.
|
* `name` (string, required): the name of the network.
|
||||||
* `type` (string, required): "ipvlan".
|
* `type` (string, required): "ipvlan".
|
||||||
* `master` (string, required): name of the host interface to enslave.
|
* `master` (string, required unless chained): name of the host interface to enslave.
|
||||||
* `mode` (string, optional): one of "l2", "l3". Defaults to "l2".
|
* `mode` (string, optional): one of "l2", "l3", "l3s". Defaults to "l2".
|
||||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
||||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
* `ipam` (dictionary, required unless chained): IPAM configuration to be used for this network.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
@ -38,3 +38,8 @@ Because all ipvlan interfaces share the MAC address with the host interface, DHC
|
|||||||
Therefore the container will not be able to reach the host via `ipvlan` interface.
|
Therefore the container will not be able to reach the host via `ipvlan` interface.
|
||||||
Be sure to also have container join a network that provides connectivity to the host (e.g. `ptp`).
|
Be sure to also have container join a network that provides connectivity to the host (e.g. `ptp`).
|
||||||
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
|
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
|
||||||
|
* For IP allocation schemes that cannot be interface agnostic, the ipvlan plugin
|
||||||
|
can be chained with an earlier plugin that handles this logic. If `master` is
|
||||||
|
omitted, then the previous Result must contain a single interface name for the
|
||||||
|
ipvlan plugin to enslave. If `ipam` is omitted, then the previous Result is used
|
||||||
|
to configure the ipvlan interface.
|
||||||
|
@ -32,6 +32,12 @@ import (
|
|||||||
|
|
||||||
type NetConf struct {
|
type NetConf struct {
|
||||||
types.NetConf
|
types.NetConf
|
||||||
|
|
||||||
|
// support chaining for master interface and IP decisions
|
||||||
|
// occurring prior to running ipvlan plugin
|
||||||
|
RawPrevResult *map[string]interface{} `json:"prevResult"`
|
||||||
|
PrevResult *current.Result `json:"-"`
|
||||||
|
|
||||||
Master string `json:"master"`
|
Master string `json:"master"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
MTU int `json:"mtu"`
|
MTU int `json:"mtu"`
|
||||||
@ -49,8 +55,31 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
|
|||||||
if err := json.Unmarshal(bytes, n); err != nil {
|
if err := json.Unmarshal(bytes, n); err != nil {
|
||||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||||
}
|
}
|
||||||
|
// Parse previous result
|
||||||
|
if n.RawPrevResult != nil {
|
||||||
|
resultBytes, err := json.Marshal(n.RawPrevResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("could not serialize prevResult: %v", err)
|
||||||
|
}
|
||||||
|
res, err := version.NewResult(n.CNIVersion, resultBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("could not parse prevResult: %v", err)
|
||||||
|
}
|
||||||
|
n.RawPrevResult = nil
|
||||||
|
n.PrevResult, err = current.NewResultFromResult(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("could not convert result to current version: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if n.Master == "" {
|
if n.Master == "" {
|
||||||
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
|
if n.PrevResult == nil {
|
||||||
|
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
|
||||||
|
}
|
||||||
|
if len(n.PrevResult.Interfaces) == 1 && n.PrevResult.Interfaces[0].Name != "" {
|
||||||
|
n.Master = n.PrevResult.Interfaces[0].Name
|
||||||
|
} else {
|
||||||
|
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return n, n.CNIVersion, nil
|
return n, n.CNIVersion, nil
|
||||||
}
|
}
|
||||||
@ -143,19 +172,26 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// run the IPAM plugin and get back the config to apply
|
var result *current.Result
|
||||||
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
// Configure iface from PrevResult if we have IPs and an IPAM
|
||||||
if err != nil {
|
// block has not been configured
|
||||||
return err
|
if n.IPAM.Type == "" && n.PrevResult != nil && len(n.PrevResult.IPs) > 0 {
|
||||||
}
|
result = n.PrevResult
|
||||||
// Convert whatever the IPAM result was into the current Result type
|
} else {
|
||||||
result, err := current.NewResultFromResult(r)
|
// run the IPAM plugin and get back the config to apply
|
||||||
if err != nil {
|
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
// Convert whatever the IPAM result was into the current Result type
|
||||||
|
result, err = current.NewResultFromResult(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(result.IPs) == 0 {
|
if len(result.IPs) == 0 {
|
||||||
return errors.New("IPAM plugin returned missing IP config")
|
return errors.New("IPAM plugin returned missing IP config")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, ipc := range result.IPs {
|
for _, ipc := range result.IPs {
|
||||||
// All addresses belong to the ipvlan interface
|
// All addresses belong to the ipvlan interface
|
||||||
@ -182,9 +218,12 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
// On chained invocation, IPAM block can be empty
|
||||||
if err != nil {
|
if n.IPAM.Type != "" {
|
||||||
return err
|
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.Netns == "" {
|
if args.Netns == "" {
|
||||||
@ -194,7 +233,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
// There is a netns so try to clean up. Delete can be called multiple times
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
// so don't return an error if the device is already removed.
|
// so don't return an error if the device is already removed.
|
||||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
if _, err := ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4); err != nil {
|
if err := ip.DelLinkByName(args.IfName); err != nil {
|
||||||
if err != ip.ErrLinkNotFound {
|
if err != ip.ErrLinkNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,79 @@ import (
|
|||||||
|
|
||||||
const MASTER_NAME = "eth0"
|
const MASTER_NAME = "eth0"
|
||||||
|
|
||||||
|
func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) {
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *current.Result
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
r, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
result, err = current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(result.Interfaces)).To(Equal(1))
|
||||||
|
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||||
|
Expect(len(result.IPs)).To(Equal(1))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure ipvlan link exists in the target namespace
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||||
|
|
||||||
|
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err = testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure ipvlan link has been deleted
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(link).To(BeNil())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
var _ = Describe("ipvlan Operations", func() {
|
var _ = Describe("ipvlan Operations", func() {
|
||||||
var originalNS ns.NetNS
|
var originalNS ns.NetNS
|
||||||
|
|
||||||
@ -116,76 +189,35 @@ var _ = Describe("ipvlan Operations", func() {
|
|||||||
}
|
}
|
||||||
}`, MASTER_NAME)
|
}`, MASTER_NAME)
|
||||||
|
|
||||||
targetNs, err := ns.NewNS()
|
ipvlanAddDelTest(conf, IFNAME, originalNS)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
})
|
||||||
defer targetNs.Close()
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
It("configures and deconfigures an iplvan link with ADD/DEL when chained", func() {
|
||||||
ContainerID: "dummy",
|
const IFNAME = "ipvl0"
|
||||||
Netns: targetNs.Path(),
|
|
||||||
IfName: IFNAME,
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
var result *current.Result
|
conf := fmt.Sprintf(`{
|
||||||
err = originalNS.Do(func(ns.NetNS) error {
|
"cniVersion": "0.3.1",
|
||||||
defer GinkgoRecover()
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"prevResult": {
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"name": "%s"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ips": [
|
||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"address": "10.1.2.2/24",
|
||||||
|
"gateway": "10.1.2.1",
|
||||||
|
"interface": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routes": []
|
||||||
|
}
|
||||||
|
}`, MASTER_NAME)
|
||||||
|
|
||||||
r, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
|
ipvlanAddDelTest(conf, IFNAME, originalNS)
|
||||||
return cmdAdd(args)
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
result, err = current.GetResult(r)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(len(result.Interfaces)).To(Equal(1))
|
|
||||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
|
||||||
Expect(len(result.IPs)).To(Equal(1))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
// Make sure ipvlan link exists in the target namespace
|
|
||||||
err = targetNs.Do(func(ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
link, err := netlink.LinkByName(IFNAME)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
|
||||||
|
|
||||||
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
|
||||||
|
|
||||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(len(addrs)).To(Equal(1))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
err = originalNS.Do(func(ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
err = testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
|
||||||
return cmdDel(args)
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
// Make sure ipvlan link has been deleted
|
|
||||||
err = targetNs.Do(func(ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
link, err := netlink.LinkByName(IFNAME)
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
Expect(link).To(BeNil())
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("deconfigures an unconfigured ipvlan link with DEL", func() {
|
It("deconfigures an unconfigured ipvlan link with DEL", func() {
|
||||||
|
@ -161,11 +161,28 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete link if err to avoid link leak in this ns
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
netns.Do(func(_ ns.NetNS) error {
|
||||||
|
return ip.DelLinkByName(args.IfName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// run the IPAM plugin and get back the config to apply
|
// run the IPAM plugin and get back the config to apply
|
||||||
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoke ipam del if err to avoid ip leak
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Convert whatever the IPAM result was into the current Result type
|
// Convert whatever the IPAM result was into the current Result type
|
||||||
result, err := current.NewResultFromResult(r)
|
result, err := current.NewResultFromResult(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -226,7 +243,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
// There is a netns so try to clean up. Delete can be called multiple times
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
// so don't return an error if the device is already removed.
|
// so don't return an error if the device is already removed.
|
||||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
if _, err := ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4); err != nil {
|
if err := ip.DelLinkByName(args.IfName); err != nil {
|
||||||
if err != ip.ErrLinkNotFound {
|
if err != ip.ErrLinkNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -259,10 +259,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
// There is a netns so try to clean up. Delete can be called multiple times
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
// so don't return an error if the device is already removed.
|
// so don't return an error if the device is already removed.
|
||||||
// If the device isn't there then don't try to clean up IP masq either.
|
// If the device isn't there then don't try to clean up IP masq either.
|
||||||
var ipn *net.IPNet
|
var ipnets []*net.IPNet
|
||||||
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
var err error
|
var err error
|
||||||
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
ipnets, err = ip.DelLinkByNameAddr(args.IfName)
|
||||||
if err != nil && err == ip.ErrLinkNotFound {
|
if err != nil && err == ip.ErrLinkNotFound {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -273,10 +273,12 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipn != nil && conf.IPMasq {
|
if len(ipnets) != 0 && conf.IPMasq {
|
||||||
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
||||||
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
||||||
err = ip.TeardownIPMasq(ipn, chain, comment)
|
for _, ipn := range ipnets {
|
||||||
|
err = ip.TeardownIPMasq(ipn, chain, comment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -181,9 +181,8 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
_, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
err = ip.DelLinkByName(args.IfName)
|
||||||
// FIXME: use ip.ErrLinkNotFound when cni is revendored
|
if err != nil && err != ip.ErrLinkNotFound {
|
||||||
if err != nil && err.Error() == "Link not found" {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -8,6 +8,8 @@ You should use this plugin as part of a network configuration list. It accepts
|
|||||||
the following configuration options:
|
the following configuration options:
|
||||||
|
|
||||||
* `snat` - boolean, default true. If true or omitted, set up the SNAT chains
|
* `snat` - boolean, default true. If true or omitted, set up the SNAT chains
|
||||||
|
* `markMasqBit` - int, (0-31), default 13. The mark bit to use for masquerading (see section SNAT). Cannot be set when `externalSetMarkChain` is used.
|
||||||
|
* `externalSetMarkChain` - string, default nil. If you already have a Masquerade mark chain (e.g. Kubernetes), specify it here. This will use that instead of creating a separate chain. When this is set, `markMasqBit` must be unspecified.
|
||||||
* `conditionsV4`, `conditionsV6` - array of strings. A list of arbitrary `iptables`
|
* `conditionsV4`, `conditionsV6` - array of strings. A list of arbitrary `iptables`
|
||||||
matches to add to the per-container rule. This may be useful if you wish to
|
matches to add to the per-container rule. This may be useful if you wish to
|
||||||
exclude specific IPs from port-mapping
|
exclude specific IPs from port-mapping
|
||||||
@ -15,7 +17,7 @@ exclude specific IPs from port-mapping
|
|||||||
The plugin expects to receive the actual list of port mappings via the
|
The plugin expects to receive the actual list of port mappings via the
|
||||||
`portMappings` [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
|
`portMappings` [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
|
||||||
|
|
||||||
So a sample standalone config list (with the file extension .conflist) might
|
A sample standalone config list for Kubernetes (with the file extension .conflist) might
|
||||||
look like:
|
look like:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@ -39,21 +41,31 @@ look like:
|
|||||||
{
|
{
|
||||||
"type": "portmap",
|
"type": "portmap",
|
||||||
"capabilities": {"portMappings": true},
|
"capabilities": {"portMappings": true},
|
||||||
"snat": false,
|
"externalSetMarkChain": "KUBE-MARK-MASQ"
|
||||||
"conditionsV4": ["!", "-d", "192.0.2.0/24"],
|
|
||||||
"conditionsV6": ["!", "-d", "fc00::/7"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A configuration file with all options set:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "portmap",
|
||||||
|
"capabilities": {"portMappings": true},
|
||||||
|
"snat": true,
|
||||||
|
"markMasqBit": 13,
|
||||||
|
"externalSetMarkChain": "CNI-HOSTPORT-SETMARK",
|
||||||
|
"conditionsV4": ["!", "-d", "192.0.2.0/24"],
|
||||||
|
"conditionsV6": ["!", "-d", "fc00::/7"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Rule structure
|
## Rule structure
|
||||||
The plugin sets up two sequences of chains and rules - one "primary" DNAT
|
The plugin sets up two sequences of chains and rules - one "primary" DNAT
|
||||||
sequence to rewrite the destination, and one additional SNAT sequence that
|
sequence to rewrite the destination, and one additional SNAT sequence that
|
||||||
rewrites the source address for packets from localhost. The sequence is somewhat
|
will masquerade traffic as needed.
|
||||||
complex to minimize the number of rules non-forwarded packets must traverse.
|
|
||||||
|
|
||||||
|
|
||||||
### DNAT
|
### DNAT
|
||||||
@ -68,50 +80,54 @@ rules look like this:
|
|||||||
- `--dst-type LOCAL -j CNI-HOSTPORT-DNAT`
|
- `--dst-type LOCAL -j CNI-HOSTPORT-DNAT`
|
||||||
|
|
||||||
`CNI-HOSTPORT-DNAT` chain:
|
`CNI-HOSTPORT-DNAT` chain:
|
||||||
- `${ConditionsV4/6} -j CNI-DN-xxxxxx` (where xxxxxx is a function of the ContainerID and network name)
|
- `${ConditionsV4/6} -p tcp --destination-ports 8080,8043 -j CNI-DN-xxxxxx` (where xxxxxx is a function of the ContainerID and network name)
|
||||||
|
|
||||||
`CNI-DN-xxxxxx` chain:
|
`CNI-HOSTPORT-SETMARK` chain:
|
||||||
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80`
|
- `-j MARK --set-xmark 0x2000/0x2000`
|
||||||
|
|
||||||
|
`CNI-DN-xxxxxx` chain:
|
||||||
|
- `-p tcp -s 172.16.30.2 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
|
||||||
|
- `-p tcp -s 127.0.0.1 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade localhost traffic)
|
||||||
|
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80` (rewrite destination)
|
||||||
|
- `-p tcp -s 172.16.30.2 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||||
|
- `-p tcp -s 127.0.0.1 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||||
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
|
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
|
||||||
|
|
||||||
New connections to the host will have to traverse every rule, so large numbers
|
New connections to the host will have to traverse every rule, so large numbers
|
||||||
of port forwards may have a performance impact. This won't affect established
|
of port forwards may have a performance impact. This won't affect established
|
||||||
connections, just the first packet.
|
connections, just the first packet.
|
||||||
|
|
||||||
### SNAT
|
### SNAT (Masquerade)
|
||||||
The SNAT rule enables port-forwarding from the localhost IP on the host.
|
Some packets also need to have the source address rewritten:
|
||||||
This rule rewrites (masquerades) the source address for connections from
|
* connections from localhost
|
||||||
localhost. If this rule did not exist, a connection to `localhost:80` would
|
* Hairpin traffic back to the container.
|
||||||
still have a source IP of 127.0.0.1 when received by the container, so no
|
|
||||||
packets would respond. Again, it is a sequence of 3 chains. Because SNAT has to
|
In the DNAT chain, a bit is set on the mark for packets that need snat. This
|
||||||
occur in the `POSTROUTING` chain, the packet has already been through the DNAT
|
chain performs that masquerading. By default, bit 13 is set, but this is
|
||||||
chain.
|
configurable. If you are using other tools that also use the iptables mark,
|
||||||
|
you should make sure this doesn't conflict.
|
||||||
|
|
||||||
|
Some container runtimes, most notably Kubernetes, already have a set of rules
|
||||||
|
for masquerading when a specific mark bit is set. If so enabled, the plugin
|
||||||
|
will use that chain instead.
|
||||||
|
|
||||||
`POSTROUTING`:
|
`POSTROUTING`:
|
||||||
- `-s 127.0.0.1 ! -d 127.0.0.1 -j CNI-HOSTPORT-SNAT`
|
- `-j CNI-HOSTPORT-MASQ`
|
||||||
|
|
||||||
`CNI-HOSTPORT-SNAT`:
|
`CNI-HOSTPORT-MASQ`:
|
||||||
- `-j CNI-SN-xxxxx`
|
- `--mark 0x2000 -j MASQUERADE`
|
||||||
|
|
||||||
`CNI-SN-xxxxx`:
|
|
||||||
- `-p tcp -s 127.0.0.1 -d 172.16.30.2 --dport 80 -j MASQUERADE`
|
|
||||||
- `-p tcp -s 127.0.0.1 -d 172.16.30.2 --dport 443 -j MASQUERADE`
|
|
||||||
|
|
||||||
Only new connections from the host, where the source address is 127.0.0.1 but
|
|
||||||
not the destination will traverse this chain. It is unlikely that any packets
|
|
||||||
will reach these rules without being SNATted, so the cost should be minimal.
|
|
||||||
|
|
||||||
Because MASQUERADE happens in POSTROUTING, it means that packets with source ip
|
Because MASQUERADE happens in POSTROUTING, it means that packets with source ip
|
||||||
127.0.0.1 need to pass a routing boundary. By default, that is not allowed
|
127.0.0.1 need to first pass a routing boundary before being masqueraded. By
|
||||||
in Linux. So, need to enable the sysctl `net.ipv4.conf.IFNAME.route_localnet`,
|
default, that is not allowed in Linux. So, the plugin needs to enable the sysctl
|
||||||
where IFNAME is the name of the host-side interface that routes traffic to the
|
`net.ipv4.conf.IFNAME.route_localnet`, where IFNAME is the name of the host-side
|
||||||
container.
|
interface that routes traffic to the container.
|
||||||
|
|
||||||
There is no equivalent to `route_localnet` for ipv6, so SNAT does not work
|
There is no equivalent to `route_localnet` for ipv6, so connections to ::1
|
||||||
for ipv6. If you need port forwarding from localhost, your container must have
|
will not be portmapped for ipv6. If you need port forwarding from localhost,
|
||||||
an ipv4 address.
|
your container must have an ipv4 address.
|
||||||
|
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
- ipsets could improve efficiency
|
- ipsets could improve efficiency
|
||||||
- SNAT does not work with ipv6.
|
- forwarding from localhost does not work with ipv6.
|
||||||
|
@ -25,12 +25,14 @@ import (
|
|||||||
type chain struct {
|
type chain struct {
|
||||||
table string
|
table string
|
||||||
name string
|
name string
|
||||||
entryRule []string // the rule that enters this chain
|
|
||||||
entryChains []string // the chains to add the entry rule
|
entryChains []string // the chains to add the entry rule
|
||||||
|
|
||||||
|
entryRules [][]string // the rules that "point" to this chain
|
||||||
|
rules [][]string // the rules this chain contains
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup idempotently creates the chain. It will not error if the chain exists.
|
// setup idempotently creates the chain. It will not error if the chain exists.
|
||||||
func (c *chain) setup(ipt *iptables.IPTables, rules [][]string) error {
|
func (c *chain) setup(ipt *iptables.IPTables) error {
|
||||||
// create the chain
|
// create the chain
|
||||||
exists, err := chainExists(ipt, c.table, c.name)
|
exists, err := chainExists(ipt, c.table, c.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -43,17 +45,21 @@ func (c *chain) setup(ipt *iptables.IPTables, rules [][]string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the rules to the chain
|
// Add the rules to the chain
|
||||||
for i := len(rules) - 1; i >= 0; i-- {
|
for i := len(c.rules) - 1; i >= 0; i-- {
|
||||||
if err := prependUnique(ipt, c.table, c.name, rules[i]); err != nil {
|
if err := prependUnique(ipt, c.table, c.name, c.rules[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the entry rules
|
// Add the entry rules to the entry chains
|
||||||
entryRule := append(c.entryRule, "-j", c.name)
|
|
||||||
for _, entryChain := range c.entryChains {
|
for _, entryChain := range c.entryChains {
|
||||||
if err := prependUnique(ipt, c.table, entryChain, entryRule); err != nil {
|
for i := len(c.entryRules) - 1; i >= 0; i-- {
|
||||||
return err
|
r := []string{}
|
||||||
|
r = append(r, c.entryRules[i]...)
|
||||||
|
r = append(r, "-j", c.name)
|
||||||
|
if err := prependUnique(ipt, c.table, entryChain, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,8 +49,12 @@ var _ = Describe("chain tests", func() {
|
|||||||
testChain = chain{
|
testChain = chain{
|
||||||
table: TABLE,
|
table: TABLE,
|
||||||
name: chainName,
|
name: chainName,
|
||||||
entryRule: []string{"-d", "203.0.113.1"},
|
|
||||||
entryChains: []string{tlChainName},
|
entryChains: []string{tlChainName},
|
||||||
|
entryRules: [][]string{{"-d", "203.0.113.1"}},
|
||||||
|
rules: [][]string{
|
||||||
|
{"-m", "comment", "--comment", "test 1", "-j", "RETURN"},
|
||||||
|
{"-m", "comment", "--comment", "test 2", "-j", "RETURN"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
@ -90,11 +94,7 @@ var _ = Describe("chain tests", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// Create the chain
|
// Create the chain
|
||||||
chainRules := [][]string{
|
err = testChain.setup(ipt)
|
||||||
{"-m", "comment", "--comment", "test 1", "-j", "RETURN"},
|
|
||||||
{"-m", "comment", "--comment", "test 2", "-j", "RETURN"},
|
|
||||||
}
|
|
||||||
err = testChain.setup(ipt, chainRules)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// Verify the chain exists
|
// Verify the chain exists
|
||||||
@ -151,15 +151,11 @@ var _ = Describe("chain tests", func() {
|
|||||||
It("creates chains idempotently", func() {
|
It("creates chains idempotently", func() {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// Create the chain
|
err := testChain.setup(ipt)
|
||||||
chainRules := [][]string{
|
|
||||||
{"-m", "comment", "--comment", "test", "-j", "RETURN"},
|
|
||||||
}
|
|
||||||
err := testChain.setup(ipt, chainRules)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// Create it again!
|
// Create it again!
|
||||||
err = testChain.setup(ipt, chainRules)
|
err = testChain.setup(ipt)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// Make sure there are only two rules
|
// Make sure there are only two rules
|
||||||
@ -167,18 +163,14 @@ var _ = Describe("chain tests", func() {
|
|||||||
rules, err := ipt.List(TABLE, testChain.name)
|
rules, err := ipt.List(TABLE, testChain.name)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
Expect(len(rules)).To(Equal(2))
|
Expect(len(rules)).To(Equal(3))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("deletes chains idempotently", func() {
|
It("deletes chains idempotently", func() {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// Create the chain
|
err := testChain.setup(ipt)
|
||||||
chainRules := [][]string{
|
|
||||||
{"-m", "comment", "--comment", "test", "-j", "RETURN"},
|
|
||||||
}
|
|
||||||
err := testChain.setup(ipt, chainRules)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
err = testChain.teardown(ipt)
|
err = testChain.teardown(ipt)
|
||||||
|
@ -47,10 +47,12 @@ type PortMapEntry struct {
|
|||||||
|
|
||||||
type PortMapConf struct {
|
type PortMapConf struct {
|
||||||
types.NetConf
|
types.NetConf
|
||||||
SNAT *bool `json:"snat,omitempty"`
|
SNAT *bool `json:"snat,omitempty"`
|
||||||
ConditionsV4 *[]string `json:"conditionsV4"`
|
ConditionsV4 *[]string `json:"conditionsV4"`
|
||||||
ConditionsV6 *[]string `json:"conditionsV6"`
|
ConditionsV6 *[]string `json:"conditionsV6"`
|
||||||
RuntimeConfig struct {
|
MarkMasqBit *int `json:"markMasqBit"`
|
||||||
|
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
|
||||||
|
RuntimeConfig struct {
|
||||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||||
} `json:"runtimeConfig,omitempty"`
|
} `json:"runtimeConfig,omitempty"`
|
||||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||||
@ -63,6 +65,10 @@ type PortMapConf struct {
|
|||||||
ContIPv6 net.IP `json:"-"`
|
ContIPv6 net.IP `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The default mark bit to signal that masquerading is required
|
||||||
|
// Kubernetes uses 14 and 15, Calico uses 20-31.
|
||||||
|
const DefaultMarkBit = 13
|
||||||
|
|
||||||
func cmdAdd(args *skel.CmdArgs) error {
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
netConf, err := parseConfig(args.StdinData, args.IfName)
|
netConf, err := parseConfig(args.StdinData, args.IfName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -145,6 +151,19 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
|
|||||||
conf.SNAT = &tvar
|
conf.SNAT = &tvar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.MarkMasqBit != nil && conf.ExternalSetMarkChain != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.MarkMasqBit == nil {
|
||||||
|
bvar := DefaultMarkBit // go constants are "special"
|
||||||
|
conf.MarkMasqBit = &bvar
|
||||||
|
}
|
||||||
|
|
||||||
|
if *conf.MarkMasqBit < 0 || *conf.MarkMasqBit > 31 {
|
||||||
|
return nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
|
||||||
|
}
|
||||||
|
|
||||||
// Reject invalid port numbers
|
// Reject invalid port numbers
|
||||||
for _, pm := range conf.RuntimeConfig.PortMaps {
|
for _, pm := range conf.RuntimeConfig.PortMaps {
|
||||||
if pm.ContainerPort <= 0 {
|
if pm.ContainerPort <= 0 {
|
||||||
|
@ -17,6 +17,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||||
@ -24,33 +25,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// This creates the chains to be added to iptables. The basic structure is
|
// This creates the chains to be added to iptables. The basic structure is
|
||||||
// a bit complex for efficiencies sake. We create 2 chains: a summary chain
|
// a bit complex for efficiency's sake. We create 2 chains: a summary chain
|
||||||
// that is shared between invocations, and an invocation (container)-specific
|
// that is shared between invocations, and an invocation (container)-specific
|
||||||
// chain. This minimizes the number of operations on the top level, but allows
|
// chain. This minimizes the number of operations on the top level, but allows
|
||||||
// for easy cleanup.
|
// for easy cleanup.
|
||||||
//
|
//
|
||||||
// We also create DNAT chains to rewrite destinations, and SNAT chains so that
|
|
||||||
// connections to localhost work.
|
|
||||||
//
|
|
||||||
// The basic setup (all operations are on the nat table) is:
|
// The basic setup (all operations are on the nat table) is:
|
||||||
//
|
//
|
||||||
// DNAT case (rewrite destination IP and port):
|
// DNAT case (rewrite destination IP and port):
|
||||||
// PREROUTING, OUTPUT: --dst-type local -j CNI-HOSTPORT_DNAT
|
// PREROUTING, OUTPUT: --dst-type local -j CNI-HOSTPORT-DNAT
|
||||||
// CNI-HOSTPORT-DNAT: -j CNI-DN-abcd123
|
// CNI-HOSTPORT-DNAT: --destination-ports 8080,8081 -j CNI-DN-abcd123
|
||||||
// CNI-DN-abcd123: -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.33:80
|
// CNI-DN-abcd123: -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.33:80
|
||||||
// CNI-DN-abcd123: -p tcp --dport 8081 -j DNAT ...
|
// CNI-DN-abcd123: -p tcp --dport 8081 -j DNAT ...
|
||||||
//
|
|
||||||
// SNAT case (rewrite source IP from localhost after dnat):
|
|
||||||
// POSTROUTING: -s 127.0.0.1 ! -d 127.0.0.1 -j CNI-HOSTPORT-SNAT
|
|
||||||
// CNI-HOSTPORT-SNAT: -j CNI-SN-abcd123
|
|
||||||
// CNI-SN-abcd123: -p tcp -s 127.0.0.1 -d 192.0.2.33 --dport 80 -j MASQUERADE
|
|
||||||
// CNI-SN-abcd123: -p tcp -s 127.0.0.1 -d 192.0.2.33 --dport 90 -j MASQUERADE
|
|
||||||
|
|
||||||
// The names of the top-level summary chains.
|
// The names of the top-level summary chains.
|
||||||
// These should never be changed, or else upgrading will require manual
|
// These should never be changed, or else upgrading will require manual
|
||||||
// intervention.
|
// intervention.
|
||||||
const TopLevelDNATChainName = "CNI-HOSTPORT-DNAT"
|
const TopLevelDNATChainName = "CNI-HOSTPORT-DNAT"
|
||||||
const TopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
const SetMarkChainName = "CNI-HOSTPORT-SETMARK"
|
||||||
|
const MarkMasqChainName = "CNI-HOSTPORT-MASQ"
|
||||||
|
const OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||||
|
|
||||||
// forwardPorts establishes port forwarding to a given container IP.
|
// forwardPorts establishes port forwarding to a given container IP.
|
||||||
// containerIP can be either v4 or v6.
|
// containerIP can be either v4 or v6.
|
||||||
@ -59,48 +53,35 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
|||||||
|
|
||||||
var ipt *iptables.IPTables
|
var ipt *iptables.IPTables
|
||||||
var err error
|
var err error
|
||||||
var conditions *[]string
|
|
||||||
|
|
||||||
if isV6 {
|
if isV6 {
|
||||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
conditions = config.ConditionsV6
|
|
||||||
} else {
|
} else {
|
||||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
conditions = config.ConditionsV4
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open iptables: %v", err)
|
return fmt.Errorf("failed to open iptables: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
toplevelDnatChain := genToplevelDnatChain()
|
// Enable masquerading for traffic as necessary.
|
||||||
if err := toplevelDnatChain.setup(ipt, nil); err != nil {
|
// The DNAT chain sets a mark bit for traffic that needs masq:
|
||||||
return fmt.Errorf("failed to create top-level DNAT chain: %v", err)
|
// - connections from localhost
|
||||||
}
|
// - hairpin traffic back to the container
|
||||||
|
// Idempotently create the rule that masquerades traffic with this mark.
|
||||||
|
// Need to do this first; the DNAT rules reference these chains
|
||||||
|
if *config.SNAT {
|
||||||
|
if config.ExternalSetMarkChain == nil {
|
||||||
|
setMarkChain := genSetMarkChain(*config.MarkMasqBit)
|
||||||
|
if err := setMarkChain.setup(ipt); err != nil {
|
||||||
|
return fmt.Errorf("unable to create chain %s: %v", setMarkChain.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
dnatChain := genDnatChain(config.Name, config.ContainerID, conditions)
|
masqChain := genMarkMasqChain(*config.MarkMasqBit)
|
||||||
_ = dnatChain.teardown(ipt) // If we somehow collide on this container ID + network, cleanup
|
if err := masqChain.setup(ipt); err != nil {
|
||||||
|
return fmt.Errorf("unable to create chain %s: %v", setMarkChain.name, err)
|
||||||
dnatRules := dnatRules(config.RuntimeConfig.PortMaps, containerIP)
|
}
|
||||||
if err := dnatChain.setup(ipt, dnatRules); err != nil {
|
|
||||||
return fmt.Errorf("unable to setup DNAT: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable SNAT for connections to localhost.
|
|
||||||
// This won't work for ipv6, since the kernel doesn't have the equvalent
|
|
||||||
// route_localnet sysctl.
|
|
||||||
if *config.SNAT && !isV6 {
|
|
||||||
toplevelSnatChain := genToplevelSnatChain(isV6)
|
|
||||||
if err := toplevelSnatChain.setup(ipt, nil); err != nil {
|
|
||||||
return fmt.Errorf("failed to create top-level SNAT chain: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
snatChain := genSnatChain(config.Name, config.ContainerID)
|
|
||||||
_ = snatChain.teardown(ipt)
|
|
||||||
|
|
||||||
snatRules := snatRules(config.RuntimeConfig.PortMaps, containerIP)
|
|
||||||
if err := snatChain.setup(ipt, snatRules); err != nil {
|
|
||||||
return fmt.Errorf("unable to setup SNAT: %v", err)
|
|
||||||
}
|
|
||||||
if !isV6 {
|
if !isV6 {
|
||||||
// Set the route_localnet bit on the host interface, so that
|
// Set the route_localnet bit on the host interface, so that
|
||||||
// 127/8 can cross a routing boundary.
|
// 127/8 can cross a routing boundary.
|
||||||
@ -113,6 +94,20 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate the DNAT (actual port forwarding) rules
|
||||||
|
toplevelDnatChain := genToplevelDnatChain()
|
||||||
|
if err := toplevelDnatChain.setup(ipt); err != nil {
|
||||||
|
return fmt.Errorf("failed to create top-level DNAT chain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||||
|
// First, idempotently tear down this chain in case there was some
|
||||||
|
// sort of collision or bad state.
|
||||||
|
fillDnatRules(&dnatChain, config, containerIP)
|
||||||
|
if err := dnatChain.setup(ipt); err != nil {
|
||||||
|
return fmt.Errorf("unable to setup DNAT: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,106 +119,153 @@ func genToplevelDnatChain() chain {
|
|||||||
return chain{
|
return chain{
|
||||||
table: "nat",
|
table: "nat",
|
||||||
name: TopLevelDNATChainName,
|
name: TopLevelDNATChainName,
|
||||||
entryRule: []string{
|
entryRules: [][]string{{
|
||||||
"-m", "addrtype",
|
"-m", "addrtype",
|
||||||
"--dst-type", "LOCAL",
|
"--dst-type", "LOCAL",
|
||||||
},
|
}},
|
||||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// genDnatChain creates the per-container chain.
|
// genDnatChain creates the per-container chain.
|
||||||
// Conditions are any static entry conditions for the chain.
|
// Conditions are any static entry conditions for the chain.
|
||||||
func genDnatChain(netName, containerID string, conditions *[]string) chain {
|
func genDnatChain(netName, containerID string) chain {
|
||||||
name := formatChainName("DN-", netName, containerID)
|
return chain{
|
||||||
comment := fmt.Sprintf(`dnat name: "%s" id: "%s"`, netName, containerID)
|
table: "nat",
|
||||||
|
name: formatChainName("DN-", netName, containerID),
|
||||||
ch := chain{
|
|
||||||
table: "nat",
|
|
||||||
name: name,
|
|
||||||
entryRule: []string{
|
|
||||||
"-m", "comment",
|
|
||||||
"--comment", comment,
|
|
||||||
},
|
|
||||||
entryChains: []string{TopLevelDNATChainName},
|
entryChains: []string{TopLevelDNATChainName},
|
||||||
}
|
}
|
||||||
if conditions != nil && len(*conditions) != 0 {
|
|
||||||
ch.entryRule = append(ch.entryRule, *conditions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dnatRules generates the destination NAT rules, one per port, to direct
|
// dnatRules generates the destination NAT rules, one per port, to direct
|
||||||
// traffic from hostip:hostport to podip:podport
|
// traffic from hostip:hostport to podip:podport
|
||||||
func dnatRules(entries []PortMapEntry, containerIP net.IP) [][]string {
|
func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||||
out := make([][]string, 0, len(entries))
|
isV6 := (containerIP.To4() == nil)
|
||||||
|
comment := trimComment(fmt.Sprintf(`dnat name: "%s" id: "%s"`, config.Name, config.ContainerID))
|
||||||
|
entries := config.RuntimeConfig.PortMaps
|
||||||
|
setMarkChainName := SetMarkChainName
|
||||||
|
if config.ExternalSetMarkChain != nil {
|
||||||
|
setMarkChainName = *config.ExternalSetMarkChain
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate the dnat entry rules. We'll use multiport, but it ony accepts
|
||||||
|
// up to 15 rules, so partition the list if needed.
|
||||||
|
// Do it in a stable order for testing
|
||||||
|
protoPorts := groupByProto(entries)
|
||||||
|
protos := []string{}
|
||||||
|
for proto := range protoPorts {
|
||||||
|
protos = append(protos, proto)
|
||||||
|
}
|
||||||
|
sort.Strings(protos)
|
||||||
|
for _, proto := range protos {
|
||||||
|
for _, portSpec := range splitPortList(protoPorts[proto]) {
|
||||||
|
r := []string{
|
||||||
|
"-m", "comment",
|
||||||
|
"--comment", comment,
|
||||||
|
"-m", "multiport",
|
||||||
|
"-p", proto,
|
||||||
|
"--destination-ports", portSpec,
|
||||||
|
}
|
||||||
|
|
||||||
|
if isV6 && config.ConditionsV6 != nil && len(*config.ConditionsV6) > 0 {
|
||||||
|
r = append(r, *config.ConditionsV6...)
|
||||||
|
} else if !isV6 && config.ConditionsV4 != nil && len(*config.ConditionsV4) > 0 {
|
||||||
|
r = append(r, *config.ConditionsV4...)
|
||||||
|
}
|
||||||
|
c.entryRules = append(c.entryRules, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For every entry, generate 3 rules:
|
||||||
|
// - mark hairpin for masq
|
||||||
|
// - mark localhost for masq (for v4)
|
||||||
|
// - do dnat
|
||||||
|
// the ordering is important here; the mark rules must be first.
|
||||||
|
c.rules = make([][]string, 0, 3*len(entries))
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
rule := []string{
|
ruleBase := []string{
|
||||||
"-p", entry.Protocol,
|
"-p", entry.Protocol,
|
||||||
"--dport", strconv.Itoa(entry.HostPort)}
|
"--dport", strconv.Itoa(entry.HostPort)}
|
||||||
|
|
||||||
if entry.HostIP != "" {
|
if entry.HostIP != "" {
|
||||||
rule = append(rule,
|
ruleBase = append(ruleBase,
|
||||||
"-d", entry.HostIP)
|
"-d", entry.HostIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule = append(rule,
|
// Add mark-to-masquerade rules for hairpin and localhost
|
||||||
|
if *config.SNAT {
|
||||||
|
// hairpin
|
||||||
|
hpRule := make([]string, len(ruleBase), len(ruleBase)+4)
|
||||||
|
copy(hpRule, ruleBase)
|
||||||
|
|
||||||
|
hpRule = append(hpRule,
|
||||||
|
"-s", containerIP.String(),
|
||||||
|
"-j", setMarkChainName,
|
||||||
|
)
|
||||||
|
c.rules = append(c.rules, hpRule)
|
||||||
|
|
||||||
|
if !isV6 {
|
||||||
|
// localhost
|
||||||
|
localRule := make([]string, len(ruleBase), len(ruleBase)+4)
|
||||||
|
copy(localRule, ruleBase)
|
||||||
|
|
||||||
|
localRule = append(localRule,
|
||||||
|
"-s", "127.0.0.1",
|
||||||
|
"-j", setMarkChainName,
|
||||||
|
)
|
||||||
|
c.rules = append(c.rules, localRule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual dnat rule
|
||||||
|
dnatRule := make([]string, len(ruleBase), len(ruleBase)+4)
|
||||||
|
copy(dnatRule, ruleBase)
|
||||||
|
dnatRule = append(dnatRule,
|
||||||
"-j", "DNAT",
|
"-j", "DNAT",
|
||||||
"--to-destination", fmtIpPort(containerIP, entry.ContainerPort))
|
"--to-destination", fmtIpPort(containerIP, entry.ContainerPort),
|
||||||
|
)
|
||||||
out = append(out, rule)
|
c.rules = append(c.rules, dnatRule)
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// genToplevelSnatChain creates the top-level summary snat chain.
|
|
||||||
// IMPORTANT: do not change this, or else upgrading plugins will require
|
|
||||||
// manual intervention
|
|
||||||
func genToplevelSnatChain(isV6 bool) chain {
|
|
||||||
return chain{
|
|
||||||
table: "nat",
|
|
||||||
name: TopLevelSNATChainName,
|
|
||||||
entryRule: []string{
|
|
||||||
"-s", localhostIP(isV6),
|
|
||||||
"!", "-d", localhostIP(isV6),
|
|
||||||
},
|
|
||||||
entryChains: []string{"POSTROUTING"},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// genSnatChain creates the snat (localhost) chain for this container.
|
// genSetMarkChain creates the SETMARK chain - the chain that sets the
|
||||||
func genSnatChain(netName, containerID string) chain {
|
// "to-be-masqueraded" mark and returns.
|
||||||
name := formatChainName("SN-", netName, containerID)
|
// Chains are idempotent, so we'll always create this.
|
||||||
comment := fmt.Sprintf(`snat name: "%s" id: "%s"`, netName, containerID)
|
func genSetMarkChain(markBit int) chain {
|
||||||
|
markValue := 1 << uint(markBit)
|
||||||
return chain{
|
markDef := fmt.Sprintf("%#x/%#x", markValue, markValue)
|
||||||
|
ch := chain{
|
||||||
table: "nat",
|
table: "nat",
|
||||||
name: name,
|
name: SetMarkChainName,
|
||||||
entryRule: []string{
|
rules: [][]string{{
|
||||||
"-m", "comment",
|
"-m", "comment",
|
||||||
"--comment", comment,
|
"--comment", "CNI portfwd masquerade mark",
|
||||||
},
|
"-j", "MARK",
|
||||||
entryChains: []string{TopLevelSNATChainName},
|
"--set-xmark", markDef,
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// snatRules sets up masquerading for connections to localhost:hostport,
|
// genMarkMasqChain creates the chain that masquerades all packets marked
|
||||||
// rewriting the source so that returning packets are correct.
|
// in the SETMARK chain
|
||||||
func snatRules(entries []PortMapEntry, containerIP net.IP) [][]string {
|
func genMarkMasqChain(markBit int) chain {
|
||||||
isV6 := (containerIP.To4() == nil)
|
markValue := 1 << uint(markBit)
|
||||||
|
markDef := fmt.Sprintf("%#x/%#x", markValue, markValue)
|
||||||
out := make([][]string, 0, len(entries))
|
ch := chain{
|
||||||
for _, entry := range entries {
|
table: "nat",
|
||||||
out = append(out, []string{
|
name: MarkMasqChainName,
|
||||||
"-p", entry.Protocol,
|
entryChains: []string{"POSTROUTING"},
|
||||||
"-s", localhostIP(isV6),
|
entryRules: [][]string{{
|
||||||
"-d", containerIP.String(),
|
"-m", "comment",
|
||||||
"--dport", strconv.Itoa(entry.ContainerPort),
|
"--comment", "CNI portfwd requiring masquerade",
|
||||||
|
}},
|
||||||
|
rules: [][]string{{
|
||||||
|
"-m", "mark",
|
||||||
|
"--mark", markDef,
|
||||||
"-j", "MASQUERADE",
|
"-j", "MASQUERADE",
|
||||||
})
|
}},
|
||||||
}
|
}
|
||||||
return out
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
|
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
|
||||||
@ -234,6 +276,18 @@ func enableLocalnetRouting(ifName string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// genOldSnatChain is no longer used, but used to be created. We'll try and
|
||||||
|
// tear it down in case the plugin version changed between ADD and DEL
|
||||||
|
func genOldSnatChain(netName, containerID string) chain {
|
||||||
|
name := formatChainName("SN-", netName, containerID)
|
||||||
|
|
||||||
|
return chain{
|
||||||
|
table: "nat",
|
||||||
|
name: name,
|
||||||
|
entryChains: []string{OldTopLevelSNATChainName},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// unforwardPorts deletes any iptables rules created by this plugin.
|
// unforwardPorts deletes any iptables rules created by this plugin.
|
||||||
// It should be idempotent - it will not error if the chain does not exist.
|
// It should be idempotent - it will not error if the chain does not exist.
|
||||||
//
|
//
|
||||||
@ -245,8 +299,10 @@ func enableLocalnetRouting(ifName string) error {
|
|||||||
// So, we first check that iptables is "generally OK" by doing a check. If
|
// So, we first check that iptables is "generally OK" by doing a check. If
|
||||||
// not, we ignore the error, unless neither v4 nor v6 are OK.
|
// not, we ignore the error, unless neither v4 nor v6 are OK.
|
||||||
func unforwardPorts(config *PortMapConf) error {
|
func unforwardPorts(config *PortMapConf) error {
|
||||||
dnatChain := genDnatChain(config.Name, config.ContainerID, nil)
|
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||||
snatChain := genSnatChain(config.Name, config.ContainerID)
|
|
||||||
|
// Might be lying around from old versions
|
||||||
|
oldSnatChain := genOldSnatChain(config.Name, config.ContainerID)
|
||||||
|
|
||||||
ip4t := maybeGetIptables(false)
|
ip4t := maybeGetIptables(false)
|
||||||
ip6t := maybeGetIptables(true)
|
ip6t := maybeGetIptables(true)
|
||||||
@ -258,16 +314,14 @@ func unforwardPorts(config *PortMapConf) error {
|
|||||||
if err := dnatChain.teardown(ip4t); err != nil {
|
if err := dnatChain.teardown(ip4t); err != nil {
|
||||||
return fmt.Errorf("could not teardown ipv4 dnat: %v", err)
|
return fmt.Errorf("could not teardown ipv4 dnat: %v", err)
|
||||||
}
|
}
|
||||||
if err := snatChain.teardown(ip4t); err != nil {
|
oldSnatChain.teardown(ip4t)
|
||||||
return fmt.Errorf("could not teardown ipv4 snat: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip6t != nil {
|
if ip6t != nil {
|
||||||
if err := dnatChain.teardown(ip6t); err != nil {
|
if err := dnatChain.teardown(ip6t); err != nil {
|
||||||
return fmt.Errorf("could not teardown ipv6 dnat: %v", err)
|
return fmt.Errorf("could not teardown ipv6 dnat: %v", err)
|
||||||
}
|
}
|
||||||
// no SNAT teardown because it doesn't work for v6
|
oldSnatChain.teardown(ip6t)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"strconv"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/libcni"
|
"github.com/containernetworking/cni/libcni"
|
||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
@ -28,19 +30,20 @@ import (
|
|||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gexec"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TIMEOUT = 90
|
const TIMEOUT = 90
|
||||||
|
|
||||||
var _ = Describe("portmap integration tests", func() {
|
var _ = Describe("portmap integration tests", func() {
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
var (
|
||||||
|
configList *libcni.NetworkConfigList
|
||||||
var configList *libcni.NetworkConfigList
|
cniConf *libcni.CNIConfig
|
||||||
var cniConf *libcni.CNIConfig
|
targetNS ns.NetNS
|
||||||
var targetNS ns.NetNS
|
containerPort int
|
||||||
var containerPort int
|
session *gexec.Session
|
||||||
var closeChan chan interface{}
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
var err error
|
var err error
|
||||||
@ -80,12 +83,12 @@ var _ = Describe("portmap integration tests", func() {
|
|||||||
fmt.Fprintln(GinkgoWriter, "namespace:", targetNS.Path())
|
fmt.Fprintln(GinkgoWriter, "namespace:", targetNS.Path())
|
||||||
|
|
||||||
// Start an echo server and get the port
|
// Start an echo server and get the port
|
||||||
containerPort, closeChan, err = RunEchoServerInNS(targetNS)
|
containerPort, session, err = StartEchoServerInNamespace(targetNS)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
|
session.Terminate().Wait()
|
||||||
if targetNS != nil {
|
if targetNS != nil {
|
||||||
targetNS.Close()
|
targetNS.Close()
|
||||||
}
|
}
|
||||||
@ -123,13 +126,20 @@ var _ = Describe("portmap integration tests", func() {
|
|||||||
// we'll also manually check the iptables chains
|
// we'll also manually check the iptables chains
|
||||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID, nil).name
|
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
|
||||||
|
|
||||||
// Create the network
|
// Create the network
|
||||||
resI, err := cniConf.AddNetworkList(configList, &runtimeConfig)
|
resI, err := cniConf.AddNetworkList(configList, &runtimeConfig)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer deleteNetwork()
|
defer deleteNetwork()
|
||||||
|
|
||||||
|
// Undo Docker's forwarding policy
|
||||||
|
cmd := exec.Command("iptables", "-t", "filter",
|
||||||
|
"-P", "FORWARD", "ACCEPT")
|
||||||
|
cmd.Stderr = GinkgoWriter
|
||||||
|
err = cmd.Run()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// Check the chain exists
|
// Check the chain exists
|
||||||
_, err = ipt.List("nat", dnatChainName)
|
_, err = ipt.List("nat", dnatChainName)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
@ -154,16 +164,19 @@ var _ = Describe("portmap integration tests", func() {
|
|||||||
hostIP, hostPort, contIP, containerPort)
|
hostIP, hostPort, contIP, containerPort)
|
||||||
|
|
||||||
// Sanity check: verify that the container is reachable directly
|
// Sanity check: verify that the container is reachable directly
|
||||||
contOK := testEchoServer(fmt.Sprintf("%s:%d", contIP.String(), containerPort))
|
contOK := testEchoServer(contIP.String(), containerPort, "")
|
||||||
|
|
||||||
// Verify that a connection to the forwarded port works
|
// Verify that a connection to the forwarded port works
|
||||||
dnatOK := testEchoServer(fmt.Sprintf("%s:%d", hostIP, hostPort))
|
dnatOK := testEchoServer(hostIP, hostPort, "")
|
||||||
|
|
||||||
// Verify that a connection to localhost works
|
// Verify that a connection to localhost works
|
||||||
snatOK := testEchoServer(fmt.Sprintf("%s:%d", "127.0.0.1", hostPort))
|
snatOK := testEchoServer("127.0.0.1", hostPort, "")
|
||||||
|
|
||||||
|
// verify that hairpin works
|
||||||
|
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
close(closeChan)
|
session.Terminate()
|
||||||
err = deleteNetwork()
|
err = deleteNetwork()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
@ -181,6 +194,9 @@ var _ = Describe("portmap integration tests", func() {
|
|||||||
if !snatOK {
|
if !snatOK {
|
||||||
Fail("connection to 127.0.0.1 was not forwarded")
|
Fail("connection to 127.0.0.1 was not forwarded")
|
||||||
}
|
}
|
||||||
|
if !hairpinOK {
|
||||||
|
Fail("Hairpin connection failed")
|
||||||
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
|
|
||||||
@ -188,40 +204,33 @@ var _ = Describe("portmap integration tests", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// testEchoServer returns true if we found an echo server on the port
|
// testEchoServer returns true if we found an echo server on the port
|
||||||
func testEchoServer(address string) bool {
|
func testEchoServer(address string, port int, netns string) bool {
|
||||||
fmt.Fprintln(GinkgoWriter, "dialing", address)
|
|
||||||
conn, err := net.Dial("tcp", address)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(GinkgoWriter, "connection to", address, "failed:", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
conn.SetDeadline(time.Now().Add(TIMEOUT * time.Second))
|
|
||||||
fmt.Fprintln(GinkgoWriter, "connected to", address)
|
|
||||||
|
|
||||||
message := "Aliquid melius quam pessimum optimum non est."
|
message := "Aliquid melius quam pessimum optimum non est."
|
||||||
_, err = fmt.Fprint(conn, message)
|
|
||||||
|
bin, err := exec.LookPath("nc")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if netns != "" {
|
||||||
|
netns = filepath.Base(netns)
|
||||||
|
cmd = exec.Command("ip", "netns", "exec", netns, bin, "-v", address, strconv.Itoa(port))
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("nc", address, strconv.Itoa(port))
|
||||||
|
}
|
||||||
|
cmd.Stdin = bytes.NewBufferString(message)
|
||||||
|
cmd.Stderr = GinkgoWriter
|
||||||
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(GinkgoWriter, "sending message to", address, " failed:", err)
|
fmt.Fprintln(GinkgoWriter, "got non-zero exit from ", cmd.Args)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.SetDeadline(time.Now().Add(TIMEOUT * time.Second))
|
if string(out) != message {
|
||||||
fmt.Fprintln(GinkgoWriter, "reading...")
|
fmt.Fprintln(GinkgoWriter, "returned message didn't match?")
|
||||||
response := make([]byte, len(message))
|
fmt.Fprintln(GinkgoWriter, string(out))
|
||||||
_, err = conn.Read(response)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(GinkgoWriter, "receiving message from", address, " failed:", err)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(GinkgoWriter, "read...")
|
return true
|
||||||
if string(response) == message {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
fmt.Fprintln(GinkgoWriter, "returned message didn't match?")
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalIP() string {
|
func getLocalIP() string {
|
||||||
|
@ -15,89 +15,64 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
|
"github.com/onsi/ginkgo/config"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/onsi/gomega/gexec"
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPortmap(t *testing.T) {
|
func TestPortmap(t *testing.T) {
|
||||||
|
rand.Seed(config.GinkgoConfig.RandomSeed)
|
||||||
|
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "portmap Suite")
|
RunSpecs(t, "portmap Suite")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenEchoServer opens a server that listens until closeChan is closed.
|
var echoServerBinaryPath string
|
||||||
// It opens on a random port and sends the port number on portChan when
|
|
||||||
// the server is up and running. If an error is encountered, closes portChan.
|
|
||||||
// If closeChan is closed, closes the socket.
|
|
||||||
func OpenEchoServer(portChan chan<- int, closeChan <-chan interface{}) error {
|
|
||||||
laddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:0")
|
|
||||||
if err != nil {
|
|
||||||
close(portChan)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sock, err := net.ListenTCP("tcp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
close(portChan)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sock.Close()
|
|
||||||
|
|
||||||
switch addr := sock.Addr().(type) {
|
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||||
case *net.TCPAddr:
|
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
|
||||||
portChan <- addr.Port
|
Expect(err).NotTo(HaveOccurred())
|
||||||
default:
|
return []byte(binaryPath)
|
||||||
close(portChan)
|
}, func(data []byte) {
|
||||||
return fmt.Errorf("addr cast failed!")
|
echoServerBinaryPath = string(data)
|
||||||
}
|
})
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-closeChan:
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
sock.SetDeadline(time.Now().Add(time.Second))
|
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||||
con, err := sock.AcceptTCP()
|
gexec.CleanupBuildArtifacts()
|
||||||
if err != nil {
|
})
|
||||||
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 512)
|
func startInNetNS(binPath string, netNS ns.NetNS) (*gexec.Session, error) {
|
||||||
con.Read(buf)
|
baseName := filepath.Base(netNS.Path())
|
||||||
con.Write(buf)
|
// we are relying on the netNS path living in /var/run/netns
|
||||||
con.Close()
|
// where `ip netns exec` can find it
|
||||||
}
|
cmd := exec.Command("ip", "netns", "exec", baseName, binPath)
|
||||||
|
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||||
|
return session, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunEchoServerInNS(netNS ns.NetNS) (int, chan interface{}, error) {
|
func StartEchoServerInNamespace(netNS ns.NetNS) (int, *gexec.Session, error) {
|
||||||
portChan := make(chan int)
|
session, err := startInNetNS(echoServerBinaryPath, netNS)
|
||||||
closeChan := make(chan interface{})
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
go func() {
|
// wait for it to print it's address on stdout
|
||||||
err := netNS.Do(func(ns.NetNS) error {
|
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||||
OpenEchoServer(portChan, closeChan)
|
_, portString, err := net.SplitHostPort(strings.TrimSpace(string(session.Out.Contents())))
|
||||||
return nil
|
Expect(err).NotTo(HaveOccurred())
|
||||||
})
|
|
||||||
// Somehow the ns.Do failed
|
|
||||||
if err != nil {
|
|
||||||
close(portChan)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
portNum := <-portChan
|
port, err := strconv.Atoi(portString)
|
||||||
if portNum == 0 {
|
Expect(err).NotTo(HaveOccurred())
|
||||||
return 0, nil, fmt.Errorf("failed to execute server")
|
return port, session, nil
|
||||||
}
|
|
||||||
|
|
||||||
return portNum, closeChan, nil
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
@ -25,13 +26,6 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
netName := "testNetName"
|
netName := "testNetName"
|
||||||
containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y"
|
containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y"
|
||||||
|
|
||||||
mappings := []PortMapEntry{
|
|
||||||
{80, 90, "tcp", ""},
|
|
||||||
{1000, 2000, "udp", ""},
|
|
||||||
}
|
|
||||||
ipv4addr := net.ParseIP("192.2.0.1")
|
|
||||||
ipv6addr := net.ParseIP("2001:db8::1")
|
|
||||||
|
|
||||||
Context("config parsing", func() {
|
Context("config parsing", func() {
|
||||||
It("Correctly parses an ADD config", func() {
|
It("Correctly parses an ADD config", func() {
|
||||||
configBytes := []byte(`{
|
configBytes := []byte(`{
|
||||||
@ -156,101 +150,179 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
|
|
||||||
Describe("Generating chains", func() {
|
Describe("Generating chains", func() {
|
||||||
Context("for DNAT", func() {
|
Context("for DNAT", func() {
|
||||||
It("generates a correct container chain", func() {
|
It("generates a correct standard container chain", func() {
|
||||||
ch := genDnatChain(netName, containerID, &[]string{"-m", "hello"})
|
ch := genDnatChain(netName, containerID)
|
||||||
|
|
||||||
Expect(ch).To(Equal(chain{
|
Expect(ch).To(Equal(chain{
|
||||||
table: "nat",
|
table: "nat",
|
||||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||||
entryRule: []string{
|
|
||||||
"-m", "comment",
|
|
||||||
"--comment", `dnat name: "testNetName" id: "` + containerID + `"`,
|
|
||||||
"-m", "hello",
|
|
||||||
},
|
|
||||||
entryChains: []string{TopLevelDNATChainName},
|
entryChains: []string{TopLevelDNATChainName},
|
||||||
}))
|
}))
|
||||||
|
configBytes := []byte(`{
|
||||||
|
"name": "test",
|
||||||
|
"type": "portmap",
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"runtimeConfig": {
|
||||||
|
"portMappings": [
|
||||||
|
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||||
|
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||||
|
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||||
|
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"snat": true,
|
||||||
|
"conditionsV4": ["a", "b"],
|
||||||
|
"conditionsV6": ["c", "d"]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
conf, err := parseConfig(configBytes, "foo")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
conf.ContainerID = containerID
|
||||||
|
|
||||||
|
ch = genDnatChain(conf.Name, containerID)
|
||||||
|
Expect(ch).To(Equal(chain{
|
||||||
|
table: "nat",
|
||||||
|
name: "CNI-DN-67e92b96e692a494b6b85",
|
||||||
|
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||||
|
|
||||||
|
Expect(ch.entryRules).To(Equal([][]string{
|
||||||
|
{"-m", "comment", "--comment",
|
||||||
|
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||||
|
"-m", "multiport",
|
||||||
|
"-p", "tcp",
|
||||||
|
"--destination-ports", "8080,8081",
|
||||||
|
"a", "b"},
|
||||||
|
{"-m", "comment", "--comment",
|
||||||
|
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||||
|
"-m", "multiport",
|
||||||
|
"-p", "udp",
|
||||||
|
"--destination-ports", "8080,8082",
|
||||||
|
"a", "b"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
|
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
|
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||||
|
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
ch.rules = nil
|
||||||
|
ch.entryRules = nil
|
||||||
|
|
||||||
|
fillDnatRules(&ch, conf, net.ParseIP("2001:db8::2"))
|
||||||
|
|
||||||
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||||
|
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||||
|
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
||||||
|
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
|
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Disable snat, generate rules
|
||||||
|
ch.rules = nil
|
||||||
|
ch.entryRules = nil
|
||||||
|
fvar := false
|
||||||
|
conf.SNAT = &fvar
|
||||||
|
|
||||||
|
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||||
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
|
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
|
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||||
|
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("generates a correct chain with external mark", func() {
|
||||||
|
ch := genDnatChain(netName, containerID)
|
||||||
|
|
||||||
|
Expect(ch).To(Equal(chain{
|
||||||
|
table: "nat",
|
||||||
|
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||||
|
entryChains: []string{TopLevelDNATChainName},
|
||||||
|
}))
|
||||||
|
configBytes := []byte(`{
|
||||||
|
"name": "test",
|
||||||
|
"type": "portmap",
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"runtimeConfig": {
|
||||||
|
"portMappings": [
|
||||||
|
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"externalSetMarkChain": "PLZ-SET-MARK",
|
||||||
|
"conditionsV4": ["a", "b"],
|
||||||
|
"conditionsV6": ["c", "d"]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
conf, err := parseConfig(configBytes, "foo")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
conf.ContainerID = containerID
|
||||||
|
|
||||||
|
ch = genDnatChain(conf.Name, containerID)
|
||||||
|
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||||
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "PLZ-SET-MARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
||||||
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("generates a correct top-level chain", func() {
|
It("generates a correct top-level chain", func() {
|
||||||
ch := genToplevelDnatChain()
|
ch := genToplevelDnatChain()
|
||||||
|
|
||||||
Expect(ch).To(Equal(chain{
|
Expect(ch).To(Equal(chain{
|
||||||
table: "nat",
|
table: "nat",
|
||||||
name: "CNI-HOSTPORT-DNAT",
|
name: "CNI-HOSTPORT-DNAT",
|
||||||
entryRule: []string{
|
|
||||||
"-m", "addrtype",
|
|
||||||
"--dst-type", "LOCAL",
|
|
||||||
},
|
|
||||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||||
|
entryRules: [][]string{{"-m", "addrtype", "--dst-type", "LOCAL"}},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Context("for SNAT", func() {
|
|
||||||
It("generates a correct container chain", func() {
|
|
||||||
ch := genSnatChain(netName, containerID)
|
|
||||||
|
|
||||||
|
It("generates the correct mark chains", func() {
|
||||||
|
masqBit := 5
|
||||||
|
ch := genSetMarkChain(masqBit)
|
||||||
Expect(ch).To(Equal(chain{
|
Expect(ch).To(Equal(chain{
|
||||||
table: "nat",
|
table: "nat",
|
||||||
name: "CNI-SN-bfd599665540dd91d5d28",
|
name: "CNI-HOSTPORT-SETMARK",
|
||||||
entryRule: []string{
|
rules: [][]string{{
|
||||||
"-m", "comment",
|
"-m", "comment",
|
||||||
"--comment", `snat name: "testNetName" id: "` + containerID + `"`,
|
"--comment", "CNI portfwd masquerade mark",
|
||||||
},
|
"-j", "MARK",
|
||||||
entryChains: []string{TopLevelSNATChainName},
|
"--set-xmark", "0x20/0x20",
|
||||||
|
}},
|
||||||
}))
|
}))
|
||||||
})
|
|
||||||
|
|
||||||
It("generates a correct top-level chain", func() {
|
ch = genMarkMasqChain(masqBit)
|
||||||
Context("for ipv4", func() {
|
Expect(ch).To(Equal(chain{
|
||||||
ch := genToplevelSnatChain(false)
|
table: "nat",
|
||||||
Expect(ch).To(Equal(chain{
|
name: "CNI-HOSTPORT-MASQ",
|
||||||
table: "nat",
|
entryChains: []string{"POSTROUTING"},
|
||||||
name: "CNI-HOSTPORT-SNAT",
|
entryRules: [][]string{{
|
||||||
entryRule: []string{
|
"-m", "comment",
|
||||||
"-s", "127.0.0.1",
|
"--comment", "CNI portfwd requiring masquerade",
|
||||||
"!", "-d", "127.0.0.1",
|
}},
|
||||||
},
|
rules: [][]string{{
|
||||||
entryChains: []string{"POSTROUTING"},
|
"-m", "mark",
|
||||||
}))
|
"--mark", "0x20/0x20",
|
||||||
})
|
"-j", "MASQUERADE",
|
||||||
})
|
}},
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("Forwarding rules", func() {
|
|
||||||
Context("for DNAT", func() {
|
|
||||||
It("generates correct ipv4 rules", func() {
|
|
||||||
rules := dnatRules(mappings, ipv4addr)
|
|
||||||
Expect(rules).To(Equal([][]string{
|
|
||||||
{"-p", "tcp", "--dport", "80", "-j", "DNAT", "--to-destination", "192.2.0.1:90"},
|
|
||||||
{"-p", "udp", "--dport", "1000", "-j", "DNAT", "--to-destination", "192.2.0.1:2000"},
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
It("generates correct ipv6 rules", func() {
|
|
||||||
rules := dnatRules(mappings, ipv6addr)
|
|
||||||
Expect(rules).To(Equal([][]string{
|
|
||||||
{"-p", "tcp", "--dport", "80", "-j", "DNAT", "--to-destination", "[2001:db8::1]:90"},
|
|
||||||
{"-p", "udp", "--dport", "1000", "-j", "DNAT", "--to-destination", "[2001:db8::1]:2000"},
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("for SNAT", func() {
|
|
||||||
|
|
||||||
It("generates correct ipv4 rules", func() {
|
|
||||||
rules := snatRules(mappings, ipv4addr)
|
|
||||||
Expect(rules).To(Equal([][]string{
|
|
||||||
{"-p", "tcp", "-s", "127.0.0.1", "-d", "192.2.0.1", "--dport", "90", "-j", "MASQUERADE"},
|
|
||||||
{"-p", "udp", "-s", "127.0.0.1", "-d", "192.2.0.1", "--dport", "2000", "-j", "MASQUERADE"},
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("generates correct ipv6 rules", func() {
|
|
||||||
rules := snatRules(mappings, ipv6addr)
|
|
||||||
Expect(rules).To(Equal([][]string{
|
|
||||||
{"-p", "tcp", "-s", "::1", "-d", "2001:db8::1", "--dport", "90", "-j", "MASQUERADE"},
|
|
||||||
{"-p", "udp", "-s", "::1", "-d", "2001:db8::1", "--dport", "2000", "-j", "MASQUERADE"},
|
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -18,6 +18,8 @@ import (
|
|||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
@ -65,3 +67,51 @@ func formatChainName(prefix, name, id string) string {
|
|||||||
chain := fmt.Sprintf("CNI-%s%x", prefix, chainBytes)
|
chain := fmt.Sprintf("CNI-%s%x", prefix, chainBytes)
|
||||||
return chain[:maxChainNameLength]
|
return chain[:maxChainNameLength]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// groupByProto groups port numbers by protocol
|
||||||
|
func groupByProto(entries []PortMapEntry) map[string][]int {
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return map[string][]int{}
|
||||||
|
}
|
||||||
|
out := map[string][]int{}
|
||||||
|
for _, e := range entries {
|
||||||
|
_, ok := out[e.Protocol]
|
||||||
|
if ok {
|
||||||
|
out[e.Protocol] = append(out[e.Protocol], e.HostPort)
|
||||||
|
} else {
|
||||||
|
out[e.Protocol] = []int{e.HostPort}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitPortList splits a list of integers in to one or more comma-separated
|
||||||
|
// string values, for use by multiport. Multiport only allows up to 15 ports
|
||||||
|
// per entry.
|
||||||
|
func splitPortList(l []int) []string {
|
||||||
|
out := []string{}
|
||||||
|
|
||||||
|
acc := []string{}
|
||||||
|
for _, i := range l {
|
||||||
|
acc = append(acc, strconv.Itoa(i))
|
||||||
|
if len(acc) == 15 {
|
||||||
|
out = append(out, strings.Join(acc, ","))
|
||||||
|
acc = []string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(acc) > 0 {
|
||||||
|
out = append(out, strings.Join(acc, ","))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// trimComment makes sure no comment is over the iptables limit of 255 chars
|
||||||
|
func trimComment(val string) string {
|
||||||
|
if len(val) <= 255 {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return val[0:253] + "..."
|
||||||
|
}
|
||||||
|
@ -12,12 +12,14 @@ OUTPUT_DIR=bin
|
|||||||
# Always clean first
|
# Always clean first
|
||||||
rm -Rf ${SRC_DIR}/${RELEASE_DIR}
|
rm -Rf ${SRC_DIR}/${RELEASE_DIR}
|
||||||
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
|
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
|
||||||
|
mkdir -p ${OUTPUT_DIR}
|
||||||
|
|
||||||
docker run -i -v ${SRC_DIR}:/opt/src --rm golang:1.8-alpine \
|
docker run -i -v ${SRC_DIR}:/opt/src --rm golang:1.9-alpine \
|
||||||
/bin/sh -xe -c "\
|
/bin/sh -xe -c "\
|
||||||
apk --no-cache add bash tar;
|
apk --no-cache add bash tar;
|
||||||
cd /opt/src; umask 0022;
|
cd /opt/src; umask 0022;
|
||||||
for arch in amd64 arm arm64 ppc64le s390x; do \
|
for arch in amd64 arm arm64 ppc64le s390x; do \
|
||||||
|
rm -f ${OUTPUT_DIR}/*; \
|
||||||
CGO_ENABLED=0 GOARCH=\$arch ./build.sh ${BUILDFLAGS}; \
|
CGO_ENABLED=0 GOARCH=\$arch ./build.sh ${BUILDFLAGS}; \
|
||||||
for format in tgz; do \
|
for format in tgz; do \
|
||||||
FILENAME=cni-plugins-\$arch-${TAG}.\$format; \
|
FILENAME=cni-plugins-\$arch-${TAG}.\$format; \
|
||||||
|
30
test.sh
30
test.sh
@ -10,36 +10,36 @@ source ./build.sh
|
|||||||
|
|
||||||
echo "Running tests"
|
echo "Running tests"
|
||||||
|
|
||||||
TESTABLE="plugins/ipam/dhcp plugins/ipam/host-local plugins/ipam/host-local/backend/allocator plugins/main/loopback plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/meta/flannel plugins/main/vlan plugins/sample pkg/ip pkg/ipam pkg/ns pkg/utils pkg/utils/hwaddr pkg/utils/sysctl plugins/meta/portmap"
|
# test everything that's not in vendor
|
||||||
|
pushd "$GOPATH/src/$REPO_PATH" >/dev/null
|
||||||
|
ALL_PKGS="$(go list ./... | grep -v vendor | xargs echo)"
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
GINKGO_FLAGS="-p --randomizeAllSpecs --randomizeSuites --failOnPending --progress"
|
||||||
|
|
||||||
# user has not provided PKG override
|
# user has not provided PKG override
|
||||||
if [ -z "$PKG" ]; then
|
if [ -z "$PKG" ]; then
|
||||||
TEST=$TESTABLE
|
GINKGO_FLAGS="$GINKGO_FLAGS -r ."
|
||||||
FMT=$TESTABLE
|
LINT_TARGETS="$ALL_PKGS"
|
||||||
|
|
||||||
# user has provided PKG override
|
# user has provided PKG override
|
||||||
else
|
else
|
||||||
TEST=$PKG
|
GINKGO_FLAGS="$GINKGO_FLAGS $PKG"
|
||||||
|
LINT_TARGETS="$PKG"
|
||||||
# only run gofmt on packages provided by user
|
|
||||||
FMT="$TEST"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# split TEST into an array and prepend REPO_PATH to each local package
|
cd "$GOPATH/src/$REPO_PATH"
|
||||||
split=(${TEST// / })
|
sudo -E bash -c "umask 0; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} ginkgo ${GINKGO_FLAGS}"
|
||||||
TEST=${split[@]/#/${REPO_PATH}/}
|
|
||||||
|
|
||||||
sudo -E bash -c "umask 0; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test ${TEST}"
|
|
||||||
|
|
||||||
echo "Checking gofmt..."
|
echo "Checking gofmt..."
|
||||||
fmtRes=$(gofmt -l $FMT)
|
fmtRes=$(go fmt $LINT_TARGETS)
|
||||||
if [ -n "${fmtRes}" ]; then
|
if [ -n "${fmtRes}" ]; then
|
||||||
echo -e "gofmt checking failed:\n${fmtRes}"
|
echo -e "go fmt checking failed:\n${fmtRes}"
|
||||||
exit 255
|
exit 255
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Checking govet..."
|
echo "Checking govet..."
|
||||||
vetRes=$(go vet $TEST)
|
vetRes=$(go vet $LINT_TARGETS)
|
||||||
if [ -n "${vetRes}" ]; then
|
if [ -n "${vetRes}" ]; then
|
||||||
echo -e "govet checking failed:\n${vetRes}"
|
echo -e "govet checking failed:\n${vetRes}"
|
||||||
exit 255
|
exit 255
|
||||||
|
21
vendor/github.com/alexflint/go-filemutex/LICENSE
generated
vendored
Normal file
21
vendor/github.com/alexflint/go-filemutex/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2017 Alex Flint.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
31
vendor/github.com/alexflint/go-filemutex/README.md
generated
vendored
Normal file
31
vendor/github.com/alexflint/go-filemutex/README.md
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# FileMutex
|
||||||
|
|
||||||
|
FileMutex is similar to `sync.RWMutex`, but also synchronizes across processes.
|
||||||
|
On Linux, OSX, and other POSIX systems it uses the flock system call. On windows
|
||||||
|
it uses the LockFileEx and UnlockFileEx system calls.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"github.com/alexflint/go-filemutex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m, err := filemutex.New("/tmp/foo.lock")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Directory did not exist or file could not created")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Lock() // Will block until lock can be acquired
|
||||||
|
|
||||||
|
// Code here is protected by the mutex
|
||||||
|
|
||||||
|
m.Unlock()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
go get github.com/alexflint/go-filemutex
|
||||||
|
|
||||||
|
Forked from https://github.com/golang/build/tree/master/cmd/builder/filemutex_*.go
|
67
vendor/github.com/alexflint/go-filemutex/filemutex_flock.go
generated
vendored
Normal file
67
vendor/github.com/alexflint/go-filemutex/filemutex_flock.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2013 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.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||||
|
|
||||||
|
package filemutex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mkdirPerm = 0750
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileMutex is similar to sync.RWMutex, but also synchronizes across processes.
|
||||||
|
// This implementation is based on flock syscall.
|
||||||
|
type FileMutex struct {
|
||||||
|
fd int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(filename string) (*FileMutex, error) {
|
||||||
|
fd, err := syscall.Open(filename, syscall.O_CREAT|syscall.O_RDONLY, mkdirPerm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &FileMutex{fd: fd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) Lock() error {
|
||||||
|
if err := syscall.Flock(m.fd, syscall.LOCK_EX); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) Unlock() error {
|
||||||
|
if err := syscall.Flock(m.fd, syscall.LOCK_UN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) RLock() error {
|
||||||
|
if err := syscall.Flock(m.fd, syscall.LOCK_SH); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) RUnlock() error {
|
||||||
|
if err := syscall.Flock(m.fd, syscall.LOCK_UN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close does an Unlock() combined with closing and unlinking the associated
|
||||||
|
// lock file. You should create a New() FileMutex for every Lock() attempt if
|
||||||
|
// using Close().
|
||||||
|
func (m *FileMutex) Close() error {
|
||||||
|
if err := syscall.Flock(m.fd, syscall.LOCK_UN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.Close(m.fd)
|
||||||
|
}
|
102
vendor/github.com/alexflint/go-filemutex/filemutex_windows.go
generated
vendored
Normal file
102
vendor/github.com/alexflint/go-filemutex/filemutex_windows.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2013 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 filemutex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
procLockFileEx = modkernel32.NewProc("LockFileEx")
|
||||||
|
procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lockfileExclusiveLock = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procUnlockFileEx.Addr(), 5, uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileMutex is similar to sync.RWMutex, but also synchronizes across processes.
|
||||||
|
// This implementation is based on flock syscall.
|
||||||
|
type FileMutex struct {
|
||||||
|
fd syscall.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(filename string) (*FileMutex, error) {
|
||||||
|
fd, err := syscall.CreateFile(&(syscall.StringToUTF16(filename)[0]), syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||||
|
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, nil, syscall.OPEN_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &FileMutex{fd: fd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) Lock() error {
|
||||||
|
var ol syscall.Overlapped
|
||||||
|
if err := lockFileEx(m.fd, lockfileExclusiveLock, 0, 1, 0, &ol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) Unlock() error {
|
||||||
|
var ol syscall.Overlapped
|
||||||
|
if err := unlockFileEx(m.fd, 0, 1, 0, &ol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) RLock() error {
|
||||||
|
var ol syscall.Overlapped
|
||||||
|
if err := lockFileEx(m.fd, 0, 0, 1, 0, &ol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileMutex) RUnlock() error {
|
||||||
|
var ol syscall.Overlapped
|
||||||
|
if err := unlockFileEx(m.fd, 0, 1, 0, &ol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close does an Unlock() combined with closing and unlinking the associated
|
||||||
|
// lock file. You should create a New() FileMutex for every Lock() attempt if
|
||||||
|
// using Close().
|
||||||
|
func (m *FileMutex) Close() error {
|
||||||
|
var ol syscall.Overlapped
|
||||||
|
if err := unlockFileEx(m.fd, 0, 1, 0, &ol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.Close(m.fd)
|
||||||
|
}
|
354
vendor/github.com/d2g/dhcp4server/LICENSE
generated
vendored
Normal file
354
vendor/github.com/d2g/dhcp4server/LICENSE
generated
vendored
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
Mozilla Public License, version 2.0
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
1.1. “Contributor”
|
||||||
|
|
||||||
|
means each individual or legal entity that creates, contributes to the
|
||||||
|
creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. “Contributor Version”
|
||||||
|
|
||||||
|
means the combination of the Contributions of others (if any) used by a
|
||||||
|
Contributor and that particular Contributor’s Contribution.
|
||||||
|
|
||||||
|
1.3. “Contribution”
|
||||||
|
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. “Covered Software”
|
||||||
|
|
||||||
|
means Source Code Form to which the initial Contributor has attached the
|
||||||
|
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||||
|
Modifications of such Source Code Form, in each case including portions
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
1.5. “Incompatible With Secondary Licenses”
|
||||||
|
means
|
||||||
|
|
||||||
|
a. that the initial Contributor has attached the notice described in
|
||||||
|
Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
b. that the Covered Software was made available under the terms of version
|
||||||
|
1.1 or earlier of the License, but not also under the terms of a
|
||||||
|
Secondary License.
|
||||||
|
|
||||||
|
1.6. “Executable Form”
|
||||||
|
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. “Larger Work”
|
||||||
|
|
||||||
|
means a work that combines Covered Software with other material, in a separate
|
||||||
|
file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. “License”
|
||||||
|
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. “Licensable”
|
||||||
|
|
||||||
|
means having the right to grant, to the maximum extent possible, whether at the
|
||||||
|
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||||
|
this License.
|
||||||
|
|
||||||
|
1.10. “Modifications”
|
||||||
|
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
a. any file in Source Code Form that results from an addition to, deletion
|
||||||
|
from, or modification of the contents of Covered Software; or
|
||||||
|
|
||||||
|
b. any new file in Source Code Form that contains any Covered Software.
|
||||||
|
|
||||||
|
1.11. “Patent Claims” of a Contributor
|
||||||
|
|
||||||
|
means any patent claim(s), including without limitation, method, process,
|
||||||
|
and apparatus claims, in any patent Licensable by such Contributor that
|
||||||
|
would be infringed, but for the grant of the License, by the making,
|
||||||
|
using, selling, offering for sale, having made, import, or transfer of
|
||||||
|
either its Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
1.12. “Secondary License”
|
||||||
|
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||||
|
General Public License, Version 2.1, the GNU Affero General Public
|
||||||
|
License, Version 3.0, or any later versions of those licenses.
|
||||||
|
|
||||||
|
1.13. “Source Code Form”
|
||||||
|
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. “You” (or “Your”)
|
||||||
|
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, “You” includes any entity that controls, is
|
||||||
|
controlled by, or is under common control with You. For purposes of this
|
||||||
|
definition, “control” means (a) the power, direct or indirect, to cause
|
||||||
|
the direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||||
|
outstanding shares or beneficial ownership of such entity.
|
||||||
|
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
a. under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or as
|
||||||
|
part of a Larger Work; and
|
||||||
|
|
||||||
|
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||||
|
sale, have made, import, and otherwise transfer either its Contributions
|
||||||
|
or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||||
|
effective for each Contribution on the date the Contributor first distributes
|
||||||
|
such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under this
|
||||||
|
License. No additional rights or licenses will be implied from the distribution
|
||||||
|
or licensing of Covered Software under this License. Notwithstanding Section
|
||||||
|
2.1(b) above, no patent license is granted by a Contributor:
|
||||||
|
|
||||||
|
a. for any code that a Contributor has removed from Covered Software; or
|
||||||
|
|
||||||
|
b. for infringements caused by: (i) Your and any other third party’s
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||||
|
Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks, or
|
||||||
|
logos of any Contributor (except as may be necessary to comply with the
|
||||||
|
notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this License
|
||||||
|
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||||
|
under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its Contributions
|
||||||
|
are its original creation(s) or it has sufficient rights to grant the
|
||||||
|
rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under applicable
|
||||||
|
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||||
|
Section 2.1.
|
||||||
|
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under the
|
||||||
|
terms of this License. You must inform recipients that the Source Code Form
|
||||||
|
of the Covered Software is governed by the terms of this License, and how
|
||||||
|
they can obtain a copy of this License. You may not attempt to alter or
|
||||||
|
restrict the recipients’ rights in the Source Code Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
a. such Covered Software must also be made available in Source Code Form,
|
||||||
|
as described in Section 3.1, and You must inform recipients of the
|
||||||
|
Executable Form how they can obtain a copy of such Source Code Form by
|
||||||
|
reasonable means in a timely manner, at a charge no more than the cost
|
||||||
|
of distribution to the recipient; and
|
||||||
|
|
||||||
|
b. You may distribute such Executable Form under the terms of this License,
|
||||||
|
or sublicense it under different terms, provided that the license for
|
||||||
|
the Executable Form does not attempt to limit or alter the recipients’
|
||||||
|
rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for the
|
||||||
|
Covered Software. If the Larger Work is a combination of Covered Software
|
||||||
|
with a work governed by one or more Secondary Licenses, and the Covered
|
||||||
|
Software is not Incompatible With Secondary Licenses, this License permits
|
||||||
|
You to additionally distribute such Covered Software under the terms of
|
||||||
|
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||||
|
their option, further distribute the Covered Software under the terms of
|
||||||
|
either this License or such Secondary License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices (including
|
||||||
|
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||||
|
of liability) contained within the Source Code Form of the Covered
|
||||||
|
Software, except that You may alter any license notices to the extent
|
||||||
|
required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||||
|
of any Contributor. You must make it absolutely clear that any such
|
||||||
|
warranty, support, indemnity, or liability obligation is offered by You
|
||||||
|
alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this License
|
||||||
|
with respect to some or all of the Covered Software due to statute, judicial
|
||||||
|
order, or regulation then You must: (a) comply with the terms of this License
|
||||||
|
to the maximum extent possible; and (b) describe the limitations and the code
|
||||||
|
they affect. Such description must be placed in a text file included with all
|
||||||
|
distributions of the Covered Software under this License. Except to the
|
||||||
|
extent prohibited by statute or regulation, such description must be
|
||||||
|
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||||
|
understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically if You
|
||||||
|
fail to comply with any of its terms. However, if You become compliant,
|
||||||
|
then the rights granted under this License from a particular Contributor
|
||||||
|
are reinstated (a) provisionally, unless and until such Contributor
|
||||||
|
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||||
|
if such Contributor fails to notify You of the non-compliance by some
|
||||||
|
reasonable means prior to 60 days after You have come back into compliance.
|
||||||
|
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||||
|
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||||
|
some reasonable means, this is the first time You have received notice of
|
||||||
|
non-compliance with this License from such Contributor, and You become
|
||||||
|
compliant prior to 30 days after Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||||
|
and cross-claims) alleging that a Contributor Version directly or
|
||||||
|
indirectly infringes any patent, then the rights granted to You by any and
|
||||||
|
all Contributors for the Covered Software under Section 2.1 of this License
|
||||||
|
shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||||
|
license agreements (excluding distributors and resellers) which have been
|
||||||
|
validly granted by You or Your distributors under this License prior to
|
||||||
|
termination shall survive termination.
|
||||||
|
|
||||||
|
6. Disclaimer of Warranty
|
||||||
|
|
||||||
|
Covered Software is provided under this License on an “as is” basis, without
|
||||||
|
warranty of any kind, either expressed, implied, or statutory, including,
|
||||||
|
without limitation, warranties that the Covered Software is free of defects,
|
||||||
|
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||||
|
risk as to the quality and performance of the Covered Software is with You.
|
||||||
|
Should any Covered Software prove defective in any respect, You (not any
|
||||||
|
Contributor) assume the cost of any necessary servicing, repair, or
|
||||||
|
correction. This disclaimer of warranty constitutes an essential part of this
|
||||||
|
License. No use of any Covered Software is authorized under this License
|
||||||
|
except under this disclaimer.
|
||||||
|
|
||||||
|
7. Limitation of Liability
|
||||||
|
|
||||||
|
Under no circumstances and under no legal theory, whether tort (including
|
||||||
|
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||||
|
distributes Covered Software as permitted above, be liable to You for any
|
||||||
|
direct, indirect, special, incidental, or consequential damages of any
|
||||||
|
character including, without limitation, damages for lost profits, loss of
|
||||||
|
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses, even if such party shall have been
|
||||||
|
informed of the possibility of such damages. This limitation of liability
|
||||||
|
shall not apply to liability for death or personal injury resulting from such
|
||||||
|
party’s negligence to the extent applicable law prohibits such limitation.
|
||||||
|
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||||
|
consequential damages, so this exclusion and limitation may not apply to You.
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the courts of
|
||||||
|
a jurisdiction where the defendant maintains its principal place of business
|
||||||
|
and such litigation shall be governed by laws of that jurisdiction, without
|
||||||
|
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||||
|
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject matter
|
||||||
|
hereof. If any provision of this License is held to be unenforceable, such
|
||||||
|
provision shall be reformed only to the extent necessary to make it
|
||||||
|
enforceable. Any law or regulation which provides that the language of a
|
||||||
|
contract shall be construed against the drafter shall not be used to construe
|
||||||
|
this License against a Contributor.
|
||||||
|
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version of
|
||||||
|
the License under which You originally received the Covered Software, or
|
||||||
|
under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a modified
|
||||||
|
version of this License if you rename the license and remove any
|
||||||
|
references to the name of the license steward (except to note that such
|
||||||
|
modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular file, then
|
||||||
|
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||||
|
directory) where a recipient would be likely to look for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||||
|
|
||||||
|
This Source Code Form is “Incompatible
|
||||||
|
With Secondary Licenses”, as defined by
|
||||||
|
the Mozilla Public License, v. 2.0.
|
||||||
|
|
4
vendor/github.com/d2g/dhcp4server/README.md
generated
vendored
Normal file
4
vendor/github.com/d2g/dhcp4server/README.md
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
dhcp4server [](http://godoc.org/github.com/d2g/dhcp4server) [](https://coveralls.io/r/d2g/dhcp4server) [](https://codeship.com/projects/59804)
|
||||||
|
===========
|
||||||
|
|
||||||
|
DHCP Server
|
95
vendor/github.com/d2g/dhcp4server/leasepool/lease.go
generated
vendored
Normal file
95
vendor/github.com/d2g/dhcp4server/leasepool/lease.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package leasepool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LeaseStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Free LeaseStatus = 0
|
||||||
|
Reserved LeaseStatus = 1
|
||||||
|
Active LeaseStatus = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lease struct {
|
||||||
|
IP net.IP //The IP of the Lease
|
||||||
|
Status LeaseStatus //Are Reserved, Active or Free
|
||||||
|
MACAddress net.HardwareAddr //Mac Address of the Device
|
||||||
|
Hostname string //Hostname From option 12
|
||||||
|
Expiry time.Time //Expiry Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Lease) MarshalJSON() ([]byte, error) {
|
||||||
|
stringMarshal := struct {
|
||||||
|
IP string
|
||||||
|
Status int
|
||||||
|
MACAddress string
|
||||||
|
Hostname string
|
||||||
|
Expiry time.Time
|
||||||
|
}{
|
||||||
|
(this.IP.String()),
|
||||||
|
int(this.Status),
|
||||||
|
(this.MACAddress.String()),
|
||||||
|
this.Hostname,
|
||||||
|
this.Expiry,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(stringMarshal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Lease) UnmarshalJSON(data []byte) error {
|
||||||
|
stringUnMarshal := struct {
|
||||||
|
IP string
|
||||||
|
Status int
|
||||||
|
MACAddress string
|
||||||
|
Hostname string
|
||||||
|
Expiry time.Time
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, &stringUnMarshal)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.IP = net.ParseIP(stringUnMarshal.IP)
|
||||||
|
this.Status = LeaseStatus(stringUnMarshal.Status)
|
||||||
|
if stringUnMarshal.MACAddress != "" {
|
||||||
|
this.MACAddress, err = net.ParseMAC(stringUnMarshal.MACAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Hostname = stringUnMarshal.Hostname
|
||||||
|
this.Expiry = stringUnMarshal.Expiry
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Lease) Equal(other Lease) bool {
|
||||||
|
if !this.IP.Equal(other.IP) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(this.Status) != int(other.Status) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.MACAddress.String() != other.MACAddress.String() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.Hostname != other.Hostname {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !this.Expiry.Equal(other.Expiry) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
51
vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go
generated
vendored
Normal file
51
vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package leasepool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Leases are Marshalled and Unmarshalled for storage.
|
||||||
|
* I JSON Marshal these for gvklite
|
||||||
|
*/
|
||||||
|
func TestMarshaling(test *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
startLease := Lease{}
|
||||||
|
startLease.IP = net.IPv4(192, 168, 0, 1)
|
||||||
|
startLease.Hostname = "ExampleHostname"
|
||||||
|
startLease.Status = Active
|
||||||
|
startLease.Expiry = time.Now()
|
||||||
|
startLease.MACAddress, err = net.ParseMAC("01:23:45:67:89:ab")
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Parsing Mac Address:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
byteStartLease, err := json.Marshal(startLease)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Marshaling to JSON:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("StartLease As JSON:" + string(byteStartLease))
|
||||||
|
|
||||||
|
endLease := Lease{}
|
||||||
|
err = json.Unmarshal(byteStartLease, &endLease)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Unmarshaling to JSON:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Logf("End Lease Object:%v\n", endLease)
|
||||||
|
|
||||||
|
if !startLease.Equal(endLease) {
|
||||||
|
byteEndLease, err := json.Marshal(endLease)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Can't Marshal End Lease For Debuging:" + err.Error())
|
||||||
|
}
|
||||||
|
test.Log("End Lease as JSON:" + string(byteEndLease))
|
||||||
|
test.Error("Starting Lease Doesn't Match End Lease")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
vendor/github.com/d2g/dhcp4server/leasepool/leasepool.go
generated
vendored
Normal file
49
vendor/github.com/d2g/dhcp4server/leasepool/leasepool.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package leasepool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lease.IP is the Key.
|
||||||
|
*/
|
||||||
|
type LeasePool interface {
|
||||||
|
//Add A Lease To The Pool
|
||||||
|
AddLease(Lease) error
|
||||||
|
|
||||||
|
//Remove
|
||||||
|
RemoveLease(net.IP) error
|
||||||
|
|
||||||
|
//Remove All Leases from the Pool (Required for Persistant LeaseManagers)
|
||||||
|
PurgeLeases() error
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the Lease
|
||||||
|
* -Found
|
||||||
|
* -Copy Of the Lease
|
||||||
|
* -Any Error
|
||||||
|
*/
|
||||||
|
GetLease(net.IP) (bool, Lease, error)
|
||||||
|
|
||||||
|
//Get the lease already in use by that hardware address.
|
||||||
|
GetLeaseForHardwareAddress(net.HardwareAddr) (bool, Lease, error)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* -Lease Available
|
||||||
|
* -Lease
|
||||||
|
* -Error
|
||||||
|
*/
|
||||||
|
GetNextFreeLease() (bool, Lease, error)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return All Leases
|
||||||
|
*/
|
||||||
|
GetLeases() ([]Lease, error)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update Lease
|
||||||
|
* - Has Updated
|
||||||
|
* - Error
|
||||||
|
*/
|
||||||
|
UpdateLease(Lease) (bool, error)
|
||||||
|
}
|
150
vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go
generated
vendored
Normal file
150
vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package memorypool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/d2g/dhcp4server/leasepool"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemoryPool struct {
|
||||||
|
pool []leasepool.Lease
|
||||||
|
poolLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add A Lease To The Pool
|
||||||
|
func (t *MemoryPool) AddLease(newLease leasepool.Lease) error {
|
||||||
|
t.poolLock.Lock()
|
||||||
|
defer t.poolLock.Unlock()
|
||||||
|
|
||||||
|
if t.pool == nil {
|
||||||
|
t.pool = make([]leasepool.Lease, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range t.pool {
|
||||||
|
if t.pool[i].IP.Equal(newLease.IP) {
|
||||||
|
//Lease Already Exists In Pool
|
||||||
|
return errors.New("Error: Lease IP \"" + newLease.IP.String() + "\" alreay exists in Pool")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.pool = append([]leasepool.Lease{newLease}, t.pool...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove a Lease From The Pool
|
||||||
|
func (t *MemoryPool) RemoveLease(leaseIP net.IP) error {
|
||||||
|
t.poolLock.Lock()
|
||||||
|
defer t.poolLock.Unlock()
|
||||||
|
|
||||||
|
for i := range t.pool {
|
||||||
|
if t.pool[i].IP.Equal(leaseIP) {
|
||||||
|
|
||||||
|
//Move the Last Element to This Position.
|
||||||
|
t.pool[i] = t.pool[len(t.pool)-1]
|
||||||
|
|
||||||
|
//Shortern the Pool By One.
|
||||||
|
t.pool = t.pool[0:(len(t.pool) - 1)]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("Error: Lease IP \"" + leaseIP.String() + "\" Is Not In The Pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove All Leases from the Pool (Required for Persistant LeaseManagers)
|
||||||
|
func (t *MemoryPool) PurgeLeases() error {
|
||||||
|
t.poolLock.Lock()
|
||||||
|
defer t.poolLock.Unlock()
|
||||||
|
|
||||||
|
t.pool = nil
|
||||||
|
t.pool = make([]leasepool.Lease, 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the Lease
|
||||||
|
* -Found
|
||||||
|
* -Copy Of the Lease
|
||||||
|
* -Any Error
|
||||||
|
*/
|
||||||
|
func (t *MemoryPool) GetLease(leaseIP net.IP) (bool, leasepool.Lease, error) {
|
||||||
|
t.poolLock.Lock()
|
||||||
|
defer t.poolLock.Unlock()
|
||||||
|
|
||||||
|
for i := range t.pool {
|
||||||
|
if t.pool[i].IP.Equal(leaseIP) {
|
||||||
|
return true, t.pool[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, leasepool.Lease{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the lease already in use by that hardware address.
|
||||||
|
func (t *MemoryPool) GetLeaseForHardwareAddress(macAddress net.HardwareAddr) (bool, leasepool.Lease, error) {
|
||||||
|
t.poolLock.Lock()
|
||||||
|
defer t.poolLock.Unlock()
|
||||||
|
|
||||||
|
for i := range t.pool {
|
||||||
|
if bytes.Equal(t.pool[i].MACAddress, macAddress) {
|
||||||
|
return true, t.pool[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, leasepool.Lease{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* -Lease Available
|
||||||
|
* -Lease
|
||||||
|
* -Error
|
||||||
|
*/
|
||||||
|
func (t *MemoryPool) GetNextFreeLease() (bool, leasepool.Lease, error) {
|
||||||
|
t.poolLock.Lock()
|
||||||
|
defer t.poolLock.Unlock()
|
||||||
|
|
||||||
|
//Loop Through the elements backwards.
|
||||||
|
for i := (len(t.pool) - 1); i >= 0; i-- {
|
||||||
|
//If the Lease Is Free
|
||||||
|
if t.pool[i].Status == leasepool.Free {
|
||||||
|
//Take the Element
|
||||||
|
iLease := t.pool[i]
|
||||||
|
//Shrink the Pool By 1
|
||||||
|
t.pool = t.pool[:(len(t.pool) - 1)]
|
||||||
|
//Place the Lease At the Begining (This saves us having some sort of counter...)
|
||||||
|
t.pool = append([]leasepool.Lease{iLease}, t.pool...)
|
||||||
|
return true, iLease, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, leasepool.Lease{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return All Leases
|
||||||
|
*/
|
||||||
|
func (t *MemoryPool) GetLeases() ([]leasepool.Lease, error) {
|
||||||
|
return t.pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update Lease
|
||||||
|
* - Has Updated
|
||||||
|
* - Error
|
||||||
|
*/
|
||||||
|
func (t *MemoryPool) UpdateLease(lease leasepool.Lease) (bool, error) {
|
||||||
|
t.poolLock.Lock()
|
||||||
|
defer t.poolLock.Unlock()
|
||||||
|
|
||||||
|
for i := range t.pool {
|
||||||
|
if t.pool[i].IP.Equal(lease.IP) {
|
||||||
|
|
||||||
|
t.pool[i].MACAddress = lease.MACAddress
|
||||||
|
t.pool[i].Hostname = lease.Hostname
|
||||||
|
t.pool[i].Expiry = lease.Expiry
|
||||||
|
t.pool[i].Status = lease.Status
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
51
vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool_test.go
generated
vendored
Normal file
51
vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool_test.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package memorypool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
"github.com/d2g/dhcp4server/leasepool"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLeaseCycle(test *testing.T) {
|
||||||
|
myMemoryLeasePool := MemoryPool{}
|
||||||
|
|
||||||
|
//Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you.
|
||||||
|
// So Create Array of IPs 192.168.1.1 to 192.168.1.30
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)})
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Creating Lease:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
hasLease, iLease, err := myMemoryLeasePool.GetNextFreeLease()
|
||||||
|
if err != nil || !hasLease {
|
||||||
|
test.Error("Error Getting Lease:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).Equal(iLease.IP) {
|
||||||
|
test.Error("Expected Lease:" + dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).String() + " Received:" + iLease.IP.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSingleLease(test *testing.T) {
|
||||||
|
myMemoryLeasePool := MemoryPool{}
|
||||||
|
|
||||||
|
err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0)})
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Creating Lease:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
hasLease, iLease, err := myMemoryLeasePool.GetNextFreeLease()
|
||||||
|
if err != nil || !hasLease {
|
||||||
|
test.Error("Error Getting Lease:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0).Equal(iLease.IP) {
|
||||||
|
test.Error("Expected Lease:" + dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0).String() + " Received:" + iLease.IP.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
572
vendor/github.com/d2g/dhcp4server/server.go
generated
vendored
Normal file
572
vendor/github.com/d2g/dhcp4server/server.go
generated
vendored
Normal file
@ -0,0 +1,572 @@
|
|||||||
|
package dhcp4server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
"github.com/d2g/dhcp4server/leasepool"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The DHCP Server Structure
|
||||||
|
*/
|
||||||
|
type Server struct {
|
||||||
|
//Configuration Options
|
||||||
|
ip net.IP //The IP Address We Tell Clients The Server Is On.
|
||||||
|
defaultGateway net.IP //The Default Gateway Address
|
||||||
|
dnsServers []net.IP //DNS Servers
|
||||||
|
subnetMask net.IP //ie. 255.255.255.0
|
||||||
|
leaseDuration time.Duration //Number of Seconds
|
||||||
|
ignoreIPs []net.IP //Slice of IP's that should be ignored by the Server.
|
||||||
|
ignoreHardwareAddress []net.HardwareAddr //Slice of Hardware Addresses we should ignore.
|
||||||
|
|
||||||
|
//Local Address
|
||||||
|
laddr net.UDPAddr
|
||||||
|
|
||||||
|
//Remote address
|
||||||
|
raddr net.UDPAddr
|
||||||
|
|
||||||
|
//LeasePool
|
||||||
|
leasePool leasepool.LeasePool //Lease Pool Manager
|
||||||
|
|
||||||
|
//Used to Gracefully Close the Server
|
||||||
|
shutdownLock sync.Mutex
|
||||||
|
shutdown bool
|
||||||
|
//Listeners & Response Connection.
|
||||||
|
connection *ipv4.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create A New Server
|
||||||
|
func New(ip net.IP, l leasepool.LeasePool, options ...func(*Server) error) (*Server, error) {
|
||||||
|
s := Server{
|
||||||
|
ip: ip,
|
||||||
|
defaultGateway: ip,
|
||||||
|
dnsServers: []net.IP{net.IPv4(208, 67, 222, 222), net.IPv4(208, 67, 220, 220)}, //OPENDNS
|
||||||
|
subnetMask: net.IPv4(255, 255, 255, 0),
|
||||||
|
leaseDuration: 24 * time.Hour,
|
||||||
|
leasePool: l,
|
||||||
|
laddr: net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67},
|
||||||
|
raddr: net.UDPAddr{IP: net.IPv4bcast, Port: 68},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.setOptions(options...)
|
||||||
|
if err != nil {
|
||||||
|
return &s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) setOptions(options ...func(*Server) error) error {
|
||||||
|
for _, opt := range options {
|
||||||
|
if err := opt(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Server IP
|
||||||
|
func IP(i net.IP) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.ip = i
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Default Gateway Address.
|
||||||
|
func DefaultGateway(r net.IP) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.defaultGateway = r
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the DNS servers.
|
||||||
|
func DNSServers(dnss []net.IP) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.dnsServers = dnss
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Subnet Mask
|
||||||
|
func SubnetMask(m net.IP) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.subnetMask = m
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Lease Duration
|
||||||
|
func LeaseDuration(d time.Duration) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.leaseDuration = d
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Ignore IPs
|
||||||
|
func IgnoreIPs(ips []net.IP) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.ignoreIPs = ips
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Ignore Hardware Addresses
|
||||||
|
func IgnoreHardwareAddresses(h []net.HardwareAddr) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.ignoreHardwareAddress = h
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set LeasePool
|
||||||
|
func LeasePool(p leasepool.LeasePool) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.leasePool = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set The Local Address
|
||||||
|
func SetLocalAddr(a net.UDPAddr) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.laddr = a
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set The Remote Address
|
||||||
|
func SetRemoteAddr(a net.UDPAddr) func(*Server) error {
|
||||||
|
return func(s *Server) error {
|
||||||
|
s.raddr = a
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) shouldShutdown() bool {
|
||||||
|
s.shutdownLock.Lock()
|
||||||
|
defer s.shutdownLock.Unlock()
|
||||||
|
return s.shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start The DHCP Server
|
||||||
|
*/
|
||||||
|
func (s *Server) ListenAndServe() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
connection, err := net.ListenPacket("udp4", s.laddr.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Debug: Error Returned From ListenPacket On \"%s\" Because of \"%s\"\n", s.laddr.String(), err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.connection = ipv4.NewPacketConn(connection)
|
||||||
|
defer s.connection.Close()
|
||||||
|
|
||||||
|
//We Currently Don't Use this Feature Which is the only bit that is Linux Only.
|
||||||
|
//if err := s.connection.SetControlMessage(ipv4.FlagInterface, true); err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Make Our Buffer (Max Buffer is 574) "I believe this 576 size comes from RFC 791" - Random Mailing list quote of the day.
|
||||||
|
buffer := make([]byte, 576)
|
||||||
|
|
||||||
|
log.Println("Trace: DHCP Server Listening.")
|
||||||
|
|
||||||
|
for {
|
||||||
|
ListenForDHCPPackets:
|
||||||
|
if s.shouldShutdown() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set Read Deadline
|
||||||
|
s.connection.SetReadDeadline(time.Now().Add(time.Second))
|
||||||
|
// Read Packet
|
||||||
|
n, control_message, source, err := s.connection.ReadFrom(buffer)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
switch v := err.(type) {
|
||||||
|
case *net.OpError:
|
||||||
|
if v.Timeout() {
|
||||||
|
goto ListenForDHCPPackets
|
||||||
|
}
|
||||||
|
case *net.AddrError:
|
||||||
|
if v.Timeout() {
|
||||||
|
goto ListenForDHCPPackets
|
||||||
|
}
|
||||||
|
case *net.UnknownNetworkError:
|
||||||
|
if v.Timeout() {
|
||||||
|
goto ListenForDHCPPackets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Debug: Unexpect Error from Connection Read From:" + err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//We seem to have an issue with undersized packets?
|
||||||
|
if n < 240 {
|
||||||
|
log.Printf("Error: Invalid Packet Size \"%d\" Received:%v\n", n, buffer[:n])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//We should ignore some requests
|
||||||
|
//It shouldn't be possible to ignore IP's because they shouldn't have them as we're the DHCP server.
|
||||||
|
//However, they can have i.e. if you're the client & server :S.
|
||||||
|
for _, ipToIgnore := range s.ignoreIPs {
|
||||||
|
if ipToIgnore.Equal(source.(*net.UDPAddr).IP) {
|
||||||
|
log.Println("Debug: Ignoring DHCP Request From IP:" + ipToIgnore.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := dhcp4.Packet(buffer[:n])
|
||||||
|
|
||||||
|
//We can ignore hardware addresses.
|
||||||
|
//Usefull for ignoring a range of hardware addresses
|
||||||
|
for _, hardwareAddressToIgnore := range s.ignoreHardwareAddress {
|
||||||
|
if bytes.Equal(hardwareAddressToIgnore, packet.CHAddr()) {
|
||||||
|
log.Println("Debug: Ignoring DHCP Request From Hardware Address:" + hardwareAddressToIgnore.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Trace: Packet Received ID:%v\n", packet.XId())
|
||||||
|
log.Printf("Trace: Packet Options:%v\n", packet.ParseOptions())
|
||||||
|
log.Printf("Trace: Packet Client IP : %v\n", packet.CIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Your IP : %v\n", packet.YIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Server IP : %v\n", packet.SIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Gateway IP: %v\n", packet.GIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Client Mac: %v\n", packet.CHAddr().String())
|
||||||
|
|
||||||
|
//We need to stop butting in with other servers.
|
||||||
|
if packet.SIAddr().Equal(net.IPv4(0, 0, 0, 0)) || packet.SIAddr().Equal(net.IP{}) || packet.SIAddr().Equal(s.ip) {
|
||||||
|
|
||||||
|
returnPacket, err := s.ServeDHCP(packet)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Debug: Error Serving DHCP:" + err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(returnPacket) > 0 {
|
||||||
|
log.Printf("Trace: Packet Returned ID:%v\n", returnPacket.XId())
|
||||||
|
log.Printf("Trace: Packet Options:%v\n", returnPacket.ParseOptions())
|
||||||
|
log.Printf("Trace: Packet Client IP : %v\n", returnPacket.CIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Your IP : %v\n", returnPacket.YIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Server IP : %v\n", returnPacket.SIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Gateway IP: %v\n", returnPacket.GIAddr().String())
|
||||||
|
log.Printf("Trace: Packet Client Mac: %v\n", returnPacket.CHAddr().String())
|
||||||
|
|
||||||
|
_, err = s.connection.WriteTo(returnPacket, control_message, &s.raddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Debug: Error Writing:" + err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ServeDHCP(packet dhcp4.Packet) (dhcp4.Packet, error) {
|
||||||
|
packetOptions := packet.ParseOptions()
|
||||||
|
|
||||||
|
switch dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]) {
|
||||||
|
case dhcp4.Discover:
|
||||||
|
|
||||||
|
//Discover Received from client
|
||||||
|
//Lets get the lease we're going to send them
|
||||||
|
found, lease, err := s.GetLease(packet)
|
||||||
|
if err != nil {
|
||||||
|
return dhcp4.Packet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Println("Warning: It Looks Like Our Leases Are Depleted...")
|
||||||
|
return dhcp4.Packet{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
offerPacket := s.OfferPacket(packet)
|
||||||
|
offerPacket.SetYIAddr(lease.IP)
|
||||||
|
|
||||||
|
//Sort out the packet options
|
||||||
|
offerPacket.PadToMinSize()
|
||||||
|
|
||||||
|
lease.Status = leasepool.Reserved
|
||||||
|
lease.MACAddress = packet.CHAddr()
|
||||||
|
|
||||||
|
//If the lease expires within the next 5 Mins increase the lease expiary (Giving the Client 5 mins to complete)
|
||||||
|
if lease.Expiry.Before(time.Now().Add(time.Minute * 5)) {
|
||||||
|
lease.Expiry = time.Now().Add(time.Minute * 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
|
||||||
|
lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := s.leasePool.UpdateLease(lease)
|
||||||
|
if err != nil {
|
||||||
|
return dhcp4.Packet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
//Unable to reserve lease (It's now active else where maybe?)
|
||||||
|
return dhcp4.Packet{}, errors.New("Unable to Reserve Lease:" + lease.IP.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return offerPacket, nil
|
||||||
|
case dhcp4.Request:
|
||||||
|
//Request Received from client
|
||||||
|
//Lets get the lease we're going to send them
|
||||||
|
found, lease, err := s.GetLease(packet)
|
||||||
|
if err != nil {
|
||||||
|
return dhcp4.Packet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Println("Warning: It Looks Like Our Leases Are Depleted...")
|
||||||
|
return dhcp4.Packet{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the lease is not the one requested We should send a NAK..
|
||||||
|
if len(packetOptions) > 0 && !net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(lease.IP) {
|
||||||
|
//NAK
|
||||||
|
declinePacket := s.DeclinePacket(packet)
|
||||||
|
declinePacket.PadToMinSize()
|
||||||
|
|
||||||
|
return declinePacket, nil
|
||||||
|
} else {
|
||||||
|
lease.Status = leasepool.Active
|
||||||
|
lease.MACAddress = packet.CHAddr()
|
||||||
|
|
||||||
|
lease.Expiry = time.Now().Add(s.leaseDuration)
|
||||||
|
|
||||||
|
if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
|
||||||
|
lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := s.leasePool.UpdateLease(lease)
|
||||||
|
if err != nil {
|
||||||
|
return dhcp4.Packet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
//ACK
|
||||||
|
acknowledgementPacket := s.AcknowledgementPacket(packet)
|
||||||
|
acknowledgementPacket.SetYIAddr(lease.IP)
|
||||||
|
|
||||||
|
//Lease time.
|
||||||
|
acknowledgementPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(lease.Expiry.Sub(time.Now())))
|
||||||
|
acknowledgementPacket.PadToMinSize()
|
||||||
|
|
||||||
|
return acknowledgementPacket, nil
|
||||||
|
} else {
|
||||||
|
//NAK
|
||||||
|
declinePacket := s.DeclinePacket(packet)
|
||||||
|
declinePacket.PadToMinSize()
|
||||||
|
|
||||||
|
return declinePacket, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case dhcp4.Decline:
|
||||||
|
//Decline from the client:
|
||||||
|
log.Printf("Debug: Decline Message:%v\n", packet)
|
||||||
|
|
||||||
|
case dhcp4.Release:
|
||||||
|
//Decline from the client:
|
||||||
|
log.Printf("Debug: Release Message:%v\n", packet)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Printf("Debug: Unexpected Packet Type:%v\n", dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return dhcp4.Packet{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create DHCP Offer Packet
|
||||||
|
*/
|
||||||
|
func (s *Server) OfferPacket(discoverPacket dhcp4.Packet) dhcp4.Packet {
|
||||||
|
|
||||||
|
offerPacket := dhcp4.NewPacket(dhcp4.BootReply)
|
||||||
|
offerPacket.SetXId(discoverPacket.XId())
|
||||||
|
offerPacket.SetFlags(discoverPacket.Flags())
|
||||||
|
|
||||||
|
offerPacket.SetCHAddr(discoverPacket.CHAddr())
|
||||||
|
offerPacket.SetGIAddr(discoverPacket.GIAddr())
|
||||||
|
offerPacket.SetSecs(discoverPacket.Secs())
|
||||||
|
|
||||||
|
//53
|
||||||
|
offerPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Offer)})
|
||||||
|
//54
|
||||||
|
offerPacket.AddOption(dhcp4.OptionServerIdentifier, s.ip.To4())
|
||||||
|
//51
|
||||||
|
offerPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(s.leaseDuration))
|
||||||
|
|
||||||
|
//Other options go in requested order...
|
||||||
|
discoverPacketOptions := discoverPacket.ParseOptions()
|
||||||
|
|
||||||
|
ourOptions := make(dhcp4.Options)
|
||||||
|
|
||||||
|
//1
|
||||||
|
ourOptions[dhcp4.OptionSubnetMask] = s.subnetMask.To4()
|
||||||
|
//3
|
||||||
|
ourOptions[dhcp4.OptionRouter] = s.defaultGateway.To4()
|
||||||
|
//6
|
||||||
|
ourOptions[dhcp4.OptionDomainNameServer] = dhcp4.JoinIPs(s.dnsServers)
|
||||||
|
|
||||||
|
if discoverPacketOptions[dhcp4.OptionParameterRequestList] != nil {
|
||||||
|
//Loop through the requested options and if we have them add them.
|
||||||
|
for _, optionCode := range discoverPacketOptions[dhcp4.OptionParameterRequestList] {
|
||||||
|
if !bytes.Equal(ourOptions[dhcp4.OptionCode(optionCode)], []byte{}) {
|
||||||
|
offerPacket.AddOption(dhcp4.OptionCode(optionCode), ourOptions[dhcp4.OptionCode(optionCode)])
|
||||||
|
delete(ourOptions, dhcp4.OptionCode(optionCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add all the options not requested.
|
||||||
|
for optionCode, optionValue := range ourOptions {
|
||||||
|
offerPacket.AddOption(optionCode, optionValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return offerPacket
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create DHCP Acknowledgement
|
||||||
|
*/
|
||||||
|
func (s *Server) AcknowledgementPacket(requestPacket dhcp4.Packet) dhcp4.Packet {
|
||||||
|
|
||||||
|
acknowledgementPacket := dhcp4.NewPacket(dhcp4.BootReply)
|
||||||
|
acknowledgementPacket.SetXId(requestPacket.XId())
|
||||||
|
acknowledgementPacket.SetFlags(requestPacket.Flags())
|
||||||
|
|
||||||
|
acknowledgementPacket.SetGIAddr(requestPacket.GIAddr())
|
||||||
|
acknowledgementPacket.SetCHAddr(requestPacket.CHAddr())
|
||||||
|
acknowledgementPacket.SetSecs(requestPacket.Secs())
|
||||||
|
|
||||||
|
acknowledgementPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.ACK)})
|
||||||
|
acknowledgementPacket.AddOption(dhcp4.OptionSubnetMask, s.subnetMask.To4())
|
||||||
|
acknowledgementPacket.AddOption(dhcp4.OptionRouter, s.defaultGateway.To4())
|
||||||
|
acknowledgementPacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(s.dnsServers))
|
||||||
|
acknowledgementPacket.AddOption(dhcp4.OptionServerIdentifier, s.ip.To4())
|
||||||
|
|
||||||
|
return acknowledgementPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create DHCP Decline
|
||||||
|
*/
|
||||||
|
func (s *Server) DeclinePacket(requestPacket dhcp4.Packet) dhcp4.Packet {
|
||||||
|
|
||||||
|
declinePacket := dhcp4.NewPacket(dhcp4.BootReply)
|
||||||
|
declinePacket.SetXId(requestPacket.XId())
|
||||||
|
declinePacket.SetFlags(requestPacket.Flags())
|
||||||
|
|
||||||
|
declinePacket.SetGIAddr(requestPacket.GIAddr())
|
||||||
|
declinePacket.SetCHAddr(requestPacket.CHAddr())
|
||||||
|
declinePacket.SetSecs(requestPacket.Secs())
|
||||||
|
|
||||||
|
declinePacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.NAK)})
|
||||||
|
declinePacket.AddOption(dhcp4.OptionSubnetMask, s.subnetMask.To4())
|
||||||
|
declinePacket.AddOption(dhcp4.OptionRouter, s.defaultGateway.To4())
|
||||||
|
declinePacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(s.dnsServers))
|
||||||
|
declinePacket.AddOption(dhcp4.OptionServerIdentifier, s.ip.To4())
|
||||||
|
|
||||||
|
return declinePacket
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get Lease tries to work out the best lease for the packet supplied.
|
||||||
|
* Taking into account all Requested IP, Exisitng MACAddresses and Free leases.
|
||||||
|
*/
|
||||||
|
func (s *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Lease, err error) {
|
||||||
|
packetOptions := packet.ParseOptions()
|
||||||
|
|
||||||
|
//Requested an IP
|
||||||
|
if (len(packetOptions) > 0) &&
|
||||||
|
packetOptions[dhcp4.OptionRequestedIPAddress] != nil &&
|
||||||
|
!net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(net.IP{}) {
|
||||||
|
//An IP Has Been Requested Let's Try and Get that One.
|
||||||
|
|
||||||
|
found, lease, err = s.leasePool.GetLease(net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
if lease.Status == leasepool.Free {
|
||||||
|
//Lease Is Free you Can Have it.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lease.Status != leasepool.Free && bytes.Equal(lease.MACAddress, packet.CHAddr()) {
|
||||||
|
//Lease isn't free but it's yours
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ok Even if you requested an IP you can't have it.
|
||||||
|
found, lease, err = s.leasePool.GetLeaseForHardwareAddress(packet.CHAddr())
|
||||||
|
if found || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Just get the next free lease if you can.
|
||||||
|
found, lease, err = s.leasePool.GetNextFreeLease()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shutdown The Server Gracefully
|
||||||
|
*/
|
||||||
|
func (s *Server) Shutdown() {
|
||||||
|
s.shutdownLock.Lock()
|
||||||
|
defer s.shutdownLock.Unlock()
|
||||||
|
s.shutdown = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Garbage Collection
|
||||||
|
* Run Garbage Collection On Your Leases To Free Expired Leases.
|
||||||
|
*/
|
||||||
|
func (s *Server) GC() error {
|
||||||
|
leases, err := s.leasePool.GetLeases()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range leases {
|
||||||
|
if leases[i].Status != leasepool.Free {
|
||||||
|
//Lease Is Not Free
|
||||||
|
|
||||||
|
if time.Now().After(leases[i].Expiry) {
|
||||||
|
//Lease has expired.
|
||||||
|
leases[i].Status = leasepool.Free
|
||||||
|
updated, err := s.leasePool.UpdateLease(leases[i])
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Error trying to Free Lease %s \"%v\"\n", leases[i].IP.To4().String(), err)
|
||||||
|
}
|
||||||
|
if !updated {
|
||||||
|
log.Printf("Warning: Unable to Free Lease %s\n", leases[i].IP.To4().String())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
426
vendor/github.com/d2g/dhcp4server/server_test.go
generated
vendored
Normal file
426
vendor/github.com/d2g/dhcp4server/server_test.go
generated
vendored
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
package dhcp4server_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
"github.com/d2g/dhcp4client"
|
||||||
|
"github.com/d2g/dhcp4server"
|
||||||
|
"github.com/d2g/dhcp4server/leasepool"
|
||||||
|
"github.com/d2g/dhcp4server/leasepool/memorypool"
|
||||||
|
"github.com/d2g/hardwareaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Example Server :D
|
||||||
|
*/
|
||||||
|
func ExampleServer() {
|
||||||
|
|
||||||
|
//Create a Lease Pool We're going to use a memory pool
|
||||||
|
//Remember the memory is cleared on restart so you will reissue the same IP Addresses.
|
||||||
|
myMemoryLeasePool := memorypool.MemoryPool{}
|
||||||
|
|
||||||
|
//Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you.
|
||||||
|
// So Create Array of IPs 192.168.1.1 to 192.168.1.30
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error Adding IP to pool:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set the port numbers to over 1024 (1067 & 1068) as the automated test don't have root access
|
||||||
|
tServer, err := dhcp4server.New(
|
||||||
|
net.IPv4(192, 168, 1, 201),
|
||||||
|
&myMemoryLeasePool,
|
||||||
|
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 1067}),
|
||||||
|
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4bcast, Port: 1068}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error Configuring Server:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start the Server...
|
||||||
|
err = tServer.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error Starting Server:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test Discovering a Lease That's not Within Our Lease Range.
|
||||||
|
* This Happens When a devce switches network.
|
||||||
|
* Example: Mobile Phone on Mobile internet Has IP 100.123.123.123 Switch To Home Wifi
|
||||||
|
* The device requests 100.123.123.123 on Home Wifi which is out of range...
|
||||||
|
*/
|
||||||
|
func TestDiscoverOutOfRangeLease(test *testing.T) {
|
||||||
|
//Setup the Server
|
||||||
|
myServer, err := dhcp4server.New(
|
||||||
|
net.IPv4(192, 168, 1, 201),
|
||||||
|
getTestLeasePool(),
|
||||||
|
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}),
|
||||||
|
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Configure Server " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
err := myServer.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error Starting Server:" + err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(5) * time.Second)
|
||||||
|
|
||||||
|
//Generate Hardware Address
|
||||||
|
HardwareMACAddress, err := hardwareaddr.GenerateEUI48()
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Generate Valid MACAddress" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
//Lets Be A Client
|
||||||
|
|
||||||
|
//We need to set the connection ports to 1068 and 1067 so we don't need root access
|
||||||
|
c, err := dhcp4client.NewInetSock(dhcp4client.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}), dhcp4client.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}))
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Client Conection Generation:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := dhcp4client.New(dhcp4client.HardwareAddr(HardwareMACAddress), dhcp4client.Connection(c))
|
||||||
|
defer client.Close()
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Conection Error:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveryPacket := client.DiscoverPacket()
|
||||||
|
discoveryPacket.SetCIAddr(net.IPv4(100, 102, 96, 123))
|
||||||
|
discoveryPacket.PadToMinSize()
|
||||||
|
|
||||||
|
err = client.SendPacket(discoveryPacket)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Sending Discover Packet" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("--Discovery Packet--")
|
||||||
|
test.Logf("Client IP : %v\n", discoveryPacket.CIAddr().String())
|
||||||
|
test.Logf("Your IP : %v\n", discoveryPacket.YIAddr().String())
|
||||||
|
test.Logf("Server IP : %v\n", discoveryPacket.SIAddr().String())
|
||||||
|
test.Logf("Gateway IP: %v\n", discoveryPacket.GIAddr().String())
|
||||||
|
test.Logf("Client Mac: %v\n", discoveryPacket.CHAddr().String())
|
||||||
|
|
||||||
|
if !bytes.Equal(discoveryPacket.CHAddr(), HardwareMACAddress) {
|
||||||
|
test.Error("MACAddresses Don't Match??")
|
||||||
|
}
|
||||||
|
|
||||||
|
offerPacket, err := client.GetOffer(&discoveryPacket)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Getting Offer:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("--Offer Packet--")
|
||||||
|
test.Logf("Client IP : %v\n", offerPacket.CIAddr().String())
|
||||||
|
test.Logf("Your IP : %v\n", offerPacket.YIAddr().String())
|
||||||
|
test.Logf("Server IP : %v\n", offerPacket.SIAddr().String())
|
||||||
|
test.Logf("Gateway IP: %v\n", offerPacket.GIAddr().String())
|
||||||
|
test.Logf("Client Mac: %v\n", offerPacket.CHAddr().String())
|
||||||
|
|
||||||
|
requestPacket, err := client.SendRequest(&offerPacket)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Sending Request:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("--Request Packet--")
|
||||||
|
test.Logf("Client IP : %v\n", requestPacket.CIAddr().String())
|
||||||
|
test.Logf("Your IP : %v\n", requestPacket.YIAddr().String())
|
||||||
|
test.Logf("Server IP : %v\n", requestPacket.SIAddr().String())
|
||||||
|
test.Logf("Gateway IP: %v\n", requestPacket.GIAddr().String())
|
||||||
|
test.Logf("Client Mac: %v\n", requestPacket.CHAddr().String())
|
||||||
|
|
||||||
|
acknowledgement, err := client.GetAcknowledgement(&requestPacket)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Getting Acknowledgement:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("--Acknowledgement Packet--")
|
||||||
|
test.Logf("Client IP : %v\n", acknowledgement.CIAddr().String())
|
||||||
|
test.Logf("Your IP : %v\n", acknowledgement.YIAddr().String())
|
||||||
|
test.Logf("Server IP : %v\n", acknowledgement.SIAddr().String())
|
||||||
|
test.Logf("Gateway IP: %v\n", acknowledgement.GIAddr().String())
|
||||||
|
test.Logf("Client Mac: %v\n", acknowledgement.CHAddr().String())
|
||||||
|
|
||||||
|
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||||
|
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
|
||||||
|
test.Error("Didn't get ACK?:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("Shutting Down Server")
|
||||||
|
myServer.Shutdown()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try Renewing A Lease From A Different Network.
|
||||||
|
*/
|
||||||
|
func TestRequestOutOfRangeLease(test *testing.T) {
|
||||||
|
//Setup the Server
|
||||||
|
myServer, err := dhcp4server.New(
|
||||||
|
net.IPv4(192, 168, 1, 201),
|
||||||
|
getTestLeasePool(),
|
||||||
|
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}),
|
||||||
|
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Configure Server " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
err := myServer.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error Starting Server:" + err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
//Sleep some so the server starts....
|
||||||
|
time.Sleep(time.Duration(5) * time.Second)
|
||||||
|
|
||||||
|
//Generate Hardware Address
|
||||||
|
HardwareMACAddress, err := hardwareaddr.GenerateEUI48()
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Generate Valid MACAddress" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
HardwareMACAddress, err = net.ParseMAC("58-94-6B-73-57-0C")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("MAC Error:%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Lets Be A Client
|
||||||
|
c, err := dhcp4client.NewInetSock(dhcp4client.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}), dhcp4client.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}))
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Client Conection Generation:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := dhcp4client.New(dhcp4client.HardwareAddr(HardwareMACAddress), dhcp4client.Connection(c))
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Conection Error:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a dummy offer packet
|
||||||
|
offerPacket := client.DiscoverPacket()
|
||||||
|
|
||||||
|
offerPacket.SetCIAddr(net.IPv4(100, 102, 96, 123))
|
||||||
|
offerPacket.SetSIAddr(net.IPv4(192, 168, 1, 201))
|
||||||
|
offerPacket.SetYIAddr(net.IPv4(100, 102, 96, 123))
|
||||||
|
offerPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Offer)})
|
||||||
|
|
||||||
|
requestPacket, err := client.SendRequest(&offerPacket)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Sending Request:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("--Request Packet--")
|
||||||
|
test.Logf("Client IP : %v\n", requestPacket.CIAddr().String())
|
||||||
|
test.Logf("Your IP : %v\n", requestPacket.YIAddr().String())
|
||||||
|
test.Logf("Server IP : %v\n", requestPacket.SIAddr().String())
|
||||||
|
test.Logf("Gateway IP: %v\n", requestPacket.GIAddr().String())
|
||||||
|
test.Logf("Client Mac: %v\n", requestPacket.CHAddr().String())
|
||||||
|
|
||||||
|
acknowledgement, err := client.GetAcknowledgement(&requestPacket)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error Getting Acknowledgement:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("--Acknowledgement Packet--")
|
||||||
|
test.Logf("Client IP : %v\n", acknowledgement.CIAddr().String())
|
||||||
|
test.Logf("Your IP : %v\n", acknowledgement.YIAddr().String())
|
||||||
|
test.Logf("Server IP : %v\n", acknowledgement.SIAddr().String())
|
||||||
|
test.Logf("Gateway IP: %v\n", acknowledgement.GIAddr().String())
|
||||||
|
test.Logf("Client Mac: %v\n", acknowledgement.CHAddr().String())
|
||||||
|
|
||||||
|
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||||
|
if len(acknowledgementOptions[dhcp4.OptionDHCPMessageType]) <= 0 || dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.NAK {
|
||||||
|
test.Errorf("Didn't get NAK got DHCP4 Message Type:%v\n", dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("Shutting Down Server")
|
||||||
|
myServer.Shutdown()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
func TestConsumeLeases(test *testing.T) {
|
||||||
|
//Setup the Server
|
||||||
|
myServer, err := dhcp4server.New(
|
||||||
|
net.IPv4(127, 0, 0, 1),
|
||||||
|
getTestLeasePool(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Configure Server " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup A Client
|
||||||
|
// Although We Won't send the packets over the network we'll use the client to create the requests.
|
||||||
|
c, err := dhcp4client.NewInetSock(dhcp4client.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}), dhcp4client.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}))
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Client Conection Generation:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := dhcp4client.New(dhcp4client.Connection(c))
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Configure Client " + err.Error())
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
//Generate Hardware Address
|
||||||
|
HardwareMACAddress, err := hardwareaddr.GenerateEUI48()
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Generate Valid MACAddress" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SetOption(dhcp4client.HardwareAddr(HardwareMACAddress))
|
||||||
|
test.Log("MAC:" + HardwareMACAddress.String())
|
||||||
|
|
||||||
|
discovery := client.DiscoverPacket()
|
||||||
|
|
||||||
|
//Run the Discovery On the Server
|
||||||
|
offer, err := myServer.ServeDHCP(discovery)
|
||||||
|
_, err = myServer.ServeDHCP(discovery)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Discovery Error:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
request := client.RequestPacket(&offer)
|
||||||
|
acknowledgement, err := myServer.ServeDHCP(request)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Acknowledge Error:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Logf("Received Lease:%v\n", acknowledgement.YIAddr().String())
|
||||||
|
if !dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).Equal(acknowledgement.YIAddr()) {
|
||||||
|
test.Error("Expected IP:" + dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).String() + " Received:" + acknowledgement.YIAddr().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
//How long the lease is for?
|
||||||
|
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||||
|
if len(acknowledgementOptions) > 0 {
|
||||||
|
test.Logf("Lease Options:%v\n", acknowledgementOptions)
|
||||||
|
if acknowledgementOptions[dhcp4.OptionIPAddressLeaseTime] != nil {
|
||||||
|
var result uint32
|
||||||
|
buf := bytes.NewBuffer(acknowledgementOptions[dhcp4.OptionIPAddressLeaseTime])
|
||||||
|
binary.Read(buf, binary.BigEndian, &result)
|
||||||
|
test.Logf("Lease Time (Seconds):%d\n", result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
test.Errorf("Lease:\"%v\" Has No Options\n", acknowledgement.YIAddr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Benchmark the ServeDHCP Function
|
||||||
|
*/
|
||||||
|
func BenchmarkServeDHCP(test *testing.B) {
|
||||||
|
//Create a Lease Pool We're going to use a memory pool
|
||||||
|
//Remember the memory is cleared on restart so you will reissue the same IP Addresses.
|
||||||
|
myMemoryLeasePool := memorypool.MemoryPool{}
|
||||||
|
|
||||||
|
//Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you.
|
||||||
|
// So Create Array of IPs 192.168.1.1 to 192.168.1.30
|
||||||
|
for i := 0; i < test.N; i++ {
|
||||||
|
err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error Adding IP to pool:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Setup the Server
|
||||||
|
myServer, err := dhcp4server.New(
|
||||||
|
net.IPv4(127, 0, 0, 1),
|
||||||
|
&myMemoryLeasePool,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Configure Server " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
//Setup A Client
|
||||||
|
// Although We Won't send the packets over the network we'll use the client to create the requests.
|
||||||
|
c, err := dhcp4client.NewInetSock(dhcp4client.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}), dhcp4client.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}))
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Client Conection Generation:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := dhcp4client.New(dhcp4client.Connection(c))
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Configure Client " + err.Error())
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
test.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < test.N; i++ {
|
||||||
|
test.StopTimer()
|
||||||
|
//Generate Hardware Address
|
||||||
|
HardwareMACAddress, err := hardwareaddr.GenerateEUI48()
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Error: Can't Generate Valid MACAddress" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SetOption(dhcp4client.HardwareAddr(HardwareMACAddress))
|
||||||
|
discovery := client.DiscoverPacket()
|
||||||
|
|
||||||
|
//Run the Discovery On the Server
|
||||||
|
test.StartTimer()
|
||||||
|
offer, err := myServer.ServeDHCP(discovery)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Discovery Error:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(offer) == 0 {
|
||||||
|
test.Error("No Valid Offer")
|
||||||
|
} else {
|
||||||
|
request := client.RequestPacket(&offer)
|
||||||
|
_, err := myServer.ServeDHCP(request)
|
||||||
|
if err != nil {
|
||||||
|
test.Error("Acknowledge Error:" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestLeasePool() *memorypool.MemoryPool {
|
||||||
|
//Create a Lease Pool We're going to use a memory pool
|
||||||
|
//Remember the memory is cleared on restart so you will reissue the same IP Addresses.
|
||||||
|
myMemoryLeasePool := memorypool.MemoryPool{}
|
||||||
|
|
||||||
|
//Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you.
|
||||||
|
// So Create Array of IPs 192.168.1.1 to 192.168.1.30
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error Adding IP to pool:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &myMemoryLeasePool
|
||||||
|
}
|
41
vendor/golang.org/x/net/bpf/asm.go
generated
vendored
Normal file
41
vendor/golang.org/x/net/bpf/asm.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2016 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 bpf
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Assemble converts insts into raw instructions suitable for loading
|
||||||
|
// into a BPF virtual machine.
|
||||||
|
//
|
||||||
|
// Currently, no optimization is attempted, the assembled program flow
|
||||||
|
// is exactly as provided.
|
||||||
|
func Assemble(insts []Instruction) ([]RawInstruction, error) {
|
||||||
|
ret := make([]RawInstruction, len(insts))
|
||||||
|
var err error
|
||||||
|
for i, inst := range insts {
|
||||||
|
ret[i], err = inst.Assemble()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disassemble attempts to parse raw back into
|
||||||
|
// Instructions. Unrecognized RawInstructions are assumed to be an
|
||||||
|
// extension not implemented by this package, and are passed through
|
||||||
|
// unchanged to the output. The allDecoded value reports whether insts
|
||||||
|
// contains no RawInstructions.
|
||||||
|
func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) {
|
||||||
|
insts = make([]Instruction, len(raw))
|
||||||
|
allDecoded = true
|
||||||
|
for i, r := range raw {
|
||||||
|
insts[i] = r.Disassemble()
|
||||||
|
if _, ok := insts[i].(RawInstruction); ok {
|
||||||
|
allDecoded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return insts, allDecoded
|
||||||
|
}
|
215
vendor/golang.org/x/net/bpf/constants.go
generated
vendored
Normal file
215
vendor/golang.org/x/net/bpf/constants.go
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
// Copyright 2016 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 bpf
|
||||||
|
|
||||||
|
// A Register is a register of the BPF virtual machine.
|
||||||
|
type Register uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RegA is the accumulator register. RegA is always the
|
||||||
|
// destination register of ALU operations.
|
||||||
|
RegA Register = iota
|
||||||
|
// RegX is the indirection register, used by LoadIndirect
|
||||||
|
// operations.
|
||||||
|
RegX
|
||||||
|
)
|
||||||
|
|
||||||
|
// An ALUOp is an arithmetic or logic operation.
|
||||||
|
type ALUOp uint16
|
||||||
|
|
||||||
|
// ALU binary operation types.
|
||||||
|
const (
|
||||||
|
ALUOpAdd ALUOp = iota << 4
|
||||||
|
ALUOpSub
|
||||||
|
ALUOpMul
|
||||||
|
ALUOpDiv
|
||||||
|
ALUOpOr
|
||||||
|
ALUOpAnd
|
||||||
|
ALUOpShiftLeft
|
||||||
|
ALUOpShiftRight
|
||||||
|
aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type.
|
||||||
|
ALUOpMod
|
||||||
|
ALUOpXor
|
||||||
|
)
|
||||||
|
|
||||||
|
// A JumpTest is a comparison operator used in conditional jumps.
|
||||||
|
type JumpTest uint16
|
||||||
|
|
||||||
|
// Supported operators for conditional jumps.
|
||||||
|
const (
|
||||||
|
// K == A
|
||||||
|
JumpEqual JumpTest = iota
|
||||||
|
// K != A
|
||||||
|
JumpNotEqual
|
||||||
|
// K > A
|
||||||
|
JumpGreaterThan
|
||||||
|
// K < A
|
||||||
|
JumpLessThan
|
||||||
|
// K >= A
|
||||||
|
JumpGreaterOrEqual
|
||||||
|
// K <= A
|
||||||
|
JumpLessOrEqual
|
||||||
|
// K & A != 0
|
||||||
|
JumpBitsSet
|
||||||
|
// K & A == 0
|
||||||
|
JumpBitsNotSet
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Extension is a function call provided by the kernel that
|
||||||
|
// performs advanced operations that are expensive or impossible
|
||||||
|
// within the BPF virtual machine.
|
||||||
|
//
|
||||||
|
// Extensions are only implemented by the Linux kernel.
|
||||||
|
//
|
||||||
|
// TODO: should we prune this list? Some of these extensions seem
|
||||||
|
// either broken or near-impossible to use correctly, whereas other
|
||||||
|
// (len, random, ifindex) are quite useful.
|
||||||
|
type Extension int
|
||||||
|
|
||||||
|
// Extension functions available in the Linux kernel.
|
||||||
|
const (
|
||||||
|
// ExtLen returns the length of the packet.
|
||||||
|
ExtLen Extension = 1
|
||||||
|
// ExtProto returns the packet's L3 protocol type.
|
||||||
|
ExtProto = 0
|
||||||
|
// ExtType returns the packet's type (skb->pkt_type in the kernel)
|
||||||
|
//
|
||||||
|
// TODO: better documentation. How nice an API do we want to
|
||||||
|
// provide for these esoteric extensions?
|
||||||
|
ExtType = 4
|
||||||
|
// ExtPayloadOffset returns the offset of the packet payload, or
|
||||||
|
// the first protocol header that the kernel does not know how to
|
||||||
|
// parse.
|
||||||
|
ExtPayloadOffset = 52
|
||||||
|
// ExtInterfaceIndex returns the index of the interface on which
|
||||||
|
// the packet was received.
|
||||||
|
ExtInterfaceIndex = 8
|
||||||
|
// ExtNetlinkAttr returns the netlink attribute of type X at
|
||||||
|
// offset A.
|
||||||
|
ExtNetlinkAttr = 12
|
||||||
|
// ExtNetlinkAttrNested returns the nested netlink attribute of
|
||||||
|
// type X at offset A.
|
||||||
|
ExtNetlinkAttrNested = 16
|
||||||
|
// ExtMark returns the packet's mark value.
|
||||||
|
ExtMark = 20
|
||||||
|
// ExtQueue returns the packet's assigned hardware queue.
|
||||||
|
ExtQueue = 24
|
||||||
|
// ExtLinkLayerType returns the packet's hardware address type
|
||||||
|
// (e.g. Ethernet, Infiniband).
|
||||||
|
ExtLinkLayerType = 28
|
||||||
|
// ExtRXHash returns the packets receive hash.
|
||||||
|
//
|
||||||
|
// TODO: figure out what this rxhash actually is.
|
||||||
|
ExtRXHash = 32
|
||||||
|
// ExtCPUID returns the ID of the CPU processing the current
|
||||||
|
// packet.
|
||||||
|
ExtCPUID = 36
|
||||||
|
// ExtVLANTag returns the packet's VLAN tag.
|
||||||
|
ExtVLANTag = 44
|
||||||
|
// ExtVLANTagPresent returns non-zero if the packet has a VLAN
|
||||||
|
// tag.
|
||||||
|
//
|
||||||
|
// TODO: I think this might be a lie: it reads bit 0x1000 of the
|
||||||
|
// VLAN header, which changed meaning in recent revisions of the
|
||||||
|
// spec - this extension may now return meaningless information.
|
||||||
|
ExtVLANTagPresent = 48
|
||||||
|
// ExtVLANProto returns 0x8100 if the frame has a VLAN header,
|
||||||
|
// 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some
|
||||||
|
// other value if no VLAN information is present.
|
||||||
|
ExtVLANProto = 60
|
||||||
|
// ExtRand returns a uniformly random uint32.
|
||||||
|
ExtRand = 56
|
||||||
|
)
|
||||||
|
|
||||||
|
// The following gives names to various bit patterns used in opcode construction.
|
||||||
|
|
||||||
|
const (
|
||||||
|
opMaskCls uint16 = 0x7
|
||||||
|
// opClsLoad masks
|
||||||
|
opMaskLoadDest = 0x01
|
||||||
|
opMaskLoadWidth = 0x18
|
||||||
|
opMaskLoadMode = 0xe0
|
||||||
|
// opClsALU
|
||||||
|
opMaskOperandSrc = 0x08
|
||||||
|
opMaskOperator = 0xf0
|
||||||
|
// opClsJump
|
||||||
|
opMaskJumpConst = 0x0f
|
||||||
|
opMaskJumpCond = 0xf0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 0 |
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
opClsLoadA uint16 = iota
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 |
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
opClsLoadX
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
opClsStoreA
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
opClsStoreX
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
// | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 |
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
opClsALU
|
||||||
|
// +-----------------------------+---+---+---+---+
|
||||||
|
// | TestOperator (4b) | 0 | 1 | 0 | 1 |
|
||||||
|
// +-----------------------------+---+---+---+---+
|
||||||
|
opClsJump
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | RetSrc (1b) | 0 | 1 | 1 | 0 |
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
opClsReturn
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | TXAorTAX (1b) | 0 | 1 | 1 | 1 |
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
opClsMisc
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddrModeImmediate uint16 = iota << 5
|
||||||
|
opAddrModeAbsolute
|
||||||
|
opAddrModeIndirect
|
||||||
|
opAddrModeScratch
|
||||||
|
opAddrModePacketLen // actually an extension, not an addressing mode.
|
||||||
|
opAddrModeMemShift
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opLoadWidth4 uint16 = iota << 3
|
||||||
|
opLoadWidth2
|
||||||
|
opLoadWidth1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Operator defined by ALUOp*
|
||||||
|
|
||||||
|
const (
|
||||||
|
opALUSrcConstant uint16 = iota << 3
|
||||||
|
opALUSrcX
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opJumpAlways = iota << 4
|
||||||
|
opJumpEqual
|
||||||
|
opJumpGT
|
||||||
|
opJumpGE
|
||||||
|
opJumpSet
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opRetSrcConstant uint16 = iota << 4
|
||||||
|
opRetSrcA
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opMiscTAX = 0x00
|
||||||
|
opMiscTXA = 0x80
|
||||||
|
)
|
82
vendor/golang.org/x/net/bpf/doc.go
generated
vendored
Normal file
82
vendor/golang.org/x/net/bpf/doc.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2016 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 bpf implements marshaling and unmarshaling of programs for the
|
||||||
|
Berkeley Packet Filter virtual machine, and provides a Go implementation
|
||||||
|
of the virtual machine.
|
||||||
|
|
||||||
|
BPF's main use is to specify a packet filter for network taps, so that
|
||||||
|
the kernel doesn't have to expensively copy every packet it sees to
|
||||||
|
userspace. However, it's been repurposed to other areas where running
|
||||||
|
user code in-kernel is needed. For example, Linux's seccomp uses BPF
|
||||||
|
to apply security policies to system calls. For simplicity, this
|
||||||
|
documentation refers only to packets, but other uses of BPF have their
|
||||||
|
own data payloads.
|
||||||
|
|
||||||
|
BPF programs run in a restricted virtual machine. It has almost no
|
||||||
|
access to kernel functions, and while conditional branches are
|
||||||
|
allowed, they can only jump forwards, to guarantee that there are no
|
||||||
|
infinite loops.
|
||||||
|
|
||||||
|
The virtual machine
|
||||||
|
|
||||||
|
The BPF VM is an accumulator machine. Its main register, called
|
||||||
|
register A, is an implicit source and destination in all arithmetic
|
||||||
|
and logic operations. The machine also has 16 scratch registers for
|
||||||
|
temporary storage, and an indirection register (register X) for
|
||||||
|
indirect memory access. All registers are 32 bits wide.
|
||||||
|
|
||||||
|
Each run of a BPF program is given one packet, which is placed in the
|
||||||
|
VM's read-only "main memory". LoadAbsolute and LoadIndirect
|
||||||
|
instructions can fetch up to 32 bits at a time into register A for
|
||||||
|
examination.
|
||||||
|
|
||||||
|
The goal of a BPF program is to produce and return a verdict (uint32),
|
||||||
|
which tells the kernel what to do with the packet. In the context of
|
||||||
|
packet filtering, the returned value is the number of bytes of the
|
||||||
|
packet to forward to userspace, or 0 to ignore the packet. Other
|
||||||
|
contexts like seccomp define their own return values.
|
||||||
|
|
||||||
|
In order to simplify programs, attempts to read past the end of the
|
||||||
|
packet terminate the program execution with a verdict of 0 (ignore
|
||||||
|
packet). This means that the vast majority of BPF programs don't need
|
||||||
|
to do any explicit bounds checking.
|
||||||
|
|
||||||
|
In addition to the bytes of the packet, some BPF programs have access
|
||||||
|
to extensions, which are essentially calls to kernel utility
|
||||||
|
functions. Currently, the only extensions supported by this package
|
||||||
|
are the Linux packet filter extensions.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
This packet filter selects all ARP packets.
|
||||||
|
|
||||||
|
bpf.Assemble([]bpf.Instruction{
|
||||||
|
// Load "EtherType" field from the ethernet header.
|
||||||
|
bpf.LoadAbsolute{Off: 12, Size: 2},
|
||||||
|
// Skip over the next instruction if EtherType is not ARP.
|
||||||
|
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
|
||||||
|
// Verdict is "send up to 4k of the packet to userspace."
|
||||||
|
bpf.RetConstant{Val: 4096},
|
||||||
|
// Verdict is "ignore packet."
|
||||||
|
bpf.RetConstant{Val: 0},
|
||||||
|
})
|
||||||
|
|
||||||
|
This packet filter captures a random 1% sample of traffic.
|
||||||
|
|
||||||
|
bpf.Assemble([]bpf.Instruction{
|
||||||
|
// Get a 32-bit random number from the Linux kernel.
|
||||||
|
bpf.LoadExtension{Num: bpf.ExtRand},
|
||||||
|
// 1% dice roll?
|
||||||
|
bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1},
|
||||||
|
// Capture.
|
||||||
|
bpf.RetConstant{Val: 4096},
|
||||||
|
// Ignore.
|
||||||
|
bpf.RetConstant{Val: 0},
|
||||||
|
})
|
||||||
|
|
||||||
|
*/
|
||||||
|
package bpf // import "golang.org/x/net/bpf"
|
434
vendor/golang.org/x/net/bpf/instructions.go
generated
vendored
Normal file
434
vendor/golang.org/x/net/bpf/instructions.go
generated
vendored
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
// Copyright 2016 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 bpf
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// An Instruction is one instruction executed by the BPF virtual
|
||||||
|
// machine.
|
||||||
|
type Instruction interface {
|
||||||
|
// Assemble assembles the Instruction into a RawInstruction.
|
||||||
|
Assemble() (RawInstruction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RawInstruction is a raw BPF virtual machine instruction.
|
||||||
|
type RawInstruction struct {
|
||||||
|
// Operation to execute.
|
||||||
|
Op uint16
|
||||||
|
// For conditional jump instructions, the number of instructions
|
||||||
|
// to skip if the condition is true/false.
|
||||||
|
Jt uint8
|
||||||
|
Jf uint8
|
||||||
|
// Constant parameter. The meaning depends on the Op.
|
||||||
|
K uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
|
||||||
|
|
||||||
|
// Disassemble parses ri into an Instruction and returns it. If ri is
|
||||||
|
// not recognized by this package, ri itself is returned.
|
||||||
|
func (ri RawInstruction) Disassemble() Instruction {
|
||||||
|
switch ri.Op & opMaskCls {
|
||||||
|
case opClsLoadA, opClsLoadX:
|
||||||
|
reg := Register(ri.Op & opMaskLoadDest)
|
||||||
|
sz := 0
|
||||||
|
switch ri.Op & opMaskLoadWidth {
|
||||||
|
case opLoadWidth4:
|
||||||
|
sz = 4
|
||||||
|
case opLoadWidth2:
|
||||||
|
sz = 2
|
||||||
|
case opLoadWidth1:
|
||||||
|
sz = 1
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
switch ri.Op & opMaskLoadMode {
|
||||||
|
case opAddrModeImmediate:
|
||||||
|
if sz != 4 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return LoadConstant{Dst: reg, Val: ri.K}
|
||||||
|
case opAddrModeScratch:
|
||||||
|
if sz != 4 || ri.K > 15 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return LoadScratch{Dst: reg, N: int(ri.K)}
|
||||||
|
case opAddrModeAbsolute:
|
||||||
|
return LoadAbsolute{Size: sz, Off: ri.K}
|
||||||
|
case opAddrModeIndirect:
|
||||||
|
return LoadIndirect{Size: sz, Off: ri.K}
|
||||||
|
case opAddrModePacketLen:
|
||||||
|
if sz != 4 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return LoadExtension{Num: ExtLen}
|
||||||
|
case opAddrModeMemShift:
|
||||||
|
return LoadMemShift{Off: ri.K}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsStoreA:
|
||||||
|
if ri.Op != opClsStoreA || ri.K > 15 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return StoreScratch{Src: RegA, N: int(ri.K)}
|
||||||
|
|
||||||
|
case opClsStoreX:
|
||||||
|
if ri.Op != opClsStoreX || ri.K > 15 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return StoreScratch{Src: RegX, N: int(ri.K)}
|
||||||
|
|
||||||
|
case opClsALU:
|
||||||
|
switch op := ALUOp(ri.Op & opMaskOperator); op {
|
||||||
|
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
|
||||||
|
if ri.Op&opMaskOperandSrc != 0 {
|
||||||
|
return ALUOpX{Op: op}
|
||||||
|
}
|
||||||
|
return ALUOpConstant{Op: op, Val: ri.K}
|
||||||
|
case aluOpNeg:
|
||||||
|
return NegateA{}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsJump:
|
||||||
|
if ri.Op&opMaskJumpConst != opClsJump {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
switch ri.Op & opMaskJumpCond {
|
||||||
|
case opJumpAlways:
|
||||||
|
return Jump{Skip: ri.K}
|
||||||
|
case opJumpEqual:
|
||||||
|
return JumpIf{
|
||||||
|
Cond: JumpEqual,
|
||||||
|
Val: ri.K,
|
||||||
|
SkipTrue: ri.Jt,
|
||||||
|
SkipFalse: ri.Jf,
|
||||||
|
}
|
||||||
|
case opJumpGT:
|
||||||
|
return JumpIf{
|
||||||
|
Cond: JumpGreaterThan,
|
||||||
|
Val: ri.K,
|
||||||
|
SkipTrue: ri.Jt,
|
||||||
|
SkipFalse: ri.Jf,
|
||||||
|
}
|
||||||
|
case opJumpGE:
|
||||||
|
return JumpIf{
|
||||||
|
Cond: JumpGreaterOrEqual,
|
||||||
|
Val: ri.K,
|
||||||
|
SkipTrue: ri.Jt,
|
||||||
|
SkipFalse: ri.Jf,
|
||||||
|
}
|
||||||
|
case opJumpSet:
|
||||||
|
return JumpIf{
|
||||||
|
Cond: JumpBitsSet,
|
||||||
|
Val: ri.K,
|
||||||
|
SkipTrue: ri.Jt,
|
||||||
|
SkipFalse: ri.Jf,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsReturn:
|
||||||
|
switch ri.Op {
|
||||||
|
case opClsReturn | opRetSrcA:
|
||||||
|
return RetA{}
|
||||||
|
case opClsReturn | opRetSrcConstant:
|
||||||
|
return RetConstant{Val: ri.K}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsMisc:
|
||||||
|
switch ri.Op {
|
||||||
|
case opClsMisc | opMiscTAX:
|
||||||
|
return TAX{}
|
||||||
|
case opClsMisc | opMiscTXA:
|
||||||
|
return TXA{}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unreachable") // switch is exhaustive on the bit pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConstant loads Val into register Dst.
|
||||||
|
type LoadConstant struct {
|
||||||
|
Dst Register
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadConstant) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadScratch loads scratch[N] into register Dst.
|
||||||
|
type LoadScratch struct {
|
||||||
|
Dst Register
|
||||||
|
N int // 0-15
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadScratch) Assemble() (RawInstruction, error) {
|
||||||
|
if a.N < 0 || a.N > 15 {
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||||
|
}
|
||||||
|
return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAbsolute loads packet[Off:Off+Size] as an integer value into
|
||||||
|
// register A.
|
||||||
|
type LoadAbsolute struct {
|
||||||
|
Off uint32
|
||||||
|
Size int // 1, 2 or 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadAbsolute) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
|
||||||
|
// into register A.
|
||||||
|
type LoadIndirect struct {
|
||||||
|
Off uint32
|
||||||
|
Size int // 1, 2 or 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadIndirect) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
|
||||||
|
// by 4 and stores the result in register X.
|
||||||
|
//
|
||||||
|
// This instruction is mainly useful to load into X the length of an
|
||||||
|
// IPv4 packet header in a single instruction, rather than have to do
|
||||||
|
// the arithmetic on the header's first byte by hand.
|
||||||
|
type LoadMemShift struct {
|
||||||
|
Off uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadMemShift) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadExtension invokes a linux-specific extension and stores the
|
||||||
|
// result in register A.
|
||||||
|
type LoadExtension struct {
|
||||||
|
Num Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadExtension) Assemble() (RawInstruction, error) {
|
||||||
|
if a.Num == ExtLen {
|
||||||
|
return assembleLoad(RegA, 4, opAddrModePacketLen, 0)
|
||||||
|
}
|
||||||
|
return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(-0x1000+a.Num))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreScratch stores register Src into scratch[N].
|
||||||
|
type StoreScratch struct {
|
||||||
|
Src Register
|
||||||
|
N int // 0-15
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a StoreScratch) Assemble() (RawInstruction, error) {
|
||||||
|
if a.N < 0 || a.N > 15 {
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||||
|
}
|
||||||
|
var op uint16
|
||||||
|
switch a.Src {
|
||||||
|
case RegA:
|
||||||
|
op = opClsStoreA
|
||||||
|
case RegX:
|
||||||
|
op = opClsStoreX
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return RawInstruction{
|
||||||
|
Op: op,
|
||||||
|
K: uint32(a.N),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALUOpConstant executes A = A <Op> Val.
|
||||||
|
type ALUOpConstant struct {
|
||||||
|
Op ALUOp
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a ALUOpConstant) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsALU | opALUSrcConstant | uint16(a.Op),
|
||||||
|
K: a.Val,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALUOpX executes A = A <Op> X
|
||||||
|
type ALUOpX struct {
|
||||||
|
Op ALUOp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a ALUOpX) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsALU | opALUSrcX | uint16(a.Op),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegateA executes A = -A.
|
||||||
|
type NegateA struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a NegateA) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsALU | uint16(aluOpNeg),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump skips the following Skip instructions in the program.
|
||||||
|
type Jump struct {
|
||||||
|
Skip uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a Jump) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsJump | opJumpAlways,
|
||||||
|
K: a.Skip,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JumpIf skips the following Skip instructions in the program if A
|
||||||
|
// <Cond> Val is true.
|
||||||
|
type JumpIf struct {
|
||||||
|
Cond JumpTest
|
||||||
|
Val uint32
|
||||||
|
SkipTrue uint8
|
||||||
|
SkipFalse uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a JumpIf) Assemble() (RawInstruction, error) {
|
||||||
|
var (
|
||||||
|
cond uint16
|
||||||
|
flip bool
|
||||||
|
)
|
||||||
|
switch a.Cond {
|
||||||
|
case JumpEqual:
|
||||||
|
cond = opJumpEqual
|
||||||
|
case JumpNotEqual:
|
||||||
|
cond, flip = opJumpEqual, true
|
||||||
|
case JumpGreaterThan:
|
||||||
|
cond = opJumpGT
|
||||||
|
case JumpLessThan:
|
||||||
|
cond, flip = opJumpGE, true
|
||||||
|
case JumpGreaterOrEqual:
|
||||||
|
cond = opJumpGE
|
||||||
|
case JumpLessOrEqual:
|
||||||
|
cond, flip = opJumpGT, true
|
||||||
|
case JumpBitsSet:
|
||||||
|
cond = opJumpSet
|
||||||
|
case JumpBitsNotSet:
|
||||||
|
cond, flip = opJumpSet, true
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", a.Cond)
|
||||||
|
}
|
||||||
|
jt, jf := a.SkipTrue, a.SkipFalse
|
||||||
|
if flip {
|
||||||
|
jt, jf = jf, jt
|
||||||
|
}
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsJump | cond,
|
||||||
|
Jt: jt,
|
||||||
|
Jf: jf,
|
||||||
|
K: a.Val,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetA exits the BPF program, returning the value of register A.
|
||||||
|
type RetA struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a RetA) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsReturn | opRetSrcA,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetConstant exits the BPF program, returning a constant value.
|
||||||
|
type RetConstant struct {
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a RetConstant) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsReturn | opRetSrcConstant,
|
||||||
|
K: a.Val,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TXA copies the value of register X to register A.
|
||||||
|
type TXA struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a TXA) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsMisc | opMiscTXA,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TAX copies the value of register A to register X.
|
||||||
|
type TAX struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a TAX) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsMisc | opMiscTAX,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
|
||||||
|
var (
|
||||||
|
cls uint16
|
||||||
|
sz uint16
|
||||||
|
)
|
||||||
|
switch dst {
|
||||||
|
case RegA:
|
||||||
|
cls = opClsLoadA
|
||||||
|
case RegX:
|
||||||
|
cls = opClsLoadX
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid target register %v", dst)
|
||||||
|
}
|
||||||
|
switch loadSize {
|
||||||
|
case 1:
|
||||||
|
sz = opLoadWidth1
|
||||||
|
case 2:
|
||||||
|
sz = opLoadWidth2
|
||||||
|
case 4:
|
||||||
|
sz = opLoadWidth4
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz)
|
||||||
|
}
|
||||||
|
return RawInstruction{
|
||||||
|
Op: cls | sz | mode,
|
||||||
|
K: k,
|
||||||
|
}, nil
|
||||||
|
}
|
184
vendor/golang.org/x/net/bpf/instructions_test.go
generated
vendored
Normal file
184
vendor/golang.org/x/net/bpf/instructions_test.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Copyright 2016 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 bpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a direct translation of the program in
|
||||||
|
// testdata/all_instructions.txt.
|
||||||
|
var allInstructions = []Instruction{
|
||||||
|
LoadConstant{Dst: RegA, Val: 42},
|
||||||
|
LoadConstant{Dst: RegX, Val: 42},
|
||||||
|
|
||||||
|
LoadScratch{Dst: RegA, N: 3},
|
||||||
|
LoadScratch{Dst: RegX, N: 3},
|
||||||
|
|
||||||
|
LoadAbsolute{Off: 42, Size: 1},
|
||||||
|
LoadAbsolute{Off: 42, Size: 2},
|
||||||
|
LoadAbsolute{Off: 42, Size: 4},
|
||||||
|
|
||||||
|
LoadIndirect{Off: 42, Size: 1},
|
||||||
|
LoadIndirect{Off: 42, Size: 2},
|
||||||
|
LoadIndirect{Off: 42, Size: 4},
|
||||||
|
|
||||||
|
LoadMemShift{Off: 42},
|
||||||
|
|
||||||
|
LoadExtension{Num: ExtLen},
|
||||||
|
LoadExtension{Num: ExtProto},
|
||||||
|
LoadExtension{Num: ExtType},
|
||||||
|
LoadExtension{Num: ExtRand},
|
||||||
|
|
||||||
|
StoreScratch{Src: RegA, N: 3},
|
||||||
|
StoreScratch{Src: RegX, N: 3},
|
||||||
|
|
||||||
|
ALUOpConstant{Op: ALUOpAdd, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpSub, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpMul, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpDiv, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpOr, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpAnd, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpShiftLeft, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpShiftRight, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpMod, Val: 42},
|
||||||
|
ALUOpConstant{Op: ALUOpXor, Val: 42},
|
||||||
|
|
||||||
|
ALUOpX{Op: ALUOpAdd},
|
||||||
|
ALUOpX{Op: ALUOpSub},
|
||||||
|
ALUOpX{Op: ALUOpMul},
|
||||||
|
ALUOpX{Op: ALUOpDiv},
|
||||||
|
ALUOpX{Op: ALUOpOr},
|
||||||
|
ALUOpX{Op: ALUOpAnd},
|
||||||
|
ALUOpX{Op: ALUOpShiftLeft},
|
||||||
|
ALUOpX{Op: ALUOpShiftRight},
|
||||||
|
ALUOpX{Op: ALUOpMod},
|
||||||
|
ALUOpX{Op: ALUOpXor},
|
||||||
|
|
||||||
|
NegateA{},
|
||||||
|
|
||||||
|
Jump{Skip: 10},
|
||||||
|
JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9},
|
||||||
|
JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8},
|
||||||
|
JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7},
|
||||||
|
JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6},
|
||||||
|
JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5},
|
||||||
|
JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4},
|
||||||
|
JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
|
||||||
|
|
||||||
|
TAX{},
|
||||||
|
TXA{},
|
||||||
|
|
||||||
|
RetA{},
|
||||||
|
RetConstant{Val: 42},
|
||||||
|
}
|
||||||
|
var allInstructionsExpected = "testdata/all_instructions.bpf"
|
||||||
|
|
||||||
|
// Check that we produce the same output as the canonical bpf_asm
|
||||||
|
// linux kernel tool.
|
||||||
|
func TestInterop(t *testing.T) {
|
||||||
|
out, err := Assemble(allInstructions)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assembly of allInstructions program failed: %s", err)
|
||||||
|
}
|
||||||
|
t.Logf("Assembled program is %d instructions long", len(out))
|
||||||
|
|
||||||
|
bs, err := ioutil.ReadFile(allInstructionsExpected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reading %s: %s", allInstructionsExpected, err)
|
||||||
|
}
|
||||||
|
// First statement is the number of statements, last statement is
|
||||||
|
// empty. We just ignore both and rely on slice length.
|
||||||
|
stmts := strings.Split(string(bs), ",")
|
||||||
|
if len(stmts)-2 != len(out) {
|
||||||
|
t.Fatalf("test program lengths don't match: %s has %d, Go implementation has %d", allInstructionsExpected, len(stmts)-2, len(allInstructions))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, stmt := range stmts[1 : len(stmts)-2] {
|
||||||
|
nums := strings.Split(stmt, " ")
|
||||||
|
if len(nums) != 4 {
|
||||||
|
t.Fatalf("malformed instruction %d in %s: %s", i+1, allInstructionsExpected, stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := out[i]
|
||||||
|
|
||||||
|
op, err := strconv.ParseUint(nums[0], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("malformed opcode %s in instruction %d of %s", nums[0], i+1, allInstructionsExpected)
|
||||||
|
}
|
||||||
|
if actual.Op != uint16(op) {
|
||||||
|
t.Errorf("opcode mismatch on instruction %d (%#v): got 0x%02x, want 0x%02x", i+1, allInstructions[i], actual.Op, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
jt, err := strconv.ParseUint(nums[1], 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("malformed jt offset %s in instruction %d of %s", nums[1], i+1, allInstructionsExpected)
|
||||||
|
}
|
||||||
|
if actual.Jt != uint8(jt) {
|
||||||
|
t.Errorf("jt mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.Jt, jt)
|
||||||
|
}
|
||||||
|
|
||||||
|
jf, err := strconv.ParseUint(nums[2], 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("malformed jf offset %s in instruction %d of %s", nums[2], i+1, allInstructionsExpected)
|
||||||
|
}
|
||||||
|
if actual.Jf != uint8(jf) {
|
||||||
|
t.Errorf("jf mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.Jf, jf)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := strconv.ParseUint(nums[3], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("malformed constant %s in instruction %d of %s", nums[3], i+1, allInstructionsExpected)
|
||||||
|
}
|
||||||
|
if actual.K != uint32(k) {
|
||||||
|
t.Errorf("constant mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.K, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that assembly and disassembly match each other.
|
||||||
|
//
|
||||||
|
// Because we offer "fake" jump conditions that don't appear in the
|
||||||
|
// machine code, disassembly won't be a 1:1 match with the original
|
||||||
|
// source, although the behavior will be identical. However,
|
||||||
|
// reassembling the disassembly should produce an identical program.
|
||||||
|
func TestAsmDisasm(t *testing.T) {
|
||||||
|
prog1, err := Assemble(allInstructions)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assembly of allInstructions program failed: %s", err)
|
||||||
|
}
|
||||||
|
t.Logf("Assembled program is %d instructions long", len(prog1))
|
||||||
|
|
||||||
|
src, allDecoded := Disassemble(prog1)
|
||||||
|
if !allDecoded {
|
||||||
|
t.Errorf("Disassemble(Assemble(allInstructions)) produced unrecognized instructions:")
|
||||||
|
for i, inst := range src {
|
||||||
|
if r, ok := inst.(RawInstruction); ok {
|
||||||
|
t.Logf(" insn %d, %#v --> %#v", i+1, allInstructions[i], r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prog2, err := Assemble(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assembly of Disassemble(Assemble(allInstructions)) failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prog2) != len(prog1) {
|
||||||
|
t.Fatalf("disassembly changed program size: %d insns before, %d insns after", len(prog1), len(prog2))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(prog1, prog2) {
|
||||||
|
t.Errorf("program mutated by disassembly:")
|
||||||
|
for i := range prog2 {
|
||||||
|
if !reflect.DeepEqual(prog1[i], prog2[i]) {
|
||||||
|
t.Logf(" insn %d, s: %#v, p1: %#v, p2: %#v", i+1, allInstructions[i], prog1[i], prog2[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
vendor/golang.org/x/net/bpf/testdata/all_instructions.bpf
generated
vendored
Normal file
1
vendor/golang.org/x/net/bpf/testdata/all_instructions.bpf
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
50,0 0 0 42,1 0 0 42,96 0 0 3,97 0 0 3,48 0 0 42,40 0 0 42,32 0 0 42,80 0 0 42,72 0 0 42,64 0 0 42,177 0 0 42,128 0 0 0,32 0 0 4294963200,32 0 0 4294963204,32 0 0 4294963256,2 0 0 3,3 0 0 3,4 0 0 42,20 0 0 42,36 0 0 42,52 0 0 42,68 0 0 42,84 0 0 42,100 0 0 42,116 0 0 42,148 0 0 42,164 0 0 42,12 0 0 0,28 0 0 0,44 0 0 0,60 0 0 0,76 0 0 0,92 0 0 0,108 0 0 0,124 0 0 0,156 0 0 0,172 0 0 0,132 0 0 0,5 0 0 10,21 8 9 42,21 0 8 42,53 0 7 42,37 0 6 42,37 4 5 42,53 3 4 42,69 2 3 42,7 0 0 0,135 0 0 0,22 0 0 0,6 0 0 0,
|
79
vendor/golang.org/x/net/bpf/testdata/all_instructions.txt
generated
vendored
Normal file
79
vendor/golang.org/x/net/bpf/testdata/all_instructions.txt
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# This filter is compiled to all_instructions.bpf by the `bpf_asm`
|
||||||
|
# tool, which can be found in the linux kernel source tree under
|
||||||
|
# tools/net.
|
||||||
|
|
||||||
|
# Load immediate
|
||||||
|
ld #42
|
||||||
|
ldx #42
|
||||||
|
|
||||||
|
# Load scratch
|
||||||
|
ld M[3]
|
||||||
|
ldx M[3]
|
||||||
|
|
||||||
|
# Load absolute
|
||||||
|
ldb [42]
|
||||||
|
ldh [42]
|
||||||
|
ld [42]
|
||||||
|
|
||||||
|
# Load indirect
|
||||||
|
ldb [x + 42]
|
||||||
|
ldh [x + 42]
|
||||||
|
ld [x + 42]
|
||||||
|
|
||||||
|
# Load IPv4 header length
|
||||||
|
ldx 4*([42]&0xf)
|
||||||
|
|
||||||
|
# Run extension function
|
||||||
|
ld #len
|
||||||
|
ld #proto
|
||||||
|
ld #type
|
||||||
|
ld #rand
|
||||||
|
|
||||||
|
# Store scratch
|
||||||
|
st M[3]
|
||||||
|
stx M[3]
|
||||||
|
|
||||||
|
# A <op> constant
|
||||||
|
add #42
|
||||||
|
sub #42
|
||||||
|
mul #42
|
||||||
|
div #42
|
||||||
|
or #42
|
||||||
|
and #42
|
||||||
|
lsh #42
|
||||||
|
rsh #42
|
||||||
|
mod #42
|
||||||
|
xor #42
|
||||||
|
|
||||||
|
# A <op> X
|
||||||
|
add x
|
||||||
|
sub x
|
||||||
|
mul x
|
||||||
|
div x
|
||||||
|
or x
|
||||||
|
and x
|
||||||
|
lsh x
|
||||||
|
rsh x
|
||||||
|
mod x
|
||||||
|
xor x
|
||||||
|
|
||||||
|
# !A
|
||||||
|
neg
|
||||||
|
|
||||||
|
# Jumps
|
||||||
|
ja end
|
||||||
|
jeq #42,prev,end
|
||||||
|
jne #42,end
|
||||||
|
jlt #42,end
|
||||||
|
jle #42,end
|
||||||
|
jgt #42,prev,end
|
||||||
|
jge #42,prev,end
|
||||||
|
jset #42,prev,end
|
||||||
|
|
||||||
|
# Register transfers
|
||||||
|
tax
|
||||||
|
txa
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
prev: ret a
|
||||||
|
end: ret #42
|
140
vendor/golang.org/x/net/bpf/vm.go
generated
vendored
Normal file
140
vendor/golang.org/x/net/bpf/vm.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2016 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 bpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A VM is an emulated BPF virtual machine.
|
||||||
|
type VM struct {
|
||||||
|
filter []Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVM returns a new VM using the input BPF program.
|
||||||
|
func NewVM(filter []Instruction) (*VM, error) {
|
||||||
|
if len(filter) == 0 {
|
||||||
|
return nil, errors.New("one or more Instructions must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ins := range filter {
|
||||||
|
check := len(filter) - (i + 1)
|
||||||
|
switch ins := ins.(type) {
|
||||||
|
// Check for out-of-bounds jumps in instructions
|
||||||
|
case Jump:
|
||||||
|
if check <= int(ins.Skip) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
|
||||||
|
}
|
||||||
|
case JumpIf:
|
||||||
|
if check <= int(ins.SkipTrue) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
||||||
|
}
|
||||||
|
if check <= int(ins.SkipFalse) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
||||||
|
}
|
||||||
|
// Check for division or modulus by zero
|
||||||
|
case ALUOpConstant:
|
||||||
|
if ins.Val != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ins.Op {
|
||||||
|
case ALUOpDiv, ALUOpMod:
|
||||||
|
return nil, errors.New("cannot divide by zero using ALUOpConstant")
|
||||||
|
}
|
||||||
|
// Check for unknown extensions
|
||||||
|
case LoadExtension:
|
||||||
|
switch ins.Num {
|
||||||
|
case ExtLen:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("extension %d not implemented", ins.Num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure last instruction is a return instruction
|
||||||
|
switch filter[len(filter)-1].(type) {
|
||||||
|
case RetA, RetConstant:
|
||||||
|
default:
|
||||||
|
return nil, errors.New("BPF program must end with RetA or RetConstant")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Though our VM works using disassembled instructions, we
|
||||||
|
// attempt to assemble the input filter anyway to ensure it is compatible
|
||||||
|
// with an operating system VM.
|
||||||
|
_, err := Assemble(filter)
|
||||||
|
|
||||||
|
return &VM{
|
||||||
|
filter: filter,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the VM's BPF program against the input bytes.
|
||||||
|
// Run returns the number of bytes accepted by the BPF program, and any errors
|
||||||
|
// which occurred while processing the program.
|
||||||
|
func (v *VM) Run(in []byte) (int, error) {
|
||||||
|
var (
|
||||||
|
// Registers of the virtual machine
|
||||||
|
regA uint32
|
||||||
|
regX uint32
|
||||||
|
regScratch [16]uint32
|
||||||
|
|
||||||
|
// OK is true if the program should continue processing the next
|
||||||
|
// instruction, or false if not, causing the loop to break
|
||||||
|
ok = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(mdlayher): implement:
|
||||||
|
// - NegateA:
|
||||||
|
// - would require a change from uint32 registers to int32
|
||||||
|
// registers
|
||||||
|
|
||||||
|
// TODO(mdlayher): add interop tests that check signedness of ALU
|
||||||
|
// operations against kernel implementation, and make sure Go
|
||||||
|
// implementation matches behavior
|
||||||
|
|
||||||
|
for i := 0; i < len(v.filter) && ok; i++ {
|
||||||
|
ins := v.filter[i]
|
||||||
|
|
||||||
|
switch ins := ins.(type) {
|
||||||
|
case ALUOpConstant:
|
||||||
|
regA = aluOpConstant(ins, regA)
|
||||||
|
case ALUOpX:
|
||||||
|
regA, ok = aluOpX(ins, regA, regX)
|
||||||
|
case Jump:
|
||||||
|
i += int(ins.Skip)
|
||||||
|
case JumpIf:
|
||||||
|
jump := jumpIf(ins, regA)
|
||||||
|
i += jump
|
||||||
|
case LoadAbsolute:
|
||||||
|
regA, ok = loadAbsolute(ins, in)
|
||||||
|
case LoadConstant:
|
||||||
|
regA, regX = loadConstant(ins, regA, regX)
|
||||||
|
case LoadExtension:
|
||||||
|
regA = loadExtension(ins, in)
|
||||||
|
case LoadIndirect:
|
||||||
|
regA, ok = loadIndirect(ins, in, regX)
|
||||||
|
case LoadMemShift:
|
||||||
|
regX, ok = loadMemShift(ins, in)
|
||||||
|
case LoadScratch:
|
||||||
|
regA, regX = loadScratch(ins, regScratch, regA, regX)
|
||||||
|
case RetA:
|
||||||
|
return int(regA), nil
|
||||||
|
case RetConstant:
|
||||||
|
return int(ins.Val), nil
|
||||||
|
case StoreScratch:
|
||||||
|
regScratch = storeScratch(ins, regScratch, regA, regX)
|
||||||
|
case TAX:
|
||||||
|
regX = regA
|
||||||
|
case TXA:
|
||||||
|
regA = regX
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
512
vendor/golang.org/x/net/bpf/vm_aluop_test.go
generated
vendored
Normal file
512
vendor/golang.org/x/net/bpf/vm_aluop_test.go
generated
vendored
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVMALUOpAdd(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpAdd,
|
||||||
|
Val: 3,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
8, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 3, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpSub(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.TAX{},
|
||||||
|
bpf.ALUOpX{
|
||||||
|
Op: bpf.ALUOpSub,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
1, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpMul(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpMul,
|
||||||
|
Val: 2,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
6, 2, 3, 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 4, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpDiv(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpDiv,
|
||||||
|
Val: 2,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
20, 2, 3, 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 2, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpDivByZeroALUOpConstant(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpDiv,
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "cannot divide by zero using ALUOpConstant" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpDivByZeroALUOpX(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
// Load byte 0 into X
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.TAX{},
|
||||||
|
// Load byte 1 into A
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 9,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Attempt to perform 1/0
|
||||||
|
bpf.ALUOpX{
|
||||||
|
Op: bpf.ALUOpDiv,
|
||||||
|
},
|
||||||
|
// Return 4 bytes if program does not terminate
|
||||||
|
bpf.LoadConstant{
|
||||||
|
Val: 12,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 3, 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpOr(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 2,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpOr,
|
||||||
|
Val: 0x01,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x00, 0x10, 0x03, 0x04,
|
||||||
|
0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0xff,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 9, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpAnd(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 2,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpAnd,
|
||||||
|
Val: 0x0019,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xaa, 0x09,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpShiftLeft(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpShiftLeft,
|
||||||
|
Val: 0x01,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 0x02,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x01, 0xaa,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpShiftRight(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpShiftRight,
|
||||||
|
Val: 0x01,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 0x04,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x08, 0xff, 0xff,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpMod(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpMod,
|
||||||
|
Val: 20,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
30, 0, 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 2, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpModByZeroALUOpConstant(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpMod,
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "cannot divide by zero using ALUOpConstant" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpModByZeroALUOpX(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
// Load byte 0 into X
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.TAX{},
|
||||||
|
// Load byte 1 into A
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 9,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Attempt to perform 1%0
|
||||||
|
bpf.ALUOpX{
|
||||||
|
Op: bpf.ALUOpMod,
|
||||||
|
},
|
||||||
|
// Return 4 bytes if program does not terminate
|
||||||
|
bpf.LoadConstant{
|
||||||
|
Val: 12,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 3, 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpXor(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpXor,
|
||||||
|
Val: 0x0a,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 0x01,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x0b, 0x00, 0x00, 0x00,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMALUOpUnknown(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: bpf.ALUOpAdd,
|
||||||
|
Val: 1,
|
||||||
|
},
|
||||||
|
// Verify that an unknown operation is a no-op
|
||||||
|
bpf.ALUOpConstant{
|
||||||
|
Op: 100,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 0x02,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
192
vendor/golang.org/x/net/bpf/vm_bpf_test.go
generated
vendored
Normal file
192
vendor/golang.org/x/net/bpf/vm_bpf_test.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A virtualMachine is a BPF virtual machine which can process an
|
||||||
|
// input packet against a BPF program and render a verdict.
|
||||||
|
type virtualMachine interface {
|
||||||
|
Run(in []byte) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// canUseOSVM indicates if the OS BPF VM is available on this platform.
|
||||||
|
func canUseOSVM() bool {
|
||||||
|
// OS BPF VM can only be used on platforms where x/net/ipv4 supports
|
||||||
|
// attaching a BPF program to a socket.
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// All BPF tests against both the Go VM and OS VM are assumed to
|
||||||
|
// be used with a UDP socket. As a result, the entire contents
|
||||||
|
// of a UDP datagram is sent through the BPF program, but only
|
||||||
|
// the body after the UDP header will ever be returned in output.
|
||||||
|
|
||||||
|
// testVM sets up a Go BPF VM, and if available, a native OS BPF VM
|
||||||
|
// for integration testing.
|
||||||
|
func testVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func(), error) {
|
||||||
|
goVM, err := bpf.NewVM(filter)
|
||||||
|
if err != nil {
|
||||||
|
// Some tests expect an error, so this error must be returned
|
||||||
|
// instead of fatally exiting the test
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mvm := &multiVirtualMachine{
|
||||||
|
goVM: goVM,
|
||||||
|
|
||||||
|
t: t,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If available, add the OS VM for tests which verify that both the Go
|
||||||
|
// VM and OS VM have exactly the same output for the same input program
|
||||||
|
// and packet.
|
||||||
|
done := func() {}
|
||||||
|
if canUseOSVM() {
|
||||||
|
osVM, osVMDone := testOSVM(t, filter)
|
||||||
|
done = func() { osVMDone() }
|
||||||
|
mvm.osVM = osVM
|
||||||
|
}
|
||||||
|
|
||||||
|
return mvm, done, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// udpHeaderLen is the length of a UDP header.
|
||||||
|
const udpHeaderLen = 8
|
||||||
|
|
||||||
|
// A multiVirtualMachine is a virtualMachine which can call out to both the Go VM
|
||||||
|
// and the native OS VM, if the OS VM is available.
|
||||||
|
type multiVirtualMachine struct {
|
||||||
|
goVM virtualMachine
|
||||||
|
osVM virtualMachine
|
||||||
|
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mvm *multiVirtualMachine) Run(in []byte) (int, error) {
|
||||||
|
if len(in) < udpHeaderLen {
|
||||||
|
mvm.t.Fatalf("input must be at least length of UDP header (%d), got: %d",
|
||||||
|
udpHeaderLen, len(in))
|
||||||
|
}
|
||||||
|
|
||||||
|
// All tests have a UDP header as part of input, because the OS VM
|
||||||
|
// packets always will. For the Go VM, this output is trimmed before
|
||||||
|
// being sent back to tests.
|
||||||
|
goOut, goErr := mvm.goVM.Run(in)
|
||||||
|
if goOut >= udpHeaderLen {
|
||||||
|
goOut -= udpHeaderLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Go output is larger than the size of the packet, packet filtering
|
||||||
|
// interop tests must trim the output bytes to the length of the packet.
|
||||||
|
// The BPF VM should not do this on its own, as other uses of it do
|
||||||
|
// not trim the output byte count.
|
||||||
|
trim := len(in) - udpHeaderLen
|
||||||
|
if goOut > trim {
|
||||||
|
goOut = trim
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the OS VM is not available, process using the Go VM alone
|
||||||
|
if mvm.osVM == nil {
|
||||||
|
return goOut, goErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// The OS VM will apply its own UDP header, so remove the pseudo header
|
||||||
|
// that the Go VM needs.
|
||||||
|
osOut, err := mvm.osVM.Run(in[udpHeaderLen:])
|
||||||
|
if err != nil {
|
||||||
|
mvm.t.Fatalf("error while running OS VM: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify both VMs return same number of bytes
|
||||||
|
var mismatch bool
|
||||||
|
if goOut != osOut {
|
||||||
|
mismatch = true
|
||||||
|
mvm.t.Logf("output byte count does not match:\n- go: %v\n- os: %v", goOut, osOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mismatch {
|
||||||
|
mvm.t.Fatal("Go BPF and OS BPF packet outputs do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
return goOut, goErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// An osVirtualMachine is a virtualMachine which uses the OS's BPF VM for
|
||||||
|
// processing BPF programs.
|
||||||
|
type osVirtualMachine struct {
|
||||||
|
l net.PacketConn
|
||||||
|
s net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// testOSVM creates a virtualMachine which uses the OS's BPF VM by injecting
|
||||||
|
// packets into a UDP listener with a BPF program attached to it.
|
||||||
|
func testOSVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func()) {
|
||||||
|
l, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open OS VM UDP listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prog, err := bpf.Assemble(filter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to compile BPF program: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := ipv4.NewPacketConn(l)
|
||||||
|
if err = p.SetBPF(prog); err != nil {
|
||||||
|
t.Fatalf("failed to attach BPF program to listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := net.Dial("udp4", l.LocalAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to dial connection to listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
done := func() {
|
||||||
|
_ = s.Close()
|
||||||
|
_ = l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &osVirtualMachine{
|
||||||
|
l: l,
|
||||||
|
s: s,
|
||||||
|
}, done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run sends the input bytes into the OS's BPF VM and returns its verdict.
|
||||||
|
func (vm *osVirtualMachine) Run(in []byte) (int, error) {
|
||||||
|
go func() {
|
||||||
|
_, _ = vm.s.Write(in)
|
||||||
|
}()
|
||||||
|
|
||||||
|
vm.l.SetDeadline(time.Now().Add(50 * time.Millisecond))
|
||||||
|
|
||||||
|
var b [512]byte
|
||||||
|
n, _, err := vm.l.ReadFrom(b[:])
|
||||||
|
if err != nil {
|
||||||
|
// A timeout indicates that BPF filtered out the packet, and thus,
|
||||||
|
// no input should be returned.
|
||||||
|
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
49
vendor/golang.org/x/net/bpf/vm_extension_test.go
generated
vendored
Normal file
49
vendor/golang.org/x/net/bpf/vm_extension_test.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVMLoadExtensionNotImplemented(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadExtension{
|
||||||
|
Num: 100,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "extension 100 not implemented" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadExtensionExtLen(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadExtension{
|
||||||
|
Num: bpf.ExtLen,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 4, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
174
vendor/golang.org/x/net/bpf/vm_instructions.go
generated
vendored
Normal file
174
vendor/golang.org/x/net/bpf/vm_instructions.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
// Copyright 2016 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 bpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
|
||||||
|
return aluOpCommon(ins.Op, regA, ins.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
|
||||||
|
// Guard against division or modulus by zero by terminating
|
||||||
|
// the program, as the OS BPF VM does
|
||||||
|
if regX == 0 {
|
||||||
|
switch ins.Op {
|
||||||
|
case ALUOpDiv, ALUOpMod:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aluOpCommon(ins.Op, regA, regX), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
|
||||||
|
switch op {
|
||||||
|
case ALUOpAdd:
|
||||||
|
return regA + value
|
||||||
|
case ALUOpSub:
|
||||||
|
return regA - value
|
||||||
|
case ALUOpMul:
|
||||||
|
return regA * value
|
||||||
|
case ALUOpDiv:
|
||||||
|
// Division by zero not permitted by NewVM and aluOpX checks
|
||||||
|
return regA / value
|
||||||
|
case ALUOpOr:
|
||||||
|
return regA | value
|
||||||
|
case ALUOpAnd:
|
||||||
|
return regA & value
|
||||||
|
case ALUOpShiftLeft:
|
||||||
|
return regA << value
|
||||||
|
case ALUOpShiftRight:
|
||||||
|
return regA >> value
|
||||||
|
case ALUOpMod:
|
||||||
|
// Modulus by zero not permitted by NewVM and aluOpX checks
|
||||||
|
return regA % value
|
||||||
|
case ALUOpXor:
|
||||||
|
return regA ^ value
|
||||||
|
default:
|
||||||
|
return regA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jumpIf(ins JumpIf, value uint32) int {
|
||||||
|
var ok bool
|
||||||
|
inV := uint32(ins.Val)
|
||||||
|
|
||||||
|
switch ins.Cond {
|
||||||
|
case JumpEqual:
|
||||||
|
ok = value == inV
|
||||||
|
case JumpNotEqual:
|
||||||
|
ok = value != inV
|
||||||
|
case JumpGreaterThan:
|
||||||
|
ok = value > inV
|
||||||
|
case JumpLessThan:
|
||||||
|
ok = value < inV
|
||||||
|
case JumpGreaterOrEqual:
|
||||||
|
ok = value >= inV
|
||||||
|
case JumpLessOrEqual:
|
||||||
|
ok = value <= inV
|
||||||
|
case JumpBitsSet:
|
||||||
|
ok = (value & inV) != 0
|
||||||
|
case JumpBitsNotSet:
|
||||||
|
ok = (value & inV) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return int(ins.SkipTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(ins.SkipFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
|
||||||
|
offset := int(ins.Off)
|
||||||
|
size := int(ins.Size)
|
||||||
|
|
||||||
|
return loadCommon(in, offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
|
||||||
|
switch ins.Dst {
|
||||||
|
case RegA:
|
||||||
|
regA = ins.Val
|
||||||
|
case RegX:
|
||||||
|
regX = ins.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
return regA, regX
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadExtension(ins LoadExtension, in []byte) uint32 {
|
||||||
|
switch ins.Num {
|
||||||
|
case ExtLen:
|
||||||
|
return uint32(len(in))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
|
||||||
|
offset := int(ins.Off) + int(regX)
|
||||||
|
size := int(ins.Size)
|
||||||
|
|
||||||
|
return loadCommon(in, offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
|
||||||
|
offset := int(ins.Off)
|
||||||
|
|
||||||
|
if !inBounds(len(in), offset, 0) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask off high 4 bits and multiply low 4 bits by 4
|
||||||
|
return uint32(in[offset]&0x0f) * 4, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func inBounds(inLen int, offset int, size int) bool {
|
||||||
|
return offset+size <= inLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCommon(in []byte, offset int, size int) (uint32, bool) {
|
||||||
|
if !inBounds(len(in), offset, size) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch size {
|
||||||
|
case 1:
|
||||||
|
return uint32(in[offset]), true
|
||||||
|
case 2:
|
||||||
|
return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
|
||||||
|
case 4:
|
||||||
|
return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid load size: %d", size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
|
||||||
|
switch ins.Dst {
|
||||||
|
case RegA:
|
||||||
|
regA = regScratch[ins.N]
|
||||||
|
case RegX:
|
||||||
|
regX = regScratch[ins.N]
|
||||||
|
}
|
||||||
|
|
||||||
|
return regA, regX
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
|
||||||
|
switch ins.Src {
|
||||||
|
case RegA:
|
||||||
|
regScratch[ins.N] = regA
|
||||||
|
case RegX:
|
||||||
|
regScratch[ins.N] = regX
|
||||||
|
}
|
||||||
|
|
||||||
|
return regScratch
|
||||||
|
}
|
380
vendor/golang.org/x/net/bpf/vm_jump_test.go
generated
vendored
Normal file
380
vendor/golang.org/x/net/bpf/vm_jump_test.go
generated
vendored
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVMJumpOne(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.Jump{
|
||||||
|
Skip: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpOutOfProgram(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.Jump{
|
||||||
|
Skip: 1,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "cannot jump 1 instructions; jumping past program bounds" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfTrueOutOfProgram(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
SkipTrue: 2,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "cannot jump 2 instructions in true case; jumping past program bounds" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfFalseOutOfProgram(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
SkipFalse: 3,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "cannot jump 3 instructions in false case; jumping past program bounds" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfEqual(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 1,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfNotEqual(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpNotEqual,
|
||||||
|
Val: 1,
|
||||||
|
SkipFalse: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfGreaterThan(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 4,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpGreaterThan,
|
||||||
|
Val: 0x00010202,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 12,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 4, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfLessThan(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 4,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpLessThan,
|
||||||
|
Val: 0xff010203,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 12,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 4, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfGreaterOrEqual(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 4,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpGreaterOrEqual,
|
||||||
|
Val: 0x00010203,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 12,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 4, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfLessOrEqual(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 4,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpLessOrEqual,
|
||||||
|
Val: 0xff010203,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 12,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 4, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfBitsSet(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 2,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpBitsSet,
|
||||||
|
Val: 0x1122,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 10,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x01, 0x02,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 2, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMJumpIfBitsNotSet(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 2,
|
||||||
|
},
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpBitsNotSet,
|
||||||
|
Val: 0x1221,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 10,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x01, 0x02,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 2, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
246
vendor/golang.org/x/net/bpf/vm_load_test.go
generated
vendored
Normal file
246
vendor/golang.org/x/net/bpf/vm_load_test.go
generated
vendored
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVMLoadAbsoluteOffsetOutOfBounds(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 100,
|
||||||
|
Size: 2,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadAbsoluteOffsetPlusSizeOutOfBounds(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 2,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadAbsoluteBadInstructionSize(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Size: 5,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "assembling instruction 1: invalid load byte length 0" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadConstantOK(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadConstant{
|
||||||
|
Dst: bpf.RegX,
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
bpf.TXA{},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadIndirectOutOfBounds(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadIndirect{
|
||||||
|
Off: 100,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadMemShiftOutOfBounds(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadMemShift{
|
||||||
|
Off: 100,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dhcp4Port = 53
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVMLoadMemShiftLoadIndirectNoResult(t *testing.T) {
|
||||||
|
vm, in, done := testDHCPv4(t)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
// Append mostly empty UDP header with incorrect DHCPv4 port
|
||||||
|
in = append(in, []byte{
|
||||||
|
0, 0,
|
||||||
|
0, dhcp4Port + 1,
|
||||||
|
0, 0,
|
||||||
|
0, 0,
|
||||||
|
}...)
|
||||||
|
|
||||||
|
out, err := vm.Run(in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 0, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadMemShiftLoadIndirectOK(t *testing.T) {
|
||||||
|
vm, in, done := testDHCPv4(t)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
// Append mostly empty UDP header with correct DHCPv4 port
|
||||||
|
in = append(in, []byte{
|
||||||
|
0, 0,
|
||||||
|
0, dhcp4Port,
|
||||||
|
0, 0,
|
||||||
|
0, 0,
|
||||||
|
}...)
|
||||||
|
|
||||||
|
out, err := vm.Run(in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := len(in)-8, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDHCPv4(t *testing.T) (virtualMachine, []byte, func()) {
|
||||||
|
// DHCPv4 test data courtesy of David Anderson:
|
||||||
|
// https://github.com/google/netboot/blob/master/dhcp4/conn_linux.go#L59-L70
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
// Load IPv4 packet length
|
||||||
|
bpf.LoadMemShift{Off: 8},
|
||||||
|
// Get UDP dport
|
||||||
|
bpf.LoadIndirect{Off: 8 + 2, Size: 2},
|
||||||
|
// Correct dport?
|
||||||
|
bpf.JumpIf{Cond: bpf.JumpEqual, Val: dhcp4Port, SkipFalse: 1},
|
||||||
|
// Accept
|
||||||
|
bpf.RetConstant{Val: 1500},
|
||||||
|
// Ignore
|
||||||
|
bpf.RetConstant{Val: 0},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal requirements to make a valid IPv4 header
|
||||||
|
h := &ipv4.Header{
|
||||||
|
Len: ipv4.HeaderLen,
|
||||||
|
Src: net.IPv4(192, 168, 1, 1),
|
||||||
|
Dst: net.IPv4(192, 168, 1, 2),
|
||||||
|
}
|
||||||
|
hb, err := h.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal IPv4 header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hb = append([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
}, hb...)
|
||||||
|
|
||||||
|
return vm, hb, done
|
||||||
|
}
|
115
vendor/golang.org/x/net/bpf/vm_ret_test.go
generated
vendored
Normal file
115
vendor/golang.org/x/net/bpf/vm_ret_test.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVMRetA(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
9,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMRetALargerThanInput(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 2,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 255,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 2, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMRetConstant(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 9,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 1, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMRetConstantLargerThanInput(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 16,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0, 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 2, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
247
vendor/golang.org/x/net/bpf/vm_scratch_test.go
generated
vendored
Normal file
247
vendor/golang.org/x/net/bpf/vm_scratch_test.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVMStoreScratchInvalidScratchRegisterTooSmall(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: bpf.RegA,
|
||||||
|
N: -1,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMStoreScratchInvalidScratchRegisterTooLarge(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: bpf.RegA,
|
||||||
|
N: 16,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMStoreScratchUnknownSourceRegister(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: 100,
|
||||||
|
N: 0,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "assembling instruction 1: invalid source register 100" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadScratchInvalidScratchRegisterTooSmall(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadScratch{
|
||||||
|
Dst: bpf.RegX,
|
||||||
|
N: -1,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadScratchInvalidScratchRegisterTooLarge(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadScratch{
|
||||||
|
Dst: bpf.RegX,
|
||||||
|
N: 16,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMLoadScratchUnknownDestinationRegister(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadScratch{
|
||||||
|
Dst: 100,
|
||||||
|
N: 0,
|
||||||
|
},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if errStr(err) != "assembling instruction 1: invalid target register 100" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMStoreScratchLoadScratchOneValue(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
// Load byte 255
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Copy to X and store in scratch[0]
|
||||||
|
bpf.TAX{},
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: bpf.RegX,
|
||||||
|
N: 0,
|
||||||
|
},
|
||||||
|
// Load byte 1
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 9,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Overwrite 1 with 255 from scratch[0]
|
||||||
|
bpf.LoadScratch{
|
||||||
|
Dst: bpf.RegA,
|
||||||
|
N: 0,
|
||||||
|
},
|
||||||
|
// Return 255
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
255, 1, 2,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 3, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMStoreScratchLoadScratchMultipleValues(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
// Load byte 10
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 8,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Store in scratch[0]
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: bpf.RegA,
|
||||||
|
N: 0,
|
||||||
|
},
|
||||||
|
// Load byte 20
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 9,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Store in scratch[1]
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: bpf.RegA,
|
||||||
|
N: 1,
|
||||||
|
},
|
||||||
|
// Load byte 30
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 10,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Store in scratch[2]
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: bpf.RegA,
|
||||||
|
N: 2,
|
||||||
|
},
|
||||||
|
// Load byte 1
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: 11,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
// Store in scratch[3]
|
||||||
|
bpf.StoreScratch{
|
||||||
|
Src: bpf.RegA,
|
||||||
|
N: 3,
|
||||||
|
},
|
||||||
|
// Load in byte 10 to X
|
||||||
|
bpf.LoadScratch{
|
||||||
|
Dst: bpf.RegX,
|
||||||
|
N: 0,
|
||||||
|
},
|
||||||
|
// Copy X -> A
|
||||||
|
bpf.TXA{},
|
||||||
|
// Verify value is 10
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 10,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
// Fail test if incorrect
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
// Load in byte 20 to A
|
||||||
|
bpf.LoadScratch{
|
||||||
|
Dst: bpf.RegA,
|
||||||
|
N: 1,
|
||||||
|
},
|
||||||
|
// Verify value is 20
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 20,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
// Fail test if incorrect
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
// Load in byte 30 to A
|
||||||
|
bpf.LoadScratch{
|
||||||
|
Dst: bpf.RegA,
|
||||||
|
N: 2,
|
||||||
|
},
|
||||||
|
// Verify value is 30
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: 30,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
// Fail test if incorrect
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
// Return first two bytes on success
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 10,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load BPF program: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
10, 20, 30, 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
if want, got := 2, out; want != got {
|
||||||
|
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
||||||
|
want, got)
|
||||||
|
}
|
||||||
|
}
|
144
vendor/golang.org/x/net/bpf/vm_test.go
generated
vendored
Normal file
144
vendor/golang.org/x/net/bpf/vm_test.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2016 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 bpf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ bpf.Instruction = unknown{}
|
||||||
|
|
||||||
|
type unknown struct{}
|
||||||
|
|
||||||
|
func (unknown) Assemble() (bpf.RawInstruction, error) {
|
||||||
|
return bpf.RawInstruction{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMUnknownInstruction(t *testing.T) {
|
||||||
|
vm, done, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadConstant{
|
||||||
|
Dst: bpf.RegA,
|
||||||
|
Val: 100,
|
||||||
|
},
|
||||||
|
// Should terminate the program with an error immediately
|
||||||
|
unknown{},
|
||||||
|
bpf.RetA{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
_, err = vm.Run([]byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x00, 0x00,
|
||||||
|
})
|
||||||
|
if errStr(err) != "unknown Instruction at index 1: bpf_test.unknown" {
|
||||||
|
t.Fatalf("unexpected error while running program: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMNoReturnInstruction(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{
|
||||||
|
bpf.LoadConstant{
|
||||||
|
Dst: bpf.RegA,
|
||||||
|
Val: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if errStr(err) != "BPF program must end with RetA or RetConstant" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMNoInputInstructions(t *testing.T) {
|
||||||
|
_, _, err := testVM(t, []bpf.Instruction{})
|
||||||
|
if errStr(err) != "one or more Instructions must be specified" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleNewVM demonstrates usage of a VM, using an Ethernet frame
|
||||||
|
// as input and checking its EtherType to determine if it should be accepted.
|
||||||
|
func ExampleNewVM() {
|
||||||
|
// Offset | Length | Comment
|
||||||
|
// -------------------------
|
||||||
|
// 00 | 06 | Ethernet destination MAC address
|
||||||
|
// 06 | 06 | Ethernet source MAC address
|
||||||
|
// 12 | 02 | Ethernet EtherType
|
||||||
|
const (
|
||||||
|
etOff = 12
|
||||||
|
etLen = 2
|
||||||
|
|
||||||
|
etARP = 0x0806
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set up a VM to filter traffic based on if its EtherType
|
||||||
|
// matches the ARP EtherType.
|
||||||
|
vm, err := bpf.NewVM([]bpf.Instruction{
|
||||||
|
// Load EtherType value from Ethernet header
|
||||||
|
bpf.LoadAbsolute{
|
||||||
|
Off: etOff,
|
||||||
|
Size: etLen,
|
||||||
|
},
|
||||||
|
// If EtherType is equal to the ARP EtherType, jump to allow
|
||||||
|
// packet to be accepted
|
||||||
|
bpf.JumpIf{
|
||||||
|
Cond: bpf.JumpEqual,
|
||||||
|
Val: etARP,
|
||||||
|
SkipTrue: 1,
|
||||||
|
},
|
||||||
|
// EtherType does not match the ARP EtherType
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 0,
|
||||||
|
},
|
||||||
|
// EtherType matches the ARP EtherType, accept up to 1500
|
||||||
|
// bytes of packet
|
||||||
|
bpf.RetConstant{
|
||||||
|
Val: 1500,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to load BPF program: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an Ethernet frame with the ARP EtherType for testing
|
||||||
|
frame := []byte{
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
|
||||||
|
0x08, 0x06,
|
||||||
|
// Payload omitted for brevity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run our VM's BPF program using the Ethernet frame as input
|
||||||
|
out, err := vm.Run(frame)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to accept Ethernet frame: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BPF VM can return a byte count greater than the number of input
|
||||||
|
// bytes, so trim the output to match the input byte length
|
||||||
|
if out > len(frame) {
|
||||||
|
out = len(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("out: %d bytes", out)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// out: 14 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// errStr returns the string representation of an error, or
|
||||||
|
// "<nil>" if it is nil.
|
||||||
|
func errStr(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
|
||||||
|
return err.Error()
|
||||||
|
}
|
180
vendor/golang.org/x/net/internal/iana/const.go
generated
vendored
Normal file
180
vendor/golang.org/x/net/internal/iana/const.go
generated
vendored
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
// go generate gen.go
|
||||||
|
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).
|
||||||
|
package iana // import "golang.org/x/net/internal/iana"
|
||||||
|
|
||||||
|
// Differentiated Services Field Codepoints (DSCP), Updated: 2013-06-25
|
||||||
|
const (
|
||||||
|
DiffServCS0 = 0x0 // CS0
|
||||||
|
DiffServCS1 = 0x20 // CS1
|
||||||
|
DiffServCS2 = 0x40 // CS2
|
||||||
|
DiffServCS3 = 0x60 // CS3
|
||||||
|
DiffServCS4 = 0x80 // CS4
|
||||||
|
DiffServCS5 = 0xa0 // CS5
|
||||||
|
DiffServCS6 = 0xc0 // CS6
|
||||||
|
DiffServCS7 = 0xe0 // CS7
|
||||||
|
DiffServAF11 = 0x28 // AF11
|
||||||
|
DiffServAF12 = 0x30 // AF12
|
||||||
|
DiffServAF13 = 0x38 // AF13
|
||||||
|
DiffServAF21 = 0x48 // AF21
|
||||||
|
DiffServAF22 = 0x50 // AF22
|
||||||
|
DiffServAF23 = 0x58 // AF23
|
||||||
|
DiffServAF31 = 0x68 // AF31
|
||||||
|
DiffServAF32 = 0x70 // AF32
|
||||||
|
DiffServAF33 = 0x78 // AF33
|
||||||
|
DiffServAF41 = 0x88 // AF41
|
||||||
|
DiffServAF42 = 0x90 // AF42
|
||||||
|
DiffServAF43 = 0x98 // AF43
|
||||||
|
DiffServEFPHB = 0xb8 // EF PHB
|
||||||
|
DiffServVOICEADMIT = 0xb0 // VOICE-ADMIT
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPv4 TOS Byte and IPv6 Traffic Class Octet, Updated: 2001-09-06
|
||||||
|
const (
|
||||||
|
NotECNTransport = 0x0 // Not-ECT (Not ECN-Capable Transport)
|
||||||
|
ECNTransport1 = 0x1 // ECT(1) (ECN-Capable Transport(1))
|
||||||
|
ECNTransport0 = 0x2 // ECT(0) (ECN-Capable Transport(0))
|
||||||
|
CongestionExperienced = 0x3 // CE (Congestion Experienced)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Protocol Numbers, Updated: 2015-10-06
|
||||||
|
const (
|
||||||
|
ProtocolIP = 0 // IPv4 encapsulation, pseudo protocol number
|
||||||
|
ProtocolHOPOPT = 0 // IPv6 Hop-by-Hop Option
|
||||||
|
ProtocolICMP = 1 // Internet Control Message
|
||||||
|
ProtocolIGMP = 2 // Internet Group Management
|
||||||
|
ProtocolGGP = 3 // Gateway-to-Gateway
|
||||||
|
ProtocolIPv4 = 4 // IPv4 encapsulation
|
||||||
|
ProtocolST = 5 // Stream
|
||||||
|
ProtocolTCP = 6 // Transmission Control
|
||||||
|
ProtocolCBT = 7 // CBT
|
||||||
|
ProtocolEGP = 8 // Exterior Gateway Protocol
|
||||||
|
ProtocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
|
||||||
|
ProtocolBBNRCCMON = 10 // BBN RCC Monitoring
|
||||||
|
ProtocolNVPII = 11 // Network Voice Protocol
|
||||||
|
ProtocolPUP = 12 // PUP
|
||||||
|
ProtocolEMCON = 14 // EMCON
|
||||||
|
ProtocolXNET = 15 // Cross Net Debugger
|
||||||
|
ProtocolCHAOS = 16 // Chaos
|
||||||
|
ProtocolUDP = 17 // User Datagram
|
||||||
|
ProtocolMUX = 18 // Multiplexing
|
||||||
|
ProtocolDCNMEAS = 19 // DCN Measurement Subsystems
|
||||||
|
ProtocolHMP = 20 // Host Monitoring
|
||||||
|
ProtocolPRM = 21 // Packet Radio Measurement
|
||||||
|
ProtocolXNSIDP = 22 // XEROX NS IDP
|
||||||
|
ProtocolTRUNK1 = 23 // Trunk-1
|
||||||
|
ProtocolTRUNK2 = 24 // Trunk-2
|
||||||
|
ProtocolLEAF1 = 25 // Leaf-1
|
||||||
|
ProtocolLEAF2 = 26 // Leaf-2
|
||||||
|
ProtocolRDP = 27 // Reliable Data Protocol
|
||||||
|
ProtocolIRTP = 28 // Internet Reliable Transaction
|
||||||
|
ProtocolISOTP4 = 29 // ISO Transport Protocol Class 4
|
||||||
|
ProtocolNETBLT = 30 // Bulk Data Transfer Protocol
|
||||||
|
ProtocolMFENSP = 31 // MFE Network Services Protocol
|
||||||
|
ProtocolMERITINP = 32 // MERIT Internodal Protocol
|
||||||
|
ProtocolDCCP = 33 // Datagram Congestion Control Protocol
|
||||||
|
Protocol3PC = 34 // Third Party Connect Protocol
|
||||||
|
ProtocolIDPR = 35 // Inter-Domain Policy Routing Protocol
|
||||||
|
ProtocolXTP = 36 // XTP
|
||||||
|
ProtocolDDP = 37 // Datagram Delivery Protocol
|
||||||
|
ProtocolIDPRCMTP = 38 // IDPR Control Message Transport Proto
|
||||||
|
ProtocolTPPP = 39 // TP++ Transport Protocol
|
||||||
|
ProtocolIL = 40 // IL Transport Protocol
|
||||||
|
ProtocolIPv6 = 41 // IPv6 encapsulation
|
||||||
|
ProtocolSDRP = 42 // Source Demand Routing Protocol
|
||||||
|
ProtocolIPv6Route = 43 // Routing Header for IPv6
|
||||||
|
ProtocolIPv6Frag = 44 // Fragment Header for IPv6
|
||||||
|
ProtocolIDRP = 45 // Inter-Domain Routing Protocol
|
||||||
|
ProtocolRSVP = 46 // Reservation Protocol
|
||||||
|
ProtocolGRE = 47 // Generic Routing Encapsulation
|
||||||
|
ProtocolDSR = 48 // Dynamic Source Routing Protocol
|
||||||
|
ProtocolBNA = 49 // BNA
|
||||||
|
ProtocolESP = 50 // Encap Security Payload
|
||||||
|
ProtocolAH = 51 // Authentication Header
|
||||||
|
ProtocolINLSP = 52 // Integrated Net Layer Security TUBA
|
||||||
|
ProtocolNARP = 54 // NBMA Address Resolution Protocol
|
||||||
|
ProtocolMOBILE = 55 // IP Mobility
|
||||||
|
ProtocolTLSP = 56 // Transport Layer Security Protocol using Kryptonet key management
|
||||||
|
ProtocolSKIP = 57 // SKIP
|
||||||
|
ProtocolIPv6ICMP = 58 // ICMP for IPv6
|
||||||
|
ProtocolIPv6NoNxt = 59 // No Next Header for IPv6
|
||||||
|
ProtocolIPv6Opts = 60 // Destination Options for IPv6
|
||||||
|
ProtocolCFTP = 62 // CFTP
|
||||||
|
ProtocolSATEXPAK = 64 // SATNET and Backroom EXPAK
|
||||||
|
ProtocolKRYPTOLAN = 65 // Kryptolan
|
||||||
|
ProtocolRVD = 66 // MIT Remote Virtual Disk Protocol
|
||||||
|
ProtocolIPPC = 67 // Internet Pluribus Packet Core
|
||||||
|
ProtocolSATMON = 69 // SATNET Monitoring
|
||||||
|
ProtocolVISA = 70 // VISA Protocol
|
||||||
|
ProtocolIPCV = 71 // Internet Packet Core Utility
|
||||||
|
ProtocolCPNX = 72 // Computer Protocol Network Executive
|
||||||
|
ProtocolCPHB = 73 // Computer Protocol Heart Beat
|
||||||
|
ProtocolWSN = 74 // Wang Span Network
|
||||||
|
ProtocolPVP = 75 // Packet Video Protocol
|
||||||
|
ProtocolBRSATMON = 76 // Backroom SATNET Monitoring
|
||||||
|
ProtocolSUNND = 77 // SUN ND PROTOCOL-Temporary
|
||||||
|
ProtocolWBMON = 78 // WIDEBAND Monitoring
|
||||||
|
ProtocolWBEXPAK = 79 // WIDEBAND EXPAK
|
||||||
|
ProtocolISOIP = 80 // ISO Internet Protocol
|
||||||
|
ProtocolVMTP = 81 // VMTP
|
||||||
|
ProtocolSECUREVMTP = 82 // SECURE-VMTP
|
||||||
|
ProtocolVINES = 83 // VINES
|
||||||
|
ProtocolTTP = 84 // Transaction Transport Protocol
|
||||||
|
ProtocolIPTM = 84 // Internet Protocol Traffic Manager
|
||||||
|
ProtocolNSFNETIGP = 85 // NSFNET-IGP
|
||||||
|
ProtocolDGP = 86 // Dissimilar Gateway Protocol
|
||||||
|
ProtocolTCF = 87 // TCF
|
||||||
|
ProtocolEIGRP = 88 // EIGRP
|
||||||
|
ProtocolOSPFIGP = 89 // OSPFIGP
|
||||||
|
ProtocolSpriteRPC = 90 // Sprite RPC Protocol
|
||||||
|
ProtocolLARP = 91 // Locus Address Resolution Protocol
|
||||||
|
ProtocolMTP = 92 // Multicast Transport Protocol
|
||||||
|
ProtocolAX25 = 93 // AX.25 Frames
|
||||||
|
ProtocolIPIP = 94 // IP-within-IP Encapsulation Protocol
|
||||||
|
ProtocolSCCSP = 96 // Semaphore Communications Sec. Pro.
|
||||||
|
ProtocolETHERIP = 97 // Ethernet-within-IP Encapsulation
|
||||||
|
ProtocolENCAP = 98 // Encapsulation Header
|
||||||
|
ProtocolGMTP = 100 // GMTP
|
||||||
|
ProtocolIFMP = 101 // Ipsilon Flow Management Protocol
|
||||||
|
ProtocolPNNI = 102 // PNNI over IP
|
||||||
|
ProtocolPIM = 103 // Protocol Independent Multicast
|
||||||
|
ProtocolARIS = 104 // ARIS
|
||||||
|
ProtocolSCPS = 105 // SCPS
|
||||||
|
ProtocolQNX = 106 // QNX
|
||||||
|
ProtocolAN = 107 // Active Networks
|
||||||
|
ProtocolIPComp = 108 // IP Payload Compression Protocol
|
||||||
|
ProtocolSNP = 109 // Sitara Networks Protocol
|
||||||
|
ProtocolCompaqPeer = 110 // Compaq Peer Protocol
|
||||||
|
ProtocolIPXinIP = 111 // IPX in IP
|
||||||
|
ProtocolVRRP = 112 // Virtual Router Redundancy Protocol
|
||||||
|
ProtocolPGM = 113 // PGM Reliable Transport Protocol
|
||||||
|
ProtocolL2TP = 115 // Layer Two Tunneling Protocol
|
||||||
|
ProtocolDDX = 116 // D-II Data Exchange (DDX)
|
||||||
|
ProtocolIATP = 117 // Interactive Agent Transfer Protocol
|
||||||
|
ProtocolSTP = 118 // Schedule Transfer Protocol
|
||||||
|
ProtocolSRP = 119 // SpectraLink Radio Protocol
|
||||||
|
ProtocolUTI = 120 // UTI
|
||||||
|
ProtocolSMP = 121 // Simple Message Protocol
|
||||||
|
ProtocolPTP = 123 // Performance Transparency Protocol
|
||||||
|
ProtocolISIS = 124 // ISIS over IPv4
|
||||||
|
ProtocolFIRE = 125 // FIRE
|
||||||
|
ProtocolCRTP = 126 // Combat Radio Transport Protocol
|
||||||
|
ProtocolCRUDP = 127 // Combat Radio User Datagram
|
||||||
|
ProtocolSSCOPMCE = 128 // SSCOPMCE
|
||||||
|
ProtocolIPLT = 129 // IPLT
|
||||||
|
ProtocolSPS = 130 // Secure Packet Shield
|
||||||
|
ProtocolPIPE = 131 // Private IP Encapsulation within IP
|
||||||
|
ProtocolSCTP = 132 // Stream Control Transmission Protocol
|
||||||
|
ProtocolFC = 133 // Fibre Channel
|
||||||
|
ProtocolRSVPE2EIGNORE = 134 // RSVP-E2E-IGNORE
|
||||||
|
ProtocolMobilityHeader = 135 // Mobility Header
|
||||||
|
ProtocolUDPLite = 136 // UDPLite
|
||||||
|
ProtocolMPLSinIP = 137 // MPLS-in-IP
|
||||||
|
ProtocolMANET = 138 // MANET Protocols
|
||||||
|
ProtocolHIP = 139 // Host Identity Protocol
|
||||||
|
ProtocolShim6 = 140 // Shim6 Protocol
|
||||||
|
ProtocolWESP = 141 // Wrapped Encapsulating Security Payload
|
||||||
|
ProtocolROHC = 142 // Robust Header Compression
|
||||||
|
ProtocolReserved = 255 // Reserved
|
||||||
|
)
|
293
vendor/golang.org/x/net/internal/iana/gen.go
generated
vendored
Normal file
293
vendor/golang.org/x/net/internal/iana/gen.go
generated
vendored
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
// Copyright 2013 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.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
//go:generate go run gen.go
|
||||||
|
|
||||||
|
// This program generates internet protocol constants and tables by
|
||||||
|
// reading IANA protocol registries.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var registries = []struct {
|
||||||
|
url string
|
||||||
|
parse func(io.Writer, io.Reader) error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"http://www.iana.org/assignments/dscp-registry/dscp-registry.xml",
|
||||||
|
parseDSCPRegistry,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"http://www.iana.org/assignments/ipv4-tos-byte/ipv4-tos-byte.xml",
|
||||||
|
parseTOSTCByte,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml",
|
||||||
|
parseProtocolNumbers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var bb bytes.Buffer
|
||||||
|
fmt.Fprintf(&bb, "// go generate gen.go\n")
|
||||||
|
fmt.Fprintf(&bb, "// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT\n\n")
|
||||||
|
fmt.Fprintf(&bb, "// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).\n")
|
||||||
|
fmt.Fprintf(&bb, `package iana // import "golang.org/x/net/internal/iana"`+"\n\n")
|
||||||
|
for _, r := range registries {
|
||||||
|
resp, err := http.Get(r.url)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
fmt.Fprintf(os.Stderr, "got HTTP status code %v for %v\n", resp.StatusCode, r.url)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := r.parse(&bb, resp.Body); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&bb, "\n")
|
||||||
|
}
|
||||||
|
b, err := format.Source(bb.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile("const.go", b, 0644); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDSCPRegistry(w io.Writer, r io.Reader) error {
|
||||||
|
dec := xml.NewDecoder(r)
|
||||||
|
var dr dscpRegistry
|
||||||
|
if err := dec.Decode(&dr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
drs := dr.escape()
|
||||||
|
fmt.Fprintf(w, "// %s, Updated: %s\n", dr.Title, dr.Updated)
|
||||||
|
fmt.Fprintf(w, "const (\n")
|
||||||
|
for _, dr := range drs {
|
||||||
|
fmt.Fprintf(w, "DiffServ%s = %#x", dr.Name, dr.Value)
|
||||||
|
fmt.Fprintf(w, "// %s\n", dr.OrigName)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, ")\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dscpRegistry struct {
|
||||||
|
XMLName xml.Name `xml:"registry"`
|
||||||
|
Title string `xml:"title"`
|
||||||
|
Updated string `xml:"updated"`
|
||||||
|
Note string `xml:"note"`
|
||||||
|
RegTitle string `xml:"registry>title"`
|
||||||
|
PoolRecords []struct {
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Space string `xml:"space"`
|
||||||
|
} `xml:"registry>record"`
|
||||||
|
Records []struct {
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Space string `xml:"space"`
|
||||||
|
} `xml:"registry>registry>record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type canonDSCPRecord struct {
|
||||||
|
OrigName string
|
||||||
|
Name string
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drr *dscpRegistry) escape() []canonDSCPRecord {
|
||||||
|
drs := make([]canonDSCPRecord, len(drr.Records))
|
||||||
|
sr := strings.NewReplacer(
|
||||||
|
"+", "",
|
||||||
|
"-", "",
|
||||||
|
"/", "",
|
||||||
|
".", "",
|
||||||
|
" ", "",
|
||||||
|
)
|
||||||
|
for i, dr := range drr.Records {
|
||||||
|
s := strings.TrimSpace(dr.Name)
|
||||||
|
drs[i].OrigName = s
|
||||||
|
drs[i].Name = sr.Replace(s)
|
||||||
|
n, err := strconv.ParseUint(dr.Space, 2, 8)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
drs[i].Value = int(n) << 2
|
||||||
|
}
|
||||||
|
return drs
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTOSTCByte(w io.Writer, r io.Reader) error {
|
||||||
|
dec := xml.NewDecoder(r)
|
||||||
|
var ttb tosTCByte
|
||||||
|
if err := dec.Decode(&ttb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
trs := ttb.escape()
|
||||||
|
fmt.Fprintf(w, "// %s, Updated: %s\n", ttb.Title, ttb.Updated)
|
||||||
|
fmt.Fprintf(w, "const (\n")
|
||||||
|
for _, tr := range trs {
|
||||||
|
fmt.Fprintf(w, "%s = %#x", tr.Keyword, tr.Value)
|
||||||
|
fmt.Fprintf(w, "// %s\n", tr.OrigKeyword)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, ")\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tosTCByte struct {
|
||||||
|
XMLName xml.Name `xml:"registry"`
|
||||||
|
Title string `xml:"title"`
|
||||||
|
Updated string `xml:"updated"`
|
||||||
|
Note string `xml:"note"`
|
||||||
|
RegTitle string `xml:"registry>title"`
|
||||||
|
Records []struct {
|
||||||
|
Binary string `xml:"binary"`
|
||||||
|
Keyword string `xml:"keyword"`
|
||||||
|
} `xml:"registry>record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type canonTOSTCByteRecord struct {
|
||||||
|
OrigKeyword string
|
||||||
|
Keyword string
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ttb *tosTCByte) escape() []canonTOSTCByteRecord {
|
||||||
|
trs := make([]canonTOSTCByteRecord, len(ttb.Records))
|
||||||
|
sr := strings.NewReplacer(
|
||||||
|
"Capable", "",
|
||||||
|
"(", "",
|
||||||
|
")", "",
|
||||||
|
"+", "",
|
||||||
|
"-", "",
|
||||||
|
"/", "",
|
||||||
|
".", "",
|
||||||
|
" ", "",
|
||||||
|
)
|
||||||
|
for i, tr := range ttb.Records {
|
||||||
|
s := strings.TrimSpace(tr.Keyword)
|
||||||
|
trs[i].OrigKeyword = s
|
||||||
|
ss := strings.Split(s, " ")
|
||||||
|
if len(ss) > 1 {
|
||||||
|
trs[i].Keyword = strings.Join(ss[1:], " ")
|
||||||
|
} else {
|
||||||
|
trs[i].Keyword = ss[0]
|
||||||
|
}
|
||||||
|
trs[i].Keyword = sr.Replace(trs[i].Keyword)
|
||||||
|
n, err := strconv.ParseUint(tr.Binary, 2, 8)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
trs[i].Value = int(n)
|
||||||
|
}
|
||||||
|
return trs
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseProtocolNumbers(w io.Writer, r io.Reader) error {
|
||||||
|
dec := xml.NewDecoder(r)
|
||||||
|
var pn protocolNumbers
|
||||||
|
if err := dec.Decode(&pn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
prs := pn.escape()
|
||||||
|
prs = append([]canonProtocolRecord{{
|
||||||
|
Name: "IP",
|
||||||
|
Descr: "IPv4 encapsulation, pseudo protocol number",
|
||||||
|
Value: 0,
|
||||||
|
}}, prs...)
|
||||||
|
fmt.Fprintf(w, "// %s, Updated: %s\n", pn.Title, pn.Updated)
|
||||||
|
fmt.Fprintf(w, "const (\n")
|
||||||
|
for _, pr := range prs {
|
||||||
|
if pr.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "Protocol%s = %d", pr.Name, pr.Value)
|
||||||
|
s := pr.Descr
|
||||||
|
if s == "" {
|
||||||
|
s = pr.OrigName
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "// %s\n", s)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, ")\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type protocolNumbers struct {
|
||||||
|
XMLName xml.Name `xml:"registry"`
|
||||||
|
Title string `xml:"title"`
|
||||||
|
Updated string `xml:"updated"`
|
||||||
|
RegTitle string `xml:"registry>title"`
|
||||||
|
Note string `xml:"registry>note"`
|
||||||
|
Records []struct {
|
||||||
|
Value string `xml:"value"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Descr string `xml:"description"`
|
||||||
|
} `xml:"registry>record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type canonProtocolRecord struct {
|
||||||
|
OrigName string
|
||||||
|
Name string
|
||||||
|
Descr string
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pn *protocolNumbers) escape() []canonProtocolRecord {
|
||||||
|
prs := make([]canonProtocolRecord, len(pn.Records))
|
||||||
|
sr := strings.NewReplacer(
|
||||||
|
"-in-", "in",
|
||||||
|
"-within-", "within",
|
||||||
|
"-over-", "over",
|
||||||
|
"+", "P",
|
||||||
|
"-", "",
|
||||||
|
"/", "",
|
||||||
|
".", "",
|
||||||
|
" ", "",
|
||||||
|
)
|
||||||
|
for i, pr := range pn.Records {
|
||||||
|
if strings.Contains(pr.Name, "Deprecated") ||
|
||||||
|
strings.Contains(pr.Name, "deprecated") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prs[i].OrigName = pr.Name
|
||||||
|
s := strings.TrimSpace(pr.Name)
|
||||||
|
switch pr.Name {
|
||||||
|
case "ISIS over IPv4":
|
||||||
|
prs[i].Name = "ISIS"
|
||||||
|
case "manet":
|
||||||
|
prs[i].Name = "MANET"
|
||||||
|
default:
|
||||||
|
prs[i].Name = sr.Replace(s)
|
||||||
|
}
|
||||||
|
ss := strings.Split(pr.Descr, "\n")
|
||||||
|
for i := range ss {
|
||||||
|
ss[i] = strings.TrimSpace(ss[i])
|
||||||
|
}
|
||||||
|
if len(ss) > 1 {
|
||||||
|
prs[i].Descr = strings.Join(ss, " ")
|
||||||
|
} else {
|
||||||
|
prs[i].Descr = ss[0]
|
||||||
|
}
|
||||||
|
prs[i].Value, _ = strconv.Atoi(pr.Value)
|
||||||
|
}
|
||||||
|
return prs
|
||||||
|
}
|
93
vendor/golang.org/x/net/ipv4/bpf_test.go
generated
vendored
Normal file
93
vendor/golang.org/x/net/ipv4/bpf_test.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2016 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 ipv4_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBPF(t *testing.T) {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
t.Skipf("not supported on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
p := ipv4.NewPacketConn(l)
|
||||||
|
|
||||||
|
// This filter accepts UDP packets whose first payload byte is
|
||||||
|
// even.
|
||||||
|
prog, err := bpf.Assemble([]bpf.Instruction{
|
||||||
|
// Load the first byte of the payload (skipping UDP header).
|
||||||
|
bpf.LoadAbsolute{Off: 8, Size: 1},
|
||||||
|
// Select LSB of the byte.
|
||||||
|
bpf.ALUOpConstant{Op: bpf.ALUOpAnd, Val: 1},
|
||||||
|
// Byte is even?
|
||||||
|
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0, SkipFalse: 1},
|
||||||
|
// Accept.
|
||||||
|
bpf.RetConstant{Val: 4096},
|
||||||
|
// Ignore.
|
||||||
|
bpf.RetConstant{Val: 0},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("compiling BPF: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = p.SetBPF(prog); err != nil {
|
||||||
|
t.Fatalf("attaching filter to Conn: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := net.Dial("udp4", l.LocalAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
go func() {
|
||||||
|
for i := byte(0); i < 10; i++ {
|
||||||
|
s.Write([]byte{i})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
l.SetDeadline(time.Now().Add(2 * time.Second))
|
||||||
|
seen := make([]bool, 5)
|
||||||
|
for {
|
||||||
|
var b [512]byte
|
||||||
|
n, _, err := l.ReadFrom(b[:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reading from listener: %s", err)
|
||||||
|
}
|
||||||
|
if n != 1 {
|
||||||
|
t.Fatalf("unexpected packet length, want 1, got %d", n)
|
||||||
|
}
|
||||||
|
if b[0] >= 10 {
|
||||||
|
t.Fatalf("unexpected byte, want 0-9, got %d", b[0])
|
||||||
|
}
|
||||||
|
if b[0]%2 != 0 {
|
||||||
|
t.Fatalf("got odd byte %d, wanted only even bytes", b[0])
|
||||||
|
}
|
||||||
|
seen[b[0]/2] = true
|
||||||
|
|
||||||
|
seenAll := true
|
||||||
|
for _, v := range seen {
|
||||||
|
if !v {
|
||||||
|
seenAll = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if seenAll {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
vendor/golang.org/x/net/ipv4/bpfopt_linux.go
generated
vendored
Normal file
27
vendor/golang.org/x/net/ipv4/bpfopt_linux.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 ipv4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetBPF attaches a BPF program to the connection.
|
||||||
|
//
|
||||||
|
// Only supported on Linux.
|
||||||
|
func (c *dgramOpt) SetBPF(filter []bpf.RawInstruction) error {
|
||||||
|
fd, err := c.sysfd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
prog := sysSockFProg{
|
||||||
|
Len: uint16(len(filter)),
|
||||||
|
Filter: (*sysSockFilter)(unsafe.Pointer(&filter[0])),
|
||||||
|
}
|
||||||
|
return os.NewSyscallError("setsockopt", setsockopt(fd, sysSOL_SOCKET, sysSO_ATTACH_FILTER, unsafe.Pointer(&prog), uint32(unsafe.Sizeof(prog))))
|
||||||
|
}
|
16
vendor/golang.org/x/net/ipv4/bpfopt_stub.go
generated
vendored
Normal file
16
vendor/golang.org/x/net/ipv4/bpfopt_stub.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package ipv4
|
||||||
|
|
||||||
|
import "golang.org/x/net/bpf"
|
||||||
|
|
||||||
|
// SetBPF attaches a BPF program to the connection.
|
||||||
|
//
|
||||||
|
// Only supported on Linux.
|
||||||
|
func (c *dgramOpt) SetBPF(filter []bpf.RawInstruction) error {
|
||||||
|
return errOpNoSupport
|
||||||
|
}
|
70
vendor/golang.org/x/net/ipv4/control.go
generated
vendored
Normal file
70
vendor/golang.org/x/net/ipv4/control.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2012 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 ipv4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rawOpt struct {
|
||||||
|
sync.RWMutex
|
||||||
|
cflags ControlFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rawOpt) set(f ControlFlags) { c.cflags |= f }
|
||||||
|
func (c *rawOpt) clear(f ControlFlags) { c.cflags &^= f }
|
||||||
|
func (c *rawOpt) isset(f ControlFlags) bool { return c.cflags&f != 0 }
|
||||||
|
|
||||||
|
type ControlFlags uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlagTTL ControlFlags = 1 << iota // pass the TTL on the received packet
|
||||||
|
FlagSrc // pass the source address on the received packet
|
||||||
|
FlagDst // pass the destination address on the received packet
|
||||||
|
FlagInterface // pass the interface index on the received packet
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ControlMessage represents per packet basis IP-level socket options.
|
||||||
|
type ControlMessage struct {
|
||||||
|
// Receiving socket options: SetControlMessage allows to
|
||||||
|
// receive the options from the protocol stack using ReadFrom
|
||||||
|
// method of PacketConn or RawConn.
|
||||||
|
//
|
||||||
|
// Specifying socket options: ControlMessage for WriteTo
|
||||||
|
// method of PacketConn or RawConn allows to send the options
|
||||||
|
// to the protocol stack.
|
||||||
|
//
|
||||||
|
TTL int // time-to-live, receiving only
|
||||||
|
Src net.IP // source address, specifying only
|
||||||
|
Dst net.IP // destination address, receiving only
|
||||||
|
IfIndex int // interface index, must be 1 <= value when specifying
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ControlMessage) String() string {
|
||||||
|
if cm == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("ttl=%d src=%v dst=%v ifindex=%d", cm.TTL, cm.Src, cm.Dst, cm.IfIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ancillary data socket options
|
||||||
|
const (
|
||||||
|
ctlTTL = iota // header field
|
||||||
|
ctlSrc // header field
|
||||||
|
ctlDst // header field
|
||||||
|
ctlInterface // inbound or outbound interface
|
||||||
|
ctlPacketInfo // inbound or outbound packet path
|
||||||
|
ctlMax
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ctlOpt represents a binding for ancillary data socket option.
|
||||||
|
type ctlOpt struct {
|
||||||
|
name int // option name, must be equal or greater than 1
|
||||||
|
length int // option length
|
||||||
|
marshal func([]byte, *ControlMessage) []byte
|
||||||
|
parse func(*ControlMessage, []byte)
|
||||||
|
}
|
40
vendor/golang.org/x/net/ipv4/control_bsd.go
generated
vendored
Normal file
40
vendor/golang.org/x/net/ipv4/control_bsd.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package ipv4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
)
|
||||||
|
|
||||||
|
func marshalDst(b []byte, cm *ControlMessage) []byte {
|
||||||
|
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||||
|
m.Level = iana.ProtocolIP
|
||||||
|
m.Type = sysIP_RECVDSTADDR
|
||||||
|
m.SetLen(syscall.CmsgLen(net.IPv4len))
|
||||||
|
return b[syscall.CmsgSpace(net.IPv4len):]
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDst(cm *ControlMessage, b []byte) {
|
||||||
|
cm.Dst = b[:net.IPv4len]
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalInterface(b []byte, cm *ControlMessage) []byte {
|
||||||
|
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||||
|
m.Level = iana.ProtocolIP
|
||||||
|
m.Type = sysIP_RECVIF
|
||||||
|
m.SetLen(syscall.CmsgLen(syscall.SizeofSockaddrDatalink))
|
||||||
|
return b[syscall.CmsgSpace(syscall.SizeofSockaddrDatalink):]
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterface(cm *ControlMessage, b []byte) {
|
||||||
|
sadl := (*syscall.SockaddrDatalink)(unsafe.Pointer(&b[0]))
|
||||||
|
cm.IfIndex = int(sadl.Index)
|
||||||
|
}
|
37
vendor/golang.org/x/net/ipv4/control_pktinfo.go
generated
vendored
Normal file
37
vendor/golang.org/x/net/ipv4/control_pktinfo.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
// +build darwin linux
|
||||||
|
|
||||||
|
package ipv4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
)
|
||||||
|
|
||||||
|
func marshalPacketInfo(b []byte, cm *ControlMessage) []byte {
|
||||||
|
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||||
|
m.Level = iana.ProtocolIP
|
||||||
|
m.Type = sysIP_PKTINFO
|
||||||
|
m.SetLen(syscall.CmsgLen(sysSizeofInetPktinfo))
|
||||||
|
if cm != nil {
|
||||||
|
pi := (*sysInetPktinfo)(unsafe.Pointer(&b[syscall.CmsgLen(0)]))
|
||||||
|
if ip := cm.Src.To4(); ip != nil {
|
||||||
|
copy(pi.Spec_dst[:], ip)
|
||||||
|
}
|
||||||
|
if cm.IfIndex > 0 {
|
||||||
|
pi.setIfindex(cm.IfIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b[syscall.CmsgSpace(sysSizeofInetPktinfo):]
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePacketInfo(cm *ControlMessage, b []byte) {
|
||||||
|
pi := (*sysInetPktinfo)(unsafe.Pointer(&b[0]))
|
||||||
|
cm.IfIndex = int(pi.Ifindex)
|
||||||
|
cm.Dst = pi.Addr[:]
|
||||||
|
}
|
23
vendor/golang.org/x/net/ipv4/control_stub.go
generated
vendored
Normal file
23
vendor/golang.org/x/net/ipv4/control_stub.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// +build nacl plan9 solaris
|
||||||
|
|
||||||
|
package ipv4
|
||||||
|
|
||||||
|
func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error {
|
||||||
|
return errOpNoSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func newControlMessage(opt *rawOpt) []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseControlMessage(b []byte) (*ControlMessage, error) {
|
||||||
|
return nil, errOpNoSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalControlMessage(cm *ControlMessage) []byte {
|
||||||
|
return nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user