Compare commits

..

98 Commits

Author SHA1 Message Date
62b36d2fbc Merge pull request #433 from ydcool/mips64le-support
add support for mips64le
2020-01-08 16:26:13 +00:00
e5fdd449dd Merge pull request #428 from weibeld/master
bridge: add missing cniVersion in README example
2020-01-08 16:24:48 +00:00
8db5e4d41b add support for mips64le
Signed-off-by: Dominic Yin <yindongchao@inspur.com>
2020-01-06 15:51:47 +08:00
ec8f6c99d0 Merge pull request #421 from aojea/portmapErrors2
Portmap doesn't fail if chain doesn't exist
2019-12-19 00:16:58 +08:00
7dea2a4c1b Add missing cniVersion in README example
Signed-off-by: Daniel Weibel <danielmweibel@gmail.com>
2019-12-18 19:08:37 +08:00
5a02c5bc61 bump go-iptables module to v0.4.5
bump the go-iptables module to v0.4.5 to avoid
concurrency issues with the portmap plugin and
errors related to iptables not able to hold the
lock.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-16 17:42:20 +01:00
bf8f171041 iptables: add idempotent functions
Add the following idempotent functions to iptables utils:

DeleteRule: idempotently delete an iptables rule
DeleteChain: idempotently delete an iptables chain
ClearChain: idempotently flush an iptables chain

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 15:13:15 +01:00
3603738c6a portmap doesn't fail if chain doesn't exist
It turns out that the portmap plugin is not idempotent if its
executed in parallel.
The errors are caused due to a race of different instantiations
deleting the chains.
This patch does that the portmap plugin doesn't fail if the
errors are because the chain doesn't exist on teardown.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 09:03:06 +01:00
d8b1289098 fix portmap port forward flakiness
Use a Describe container for the It code block of the
portmap port forward integration test.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 09:03:06 +01:00
6551165853 Merge pull request #412 from containernetworking/new-maintainers
Add Bruce Ma and Piotr Skarmuk as owners
2019-12-04 10:11:31 -06:00
10a01b09ae Add Bruce Ma and Piotr Skarmuk as owners
Signed-off-by: Bryan Boreham <bryan@weave.works>
2019-11-16 11:45:44 +00:00
497560f35f Merge pull request #408 from tgross/idempotent_chain_creation
ensure iptables chain creation is idempotent
2019-11-13 17:20:45 +01:00
58dd90b996 ensure iptables chain creation is idempotent
Concurrent use of the `portmap` and `firewall` plugins can result in
errors during iptables chain creation:

- The `portmap` plugin has a time-of-check-time-of-use race where it
  checks for existence of the chain but the operation isn't atomic.
- The `firewall` plugin doesn't check for existing chains and just
  returns an error.

This commit makes both operations idempotent by creating the chain and
then discarding the error if it's caused by the chain already
existing. It also factors the chain creation out into `pkg/utils` as a
site for future refactoring work.

Signed-off-by: Tim Gross <tim@0x74696d.com>
2019-11-11 10:00:11 -05:00
d5efdfe1f6 Merge pull request #409 from squeed/fix-integ-tests
integration: fix ip address collision in integration tests
2019-11-11 14:07:52 +01:00
05f121a406 integration: fix ip address collision in integration tests
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2019-11-11 13:36:21 +01:00
825fbd8a95 Merge pull request #405 from mars1024/feat/vlan_mtu_validation
vlan: add MTU validation to loadNetConf
2019-11-06 16:29:57 +00:00
1a30688da0 add some testcases about invalid MTUs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 20:15:18 +08:00
bee8d6cf30 vlan: add MTU validation in loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 19:59:33 +08:00
a16232968d Merge pull request #400 from s1061123/fix/overwrite-ips
static: prioritize the input sources for IPs
2019-10-23 16:54:54 +01:00
1880421389 Merge pull request #401 from giuseppe/run-in-a-userns
testutils: newNS() works in a rootless user namespace
2019-10-23 16:29:59 +01:00
a2ed3d9a69 Merge pull request #403 from s1061123/dev/addgarp
Sending GratuitousArp in case of MAC address update
2019-10-23 16:24:58 +01:00
7bcaae263f Merge pull request #404 from mars1024/feat/mtu_validation
macvlan: add MTU validation to loadNetConf
2019-10-23 16:13:06 +01:00
e1f955d9bf macvlan: add MTU validation to loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-23 20:39:14 +08:00
2583a0b4ad Sending GratuitousArp in case of MAC address update
This change sends gratuitous ARP when MAC address is changed to
let other devices to know the MAC address update.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2019-10-23 15:17:38 +09:00
85083ea434 testutils: newNS() works in a rootless user namespace
When running in a user namespace created by an unprivileged user the
owner of /var/run will be reported as the unknown user (as defined in
/proc/sys/kernel/overflowuid) so any access to the directory will
fail.

If the XDG_RUNTIME_DIR environment variable is set, check whether the
current user is also the owner of /var/run.  If the owner is different
than the current user, use the $XDG_RUNTIME_DIR/netns directory.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2019-10-19 12:04:53 +02:00
2290fc8d8a static: prioritize the input sources for IPs
This change introduce priorities for IPs input among CNI_ARGS,
'args' and runtimeConfig. Fix #399.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2019-10-17 13:36:49 +09:00
411d060b81 Merge pull request #389 from CallMeFoxie/bw-units
Use uint64 for Bandwidth plugin
2019-10-09 16:25:06 +01:00
5915b49b38 Merge pull request #394 from mars1024/bugfix/validate_vlanid
bridge: check vlan id when loading net conf
2019-10-09 17:23:47 +02:00
c25c62742b Merge pull request #396 from oshothebig/contributing-doc
contributing doc: revise test script name to run
2019-10-09 10:21:03 -05:00
b7ffa24326 vlan/bridge: fix some typo
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-08 11:57:30 +08:00
15894b36a0 Merge pull request #397 from oshothebig/install-cnitool
contributing doc: describe cnitool installation
2019-10-07 14:27:23 +02:00
77b51f0bc9 contributing doc: describe cnitool installation
cnitool must be installed before running tests because cnitool is
invoked during the tests

Signed-off-by: Sho SHIMIZU <sho.shimizu@gmail.com>
2019-10-07 17:42:44 +09:00
bd63528b0b contributing doc: revise test script name to run
test.sh doesn't exists now as it was separated into two OS-specific
scripts in 4e1f7802db.

Signed-off-by: Sho SHIMIZU <sho.shimizu@gmail.com>
2019-10-04 18:32:12 +09:00
cf187287af Update tests for uint64
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:55:41 +02:00
0dff883769 Use uint64 for Bandwidth plugin
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:05:27 +02:00
d0eeb27494 Merge pull request #390 from sipsma/firewall-fix
firewall: don't return error in DEL if prevResult is not found.
2019-10-02 10:38:47 -05:00
e70558cbe1 bridge: check vlan id when loading net conf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-09-30 17:12:31 +08:00
0a1421a08c firewall: remove unused netns check from DEL method
Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-25 20:38:02 +00:00
0f19aa2f8d Merge pull request #388 from sipsma/fix-ptpdns
ptp: only override DNS conf if DNS settings provided
2019-09-25 17:43:24 +02:00
e91889678b Merge pull request #391 from beautytiger/dev-190925
bugfix: defer after err check, or it may panic
2019-09-25 16:25:21 +01:00
8ec6bd6a42 bugfix: defer after err check, or it may panic
Signed-off-by: Guangming Wang <guangming.wang@daocloud.io>
2019-09-25 22:21:49 +08:00
fc7059c1ae firewall: don't return error in DEL if prevResult is not found.
The CNI spec states that for DEL implementations, "when CNI_NETNS and/or
prevResult are not provided, the plugin should clean up as many resources as
possible (e.g. releasing IPAM allocations) and return a successful response".
This change results in the firewall plugin conforming to the spec by not
returning an error whenever the del method is not provided a prevResult.

Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-23 21:11:07 +00:00
a96c469e62 ptp: only override DNS conf if DNS settings provided
Previously, if an IPAM plugin provided DNS settings in the result to the PTP
plugin, those settings were always lost because the PTP plugin would always
provide its own DNS settings in the result even if the PTP plugin was not
configured with any DNS settings.

This was especially problematic when trying to use, for example, the host-local
IPAM plugin's support for retrieving DNS settings from a resolv.conf file on
the host. Before this change, those DNS settings were always lost when using the
PTP plugin and couldn't be specified as part of PTP instead because PTP does not
support parsing a resolv.conf file.

This change checks to see if any fields were actually set in the PTP plugin's
DNS settings and only overrides any previous DNS results from an IPAM plugin in
the case that settings actually were provided to PTP. In the case where no
DNS settings are provided to PTP, the DNS results of the IPAM plugin (if any)
are used instead.

Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-18 21:09:22 +00:00
291ab6cc84 Merge pull request #386 from janisz/patch-1
Bump Go version
2019-09-18 17:12:43 +02:00
90125f40ba Bump Go version
Signed-off-by: Tomasz Janiszewski <janiszt@gmail.com>
2019-09-18 11:20:33 +02:00
23d5525ec3 Merge pull request #383 from mccv1r0/issue381
When prevResults are not supplied to loopback plugin, create results to return
2019-09-11 11:11:55 -05:00
fd42109a06 When prevResults are not returned to loopback plugin, create results to return based on
the lo interface and IP address assigned inside container.

Signed-off-by: Michael Cambria <mcambria@redhat.com>
2019-09-11 11:57:03 -04:00
4bb288193c Merge pull request #379 from xcelsion/fix-host-container-address-family-mismatch
Fix dual-stack support in meta/portmap
2019-09-11 17:16:36 +02:00
e8365e126d Fixed issue where hostIP address family was not checked against the containerIP address family. closes #378
Signed-off-by: Niels van Oosterom <xcelsion@users.noreply.github.com>
2019-09-06 15:23:00 +02:00
7e68430081 Merge pull request #377 from mars1024/bumpup/to/0.7.1
bump up libcni to v0.7.1
2019-08-28 10:55:43 -05:00
f81a529ebd Merge pull request #375 from smarkm/master
Fixes #342, cleanup netns after test suite
2019-08-28 17:55:36 +02:00
630a4d8db6 Merge pull request #374 from mars1024/feat/loopback_support_check
loopback support CNI CHECK and result cache
2019-08-28 10:53:43 -05:00
3d56f7504d loopback plugin support to pass previous result transpartently
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 22:56:22 +08:00
659a09f34e loopback support CNI CHECK
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:36:37 +08:00
b76ace9c64 bump up libcni to v0.7.1
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:24:16 +08:00
0d0dcfc02f Cleanup netns after test suit
Signed-off-by: smarkm <smark@freecoop.net>
2019-08-22 08:10:35 +08:00
485be65581 Merge pull request #372 from squeed/fix-build
win-bridge, win-overlay: remove extra import
2019-08-14 11:26:26 -07:00
ca82120019 win-bridge, win-overlay: remove extra import 2019-08-14 18:15:23 +02:00
c9e1c0c1ed Merge pull request #281 from s1061123/dev/support-args
Support "args" in static and tuning
2019-08-14 17:43:04 +02:00
2d6d4b260a Merge pull request #366 from Random-Liu/fix-bridge-race
Fix: failed to set bridge addr: could not add IP address to \"cni0\": file exists
2019-08-12 13:08:32 +02:00
ad7c1d189b Fix a race condition in the bridge plugin.
Signed-off-by: Lantao Liu <lantaol@google.com>
2019-08-09 17:08:04 -07:00
a069a5f1a3 Support "args" in static and tuning
Support "args" field in JSON config to additional configuration
in static and tuning plugins.
2019-08-09 11:52:06 +09:00
ccd683e1a3 Merge pull request #357 from mars1024/bugfix/host-device
host-device: revert name setting to make retries idempotent
2019-08-07 08:54:26 -07:00
a11cb626b0 Merge pull request #331 from nagiesek/LoopbackDsr
Loopback dsr & L2Tunnel
2019-08-07 17:48:00 +02:00
f36dbc2031 Merge pull request #358 from mccv1r0/ipt-vendor
Vendor update go-iptables
2019-08-07 10:40:26 -05:00
e9d511c5bc Merge pull request #364 from s1061123/fix/removeifdown
Remove link Down/Up in MAC address change to prevent route flush
2019-08-07 17:39:02 +02:00
91a68d56f9 Vendor update go-iptables to obtain commit f1d0510cabcb710d5c5dd284096f81444b9d8d10
Update go.mod & go.sub
2019-08-07 10:56:30 -04:00
8902d2614a Remove link Down/Up in MAC address change to prevent route flush 2019-08-07 13:54:10 +09:00
df9af9ab41 [Windows] Adds optional loopbackDSR argument to cni config.
Adds a bool to the cni config that will add a policy that allows for loopbackDSR on an interface. Updates relevant documentation. Allows L2Tunnel networks to be used for L2Bridge plugin.
2019-07-31 15:45:54 -07:00
5e2e365291 host-device: remove useless Expects in testcases
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-29 21:09:03 +08:00
4b68f56820 host-device: add testcases for imdempotence of CmdDel
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-29 20:47:28 +08:00
ded2f17577 Merge pull request #328 from mars1024/feature/hostlocal_idempotent
host-local support idempotent allocation
2019-07-24 17:32:15 +02:00
57650a1e5b host-device: revert name setting to make retries idempotent
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-19 21:26:07 +08:00
7ba2bcfeab Merge pull request #353 from cf-container-networking/fix-hash-func
Increase IfbDeviceName size and refactor hashing functions, fix
2019-07-18 17:12:53 -04:00
3fb8dcfd4c pkg/meta/bandwidth: increase IfbDeviceName size
* Increase entroy from 2 bytes to 7 bytes to prevent collisions
* Extract common library function for hash with prefix
* Refactor portmap plugin to use library function

fixes #347

Co-authored-by: Cameron Moreau <cmoreau@pivotal.io>
Co-authored-by: Mikael Manukyan <mmanukyan@pivotal.io>
2019-07-18 11:45:38 -07:00
966bbcb8a5 Merge pull request #348 from cf-container-networking/update-vagrantfile
update Go version in Vagrantfile
2019-07-10 10:04:52 -05:00
7d76537d4a Merge pull request #349 from cf-container-networking/fix-ip-test
pkg/ip unit test: be agnostic of Linux version
2019-07-10 10:04:32 -05:00
f3b1ffc960 pkg/ip unit test: be agnostic of Linux version
on Linux 4.4 the syscall error message is "invalid argument" not "file
exists"

Co-authored-by: Gabe Rosenhouse <grosenhouse@pivotal.io>
2019-07-09 16:58:57 -07:00
ce9560712e update Go version in Vagrantfile
update go to the lastest version v1.12.7

Co-authored-by: Gabe Rosenhouse <grosenhouse@pivotal.io>
2019-07-09 16:51:00 -07:00
e2984e7840 host-local: return error if duplicate allocation is requested for a given ID
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 10:05:18 +08:00
eb1ff18c4c host-local: add some testcases for allocation idempotency
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 09:39:56 +08:00
e8771b36a2 host-local: make allocation idempotent to multiple requests with same id
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 09:39:56 +08:00
7f8ea631e5 host-local: make Store interface support to get ip list by id
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 09:39:56 +08:00
0eddc554c0 Merge pull request #343 from s1061123/dev/runtime-ips-mac
Support ips capability in static and mac capability in tuning
2019-07-03 17:48:55 +02:00
e8a25e33cd Merge pull request #344 from cadmuxe/veth_name
Make host-side veth name configurable.
2019-07-03 17:46:02 +02:00
96bd10f679 Add pkg/ip/link_linux.go:SetupVethWithName to support the host-side veth
name configuration.
2019-06-26 10:24:40 -07:00
303299b7b2 Merge pull request #337 from nagiesek/goModUpdate
Go mod update
2019-06-26 11:03:40 -05:00
d42007865a update iptables 2019-06-26 02:14:56 -07:00
ce60e8eb3d dhcp module update 2019-06-26 02:07:24 -07:00
addbcd34b4 update ethtool 2019-06-26 02:07:24 -07:00
e8c953999e vendor update 2019-06-26 02:07:24 -07:00
13fbc4afdf Move over to go mod from dep 2019-06-26 02:07:23 -07:00
545a77f4bb skip makeVeth retry if a vethPeerName is set. 2019-06-25 14:17:42 -07:00
c204dbd47c update pkg/ip/link_linux.go:SetupVeth to support host-side veth name config. 2019-06-21 11:39:30 -07:00
660685a8af Support ips capability in static and mac capability in tuning
This change introduces new capability flag to change MAC address
and to specify IP addresses by tuning and static.
2019-06-20 17:11:47 +09:00
2b6808807f Merge pull request #341 from mars1024/bump_cni_to_0.7.1
bump containernetworking/cni to v0.7.1
2019-06-19 17:51:14 +02:00
869d5ec873 pkg/ipam : use delegateArgs instead of env set/unset in ipam.ExecDel
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-06-18 21:02:01 +08:00
93919752fb bump containernetworking/cni up to v0.7.1
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-06-18 20:49:51 +08:00
961 changed files with 38599 additions and 265760 deletions

View File

@ -1,10 +1,11 @@
language: go
sudo: required
dist: trusty
dist: xenial
go:
- 1.11.x
- 1.12.x
- 1.13.x
env:
global:
@ -17,6 +18,7 @@ env:
- TARGET=arm64
- TARGET=ppc64le
- TARGET=s390x
- TARGET=mips64le
matrix:
fast_finish: true

View File

@ -72,10 +72,11 @@ vagrant ssh
# you're now in a shell in a virtual machine
sudo su
go get github.com/onsi/ginkgo/ginkgo
go install github.com/containernetworking/cni/cnitool
cd /go/src/github.com/containernetworking/plugins
# to run the full test suite
./test.sh
./test_linux.sh
# to focus on a particular test suite
cd plugins/main/loopback

367
Godeps/Godeps.json generated
View File

@ -1,367 +0,0 @@
{
"ImportPath": "github.com/containernetworking/plugins",
"GoVersion": "go1.7",
"GodepVersion": "v80",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/Microsoft/go-winio",
"Comment": "v0.4.11",
"Rev": "97e4973ce50b2ff5f09635a57e2b88a037aae829"
},
{
"ImportPath": "github.com/Microsoft/hcsshim",
"Comment": "v0.7.6",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/guid",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcs",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcserror",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/hns",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/interop",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/longpath",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/mergemaps",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/safefile",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/schema1",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/timeout",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/wclayer",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/alexflint/go-filemutex",
"Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9"
},
{
"ImportPath": "github.com/buger/jsonparser",
"Rev": "f4dd9f5a6b441265aefc1d44872a6f8c10f42b37"
},
{
"ImportPath": "github.com/containernetworking/cni/libcni",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/version",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/coreos/go-iptables/iptables",
"Comment": "v0.4.1",
"Rev": "78b5fff24e6df8886ef8eca9411f683a884349a5"
},
{
"ImportPath": "github.com/coreos/go-systemd/activation",
"Comment": "v17",
"Rev": "39ca1b05acc7ad1220e09f133283b8859a8b71ab"
},
{
"ImportPath": "github.com/d2g/dhcp4",
"Rev": "f0e4d29ff0231dce36e250b2ed9ff08412584bca"
},
{
"ImportPath": "github.com/d2g/dhcp4client",
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
},
{
"ImportPath": "github.com/d2g/dhcp4server",
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
},
{
"ImportPath": "github.com/d2g/dhcp4server/leasepool",
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
},
{
"ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool",
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
},
{
"ImportPath": "github.com/godbus/dbus",
"Comment": "v4.1.0-6-g885f9cc",
"Rev": "885f9cc04c9c1a6a61a2008e211d36c5737be3f5"
},
{
"ImportPath": "github.com/j-keck/arping",
"Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
},
{
"ImportPath": "github.com/juju/errors",
"Rev": "22422dad46e14561a0854ad42497a75af9b61909"
},
{
"ImportPath": "github.com/mattn/go-shellwords",
"Comment": "v1.0.3",
"Rev": "02e3cf038dcea8290e44424da473dd12be796a8a"
},
{
"ImportPath": "github.com/onsi/ginkgo",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/config",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/extensions/table",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/types",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/gomega",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/format",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gbytes",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gexec",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/assertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/types",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/safchain/ethtool",
"Rev": "42ed695e3de80b9d695f280295fd7994639f209d"
},
{
"ImportPath": "github.com/sirupsen/logrus",
"Comment": "v1.0.6",
"Rev": "3e01752db0189b9157070a0e1668a620f9a85da2"
},
{
"ImportPath": "github.com/vishvananda/netlink",
"Comment": "v1.0.0-40-g023a6da",
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
},
{
"ImportPath": "github.com/vishvananda/netlink/nl",
"Comment": "v1.0.0-40-g023a6da",
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
},
{
"ImportPath": "github.com/vishvananda/netns",
"Rev": "13995c7128ccc8e51e9a6bd2b551020a27180abd"
},
{
"ImportPath": "golang.org/x/crypto/ssh/terminal",
"Rev": "7c1a557ab941a71c619514f229f0b27ccb0c27cf"
},
{
"ImportPath": "golang.org/x/net/bpf",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/net/ipv4",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
},
{
"ImportPath": "golang.org/x/sys/windows",
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
},
{
"ImportPath": "golang.org/x/net/internal/socket",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
}
]
}

5
Godeps/Readme generated
View File

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@ -1,8 +1,10 @@
# Owners
This is the official list of the CNI network plugins owners:
- Bruce Ma <brucema19901024@gmail.com> (@mars1024)
- Bryan Boreham <bryan@weave.works> (@bboreham)
- Casey Callendrello <casey.callendrello@coreos.com> (@squeed)
- Dan Williams <dcbw@redhat.com> (@dcbw)
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
- Matt Dupre <matt@tigera.io> (@matthewdupre)
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
- Stefan Junker <stefan.junker@coreos.com> (@steveeJ)

4
Vagrantfile vendored
View File

@ -10,9 +10,9 @@ Vagrant.configure(2) do |config|
set -e -x -u
apt-get update -y || (sleep 40 && apt-get update -y)
apt-get install -y git gcc-multilib gcc-mingw-w64
wget -qO- https://storage.googleapis.com/golang/go1.11.1.linux-amd64.tar.gz | tar -C /usr/local -xz
wget -qO- https://storage.googleapis.com/golang/go1.12.7.linux-amd64.tar.gz | tar -C /usr/local -xz
echo 'export GOPATH=/go' >> /root/.bashrc
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
cd /go/src/github.com/containernetworking/plugins
SHELL
end
end

View File

@ -16,6 +16,7 @@ fi
export GOPATH=${PWD}/gopath
export GO="${GO:-go}"
export GOFLAGS="${GOFLAGS} -mod=vendor"
mkdir -p "${PWD}/bin"

View File

@ -12,6 +12,8 @@ ln -s ${PWD} ${GOPATH}/src/${REPO_PATH} || exit 255
export GO="${GO:-go}"
export GOOS=windows
export GOFLAGS="${GOFLAGS} -mod=vendor"
echo $GOFLAGS
PLUGINS=$(cat plugins/windows_only.txt)
for d in $PLUGINS; do

40
go.mod Normal file
View File

@ -0,0 +1,40 @@
module github.com/containernetworking/plugins
go 1.12
require (
github.com/Microsoft/go-winio v0.4.11 // indirect
github.com/Microsoft/hcsshim v0.8.6
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
github.com/containernetworking/cni v0.7.1
github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
github.com/golang/protobuf v1.3.1 // indirect
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
github.com/juju/testing v0.0.0-20190613124551-e81189438503 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-shellwords v1.0.3
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8
github.com/sirupsen/logrus v1.0.6 // indirect
github.com/stretchr/testify v1.3.0 // indirect
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 // indirect
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

81
go.sum Normal file
View File

@ -0,0 +1,81 @@
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUqU3jMrZiTuW0zkYeKlKDAFD+DG20IoO421/Y=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK31EJ9FzE=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg=
github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.4.4 h1:5oOUvU7Fk53Hn/rkdJ0zcYGCffotqXpyi4ADCkO1TJ8=
github.com/coreos/go-iptables v0.4.4/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0 h1:suYBsYZIkSlUMEz4TAYCczKf62IA2UWC+O8+KtdOhCo=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02LdEKPcgErLJ347NUwxPKs5u8ieiY=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 h1:itqmmf1PFpC4n5JW+j4BU7X4MTfVurhYRTjODoPb2Y8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uHz20ZHLDK+SW5Us/vWF5IHRaY=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20190613124551-e81189438503 h1:ZUgTbk8oHgP0jpMieifGC9Lv47mHn8Pb3mFX3/Ew4iY=
github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf h1:3J37+NPjNyGW/dbfXtj3yWuF9OEepIdGOXRaJGbORV8=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -149,12 +149,12 @@ var _ = Describe("Basic PTP using cnitool", func() {
})
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
ipRegexp := regexp.MustCompile("10\\.11\\.2\\.\\d{1,3}")
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
chainedBridgeIP := ipRegexp.FindString(chainedBridgeBandwidthEnv.runInNS(contNS1, "ip", "addr"))
Expect(chainedBridgeIP).To(ContainSubstring("10.11.2."))
Expect(chainedBridgeIP).To(ContainSubstring("10.12.2."))
By(fmt.Sprintf("adding %s to %s\n\n", "basic-bridge", contNS2.ShortName()))
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS2.LongName())

View File

@ -4,12 +4,12 @@
"plugins": [
{
"type": "bridge",
"bridge": "test-bridge-0",
"bridge": "test-bridge-1",
"isDefaultGateway": true,
"ipam": {
"type": "host-local",
"subnet": "10.11.2.0/24",
"dataDir": "/tmp/foo"
"subnet": "10.12.2.0/24",
"dataDir": "/tmp/bar"
}
},
{

View File

@ -77,6 +77,9 @@ func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint
}
}
if n.LoopbackDSR {
n.ApplyLoopbackDSR(&epInfo.IpAddress)
}
if hnsEndpoint == nil {
hnsEndpoint = &hcsshim.HNSEndpoint{
Name: epInfo.EndpointName,
@ -117,14 +120,8 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
if hcnEndpoint == nil {
routes := []hcn.Route{
{
NextHop: GetIpString(&epInfo.Gateway),
DestinationPrefix: func() string {
destinationPrefix := "0.0.0.0/0"
if ipv6 := epInfo.Gateway.To4(); ipv6 == nil {
destinationPrefix = "::/0"
}
return destinationPrefix
}(),
NextHop: GetIpString(&epInfo.Gateway),
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
},
}
@ -138,6 +135,9 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
}
ipConfigs := []hcn.IpConfig{hcnIpConfig}
if n.LoopbackDSR {
n.ApplyLoopbackDSR(&epInfo.IpAddress)
}
hcnEndpoint = &hcn.HostComputeEndpoint{
SchemaVersion: hcn.Version{Major: 2},
Name: epInfo.EndpointName,

View File

@ -17,6 +17,9 @@ package hns
import (
"bytes"
"encoding/json"
"fmt"
"net"
"github.com/Microsoft/hcsshim/hcn"
"github.com/buger/jsonparser"
"github.com/containernetworking/cni/pkg/types"
@ -26,9 +29,16 @@ import (
// NetConf is the CNI spec
type NetConf struct {
types.NetConf
// ApiVersion is either 1 or 2, which specifies which hns APIs to call
ApiVersion int `json:"ApiVersion"`
// V2 Api Policies
HcnPolicyArgs []hcn.EndpointPolicy `json:"HcnPolicyArgs,omitempty"`
Policies []policy `json:"policies,omitempty"`
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
// V1 Api Policies
Policies []policy `json:"policies,omitempty"`
// Options to be passed in by the runtime
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
// If true, adds a policy to endpoints to support loopback direct server return
LoopbackDSR bool `json:"loopbackDSR"`
}
type RuntimeDNS struct {
@ -45,6 +55,31 @@ type policy struct {
Value json.RawMessage `json:"value"`
}
func GetDefaultDestinationPrefix(ip *net.IP) string {
destinationPrefix := "0.0.0.0/0"
if ipv6 := ip.To4(); ipv6 == nil {
destinationPrefix = "::/0"
}
return destinationPrefix
}
func (n *NetConf) ApplyLoopbackDSR(ip *net.IP) {
value := fmt.Sprintf(`"Destinations" : ["%s"]`, ip.String())
if n.ApiVersion == 2 {
hcnLoopbackRoute := hcn.EndpointPolicy{
Type: "OutBoundNAT",
Settings: []byte(fmt.Sprintf("{%s}", value)),
}
n.HcnPolicyArgs = append(n.HcnPolicyArgs, hcnLoopbackRoute)
} else {
hnsLoopbackRoute := policy{
Name: "EndpointPolicy",
Value: []byte(fmt.Sprintf(`{"Type": "OutBoundNAT", %s}`, value)),
}
n.Policies = append(n.Policies, hnsLoopbackRoute)
}
}
// If runtime dns values are there use that else use cni conf supplied dns
func (n *NetConf) GetDNS() types.DNS {
dnsResult := n.DNS

View File

@ -60,11 +60,15 @@ func peerExists(name string) bool {
return true
}
func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) {
func makeVeth(name, vethPeerName string, mtu int) (peerName string, veth netlink.Link, err error) {
for i := 0; i < 10; i++ {
peerName, err = RandomVethName()
if err != nil {
return
if vethPeerName != "" {
peerName = vethPeerName
} else {
peerName, err = RandomVethName()
if err != nil {
return
}
}
veth, err = makeVethPair(name, peerName, mtu)
@ -73,7 +77,7 @@ func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err err
return
case os.IsExist(err):
if peerExists(peerName) {
if peerExists(peerName) && vethPeerName == "" {
continue
}
err = fmt.Errorf("container veth name provided (%v) already exists", name)
@ -121,12 +125,13 @@ func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
}
}
// SetupVeth sets up a pair of virtual ethernet devices.
// Call SetupVeth from inside the container netns. It will create both veth
// SetupVethWithName sets up a pair of virtual ethernet devices.
// Call SetupVethWithName from inside the container netns. It will create both veth
// devices and move the host-side veth into the provided hostNS namespace.
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
hostVethName, contVeth, err := makeVeth(contVethName, mtu)
// hostVethName: If hostVethName is not specified, the host-side veth name will use a random string.
// On success, SetupVethWithName returns (hostVeth, containerVeth, nil)
func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu)
if err != nil {
return net.Interface{}, net.Interface{}, err
}
@ -161,6 +166,14 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
}
// SetupVeth sets up a pair of virtual ethernet devices.
// Call SetupVeth from inside the container netns. It will create both veth
// devices and move the host-side veth into the provided hostNS namespace.
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
return SetupVethWithName(contVethName, "", mtu, hostNS)
}
// DelLinkByName removes an interface link.
func DelLinkByName(ifName string) error {
iface, err := netlink.LinkByName(ifName)

View File

@ -189,9 +189,8 @@ var _ = Describe("Link", func() {
It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
Expect(err.Error()).To(Equal("failed to move veth to host netns: file exists"))
Expect(err.Error()).To(HavePrefix("failed to move veth to host netns: "))
return nil
})

View File

@ -16,10 +16,8 @@ package ipam
import (
"context"
"fmt"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types"
"os"
)
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
@ -31,13 +29,5 @@ func ExecCheck(plugin string, netconf []byte) error {
}
func ExecDel(plugin string, netconf []byte) error {
cmd := os.Getenv("CNI_COMMAND")
if cmd == "" {
return fmt.Errorf("environment variable CNI_COMMAND must be specified.")
}
// Set CNI_COMMAND to DEL explicity. We might be deleting due to an ADD gone wrong.
// restore CNI_COMMAND to original value upon return.
os.Setenv("CNI_COMMAND", "DEL")
defer os.Setenv("CNI_COMMAND", cmd)
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
}

60
pkg/testutils/dns.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2019 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 testutils
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/containernetworking/cni/pkg/types"
)
// TmpResolvConf will create a temporary file and write the provided DNS settings to
// it in the resolv.conf format. It returns the path of the created temporary file or
// an error if any occurs while creating/writing the file. It is the caller's
// responsibility to remove the file.
func TmpResolvConf(dnsConf types.DNS) (string, error) {
f, err := ioutil.TempFile("", "cni_test_resolv.conf")
if err != nil {
return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err)
}
defer f.Close()
path := f.Name()
defer func() {
if err != nil {
os.RemoveAll(path)
}
}()
// see "man 5 resolv.conf" for the format of resolv.conf
var resolvConfLines []string
for _, nameserver := range dnsConf.Nameservers {
resolvConfLines = append(resolvConfLines, fmt.Sprintf("nameserver %s", nameserver))
}
resolvConfLines = append(resolvConfLines, fmt.Sprintf("domain %s", dnsConf.Domain))
resolvConfLines = append(resolvConfLines, fmt.Sprintf("search %s", strings.Join(dnsConf.Search, " ")))
resolvConfLines = append(resolvConfLines, fmt.Sprintf("options %s", strings.Join(dnsConf.Options, " ")))
resolvConf := strings.Join(resolvConfLines, "\n")
_, err = f.Write([]byte(resolvConf))
if err != nil {
return "", fmt.Errorf("failed to write temp resolv.conf for CNI test: %v", err)
}
return path, err
}

View File

@ -22,17 +22,36 @@ import (
"runtime"
"strings"
"sync"
"syscall"
"github.com/containernetworking/plugins/pkg/ns"
"golang.org/x/sys/unix"
)
const nsRunDir = "/var/run/netns"
func getNsRunDir() string {
xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
/// If XDG_RUNTIME_DIR is set, check if the current user owns /var/run. If
// the owner is different, we are most likely running in a user namespace.
// In that case use $XDG_RUNTIME_DIR/netns as runtime dir.
if xdgRuntimeDir != "" {
if s, err := os.Stat("/var/run"); err == nil {
st, ok := s.Sys().(*syscall.Stat_t)
if ok && int(st.Uid) != os.Geteuid() {
return path.Join(xdgRuntimeDir, "netns")
}
}
}
return "/var/run/netns"
}
// Creates a new persistent (bind-mounted) network namespace and returns an object
// representing that namespace, without switching to it.
func NewNS() (ns.NetNS, error) {
nsRunDir := getNsRunDir()
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
if err != nil {
@ -135,7 +154,7 @@ func NewNS() (ns.NetNS, error) {
func UnmountNS(ns ns.NetNS) error {
nsPath := ns.Path()
// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
if strings.HasPrefix(nsPath, nsRunDir) {
if strings.HasPrefix(nsPath, getNsRunDir()) {
if err := unix.Unmount(nsPath, 0); err != nil {
return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err)
}

121
pkg/utils/iptables.go Normal file
View File

@ -0,0 +1,121 @@
// 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 utils
import (
"errors"
"fmt"
"github.com/coreos/go-iptables/iptables"
)
const statusChainExists = 1
// EnsureChain idempotently creates the iptables chain. It does not
// return an error if the chain already exists.
func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
exists, err := ChainExists(ipt, table, chain)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
}
if !exists {
err = ipt.NewChain(table, chain)
if err != nil {
eerr, eok := err.(*iptables.Error)
if eok && eerr.ExitStatus() != statusChainExists {
return err
}
}
}
return nil
}
// ChainExists checks whether an iptables chain exists.
func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
if ipt == nil {
return false, errors.New("failed to check iptable chain: IPTables was nil")
}
chains, err := ipt.ListChains(table)
if err != nil {
return false, err
}
for _, ch := range chains {
if ch == chain {
return true, nil
}
}
return false, nil
}
// DeleteRule idempotently delete the iptables rule in the specified table/chain.
// It does not return an error if the referring chain doesn't exist
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
if err := ipt.Delete(table, chain, rulespec...); err != nil {
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
case eok && eerr.ExitStatus() == 2:
// swallow here, invalid command line parameter because the referring rule is missing
return nil
default:
return fmt.Errorf("Failed to delete referring rule %s %s: %v", table, chain, err)
}
}
return nil
}
// DeleteChain idempotently deletes the specified table/chain.
// It does not return an errors if the chain does not exist
func DeleteChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.DeleteChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
default:
return err
}
}
// ClearChain idempotently clear the iptables rules in the specified table/chain.
// If the chain does not exist, a new one will be created
func ClearChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.ClearChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return EnsureChain(ipt, table, chain)
default:
return err
}
}

View File

@ -0,0 +1,97 @@
// Copyright 2017-2018 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 utils
import (
"fmt"
"math/rand"
"runtime"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/coreos/go-iptables/iptables"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const TABLE = "filter" // We'll monkey around here
var _ = Describe("chain tests", func() {
var testChain string
var ipt *iptables.IPTables
var cleanup func()
BeforeEach(func() {
// Save a reference to the original namespace,
// Add a new NS
currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
testChain = fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
runtime.LockOSThread()
err = testNs.Set()
Expect(err).NotTo(HaveOccurred())
cleanup = func() {
if ipt == nil {
return
}
ipt.ClearChain(TABLE, testChain)
ipt.DeleteChain(TABLE, testChain)
currNs.Set()
}
})
AfterEach(func() {
cleanup()
})
Describe("EnsureChain", func() {
It("creates chains idempotently", func() {
err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Create it again!
err = EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("DeleteChain", func() {
It("delete chains idempotently", func() {
// Create chain
err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete chain
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete it again!
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
})
})
})

View File

@ -22,16 +22,22 @@ import (
const (
maxChainLength = 28
chainPrefix = "CNI-"
prefixLength = len(chainPrefix)
)
// Generates a chain name to be used with iptables.
// Ensures that the generated chain name is exactly
// maxChainLength chars in length
// FormatChainName generates a chain name to be used
// with iptables. Ensures that the generated chain
// name is exactly maxChainLength chars in length.
func FormatChainName(name string, id string) string {
chainBytes := sha512.Sum512([]byte(name + id))
chain := fmt.Sprintf("%s%x", chainPrefix, chainBytes)
return chain[:maxChainLength]
return MustFormatChainNameWithPrefix(name, id, "")
}
// MustFormatChainNameWithPrefix generates a chain name similar
// to FormatChainName, but adds a custom prefix between
// chainPrefix and unique identifier. Ensures that the
// generated chain name is exactly maxChainLength chars in length.
// Panics if the given prefix is too long.
func MustFormatChainNameWithPrefix(name string, id string, prefix string) string {
return MustFormatHashWithPrefix(maxChainLength, chainPrefix+prefix, name+id)
}
// FormatComment returns a comment used for easier
@ -39,3 +45,16 @@ func FormatChainName(name string, id string) string {
func FormatComment(name string, id string) string {
return fmt.Sprintf("name: %q id: %q", name, id)
}
const MaxHashLen = sha512.Size * 2
// MustFormatHashWithPrefix returns a string of given length that begins with the
// given prefix. It is filled with entropy based on the given string toHash.
func MustFormatHashWithPrefix(length int, prefix string, toHash string) string {
if len(prefix) >= length || length > MaxHashLen {
panic("invalid length")
}
output := sha512.Sum512([]byte(toHash))
return fmt.Sprintf("%s%x", prefix, output)[:length]
}

View File

@ -15,37 +15,151 @@
package utils
import (
"fmt"
"strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Utils", func() {
It("must format a short name", func() {
chain := FormatChainName("test", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
Describe("FormatChainName", func() {
It("must format a short name", func() {
chain := FormatChainName("test", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
})
It("must truncate a long name", func() {
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
})
It("must be predictable", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
})
It("must change when a character changes", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Expect(chain1).NotTo(Equal(chain2))
})
})
It("must truncate a long name", func() {
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Describe("MustFormatChainNameWithPrefix", func() {
It("generates a chain name with a prefix", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
})
It("must format a short name", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
})
It("must truncate a long name", func() {
chain := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
})
It("must be predictable", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
})
It("must change when a character changes", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1235", "PREFIX-")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
Expect(chain1).NotTo(Equal(chain2))
})
It("panics when prefix is too large", func() {
longPrefix := strings.Repeat("PREFIX-", 4)
Expect(func() {
MustFormatChainNameWithPrefix("test", "1234", longPrefix)
}).To(Panic())
})
})
It("must be predictable", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
Describe("MustFormatHashWithPrefix", func() {
It("always returns a string with the given prefix", func() {
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HavePrefix("AAA"))
Expect(MustFormatHashWithPrefix(10, "foo", "some string")).To(HavePrefix("foo"))
Expect(MustFormatHashWithPrefix(10, "bar", "some string")).To(HavePrefix("bar"))
})
It("always returns a string of the given length", func() {
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HaveLen(10))
Expect(MustFormatHashWithPrefix(15, "AAA", "some string")).To(HaveLen(15))
Expect(MustFormatHashWithPrefix(5, "AAA", "some string")).To(HaveLen(5))
})
It("is deterministic", func() {
val1 := MustFormatHashWithPrefix(10, "AAA", "some string")
val2 := MustFormatHashWithPrefix(10, "AAA", "some string")
val3 := MustFormatHashWithPrefix(10, "AAA", "some string")
Expect(val1).To(Equal(val2))
Expect(val1).To(Equal(val3))
})
It("is (nearly) perfect (injective function)", func() {
hashes := map[string]int{}
for i := 0; i < 1000; i++ {
name := fmt.Sprintf("string %d", i)
hashes[MustFormatHashWithPrefix(8, "", name)]++
}
for key, count := range hashes {
Expect(count).To(Equal(1), "for key "+key+" got non-unique correspondence")
}
})
assertPanicWith := func(f func(), expectedErrorMessage string) {
defer func() {
Expect(recover()).To(Equal(expectedErrorMessage))
}()
f()
Fail("function should have panicked but did not")
}
It("panics when prefix is longer than the length", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(3, "AAA", "some string") },
"invalid length",
)
})
It("panics when length is not positive", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(0, "", "some string") },
"invalid length",
)
})
It("panics when length is larger than MaxLen", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(MaxHashLen+1, "", "some string") },
"invalid length",
)
})
})
It("must change when a character changes", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Expect(chain1).NotTo(Equal(chain2))
})
})

View File

@ -40,7 +40,7 @@ func NewIPAllocator(s *RangeSet, store backend.Store, id int) *IPAllocator {
}
}
// Get alocates an IP
// Get allocates an IP
func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*current.IPConfig, error) {
a.store.Lock()
defer a.store.Unlock()
@ -73,6 +73,17 @@ func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*curren
gw = r.Gateway
} else {
// try to get allocated IPs for this given id, if exists, just return error
// because duplicate allocation is not allowed in SPEC
// https://github.com/containernetworking/cni/blob/master/SPEC.md
allocatedIPs := a.store.GetByID(id, ifname)
for _, allocatedIP := range allocatedIPs {
// check whether the existing IP belong to this range set
if _, err := a.rangeset.RangeFor(allocatedIP); err == nil {
return nil, fmt.Errorf("%s has been allocated to %s, duplicate allocation is not allowed", allocatedIP.String(), id)
}
}
iter, err := a.GetIter()
if err != nil {
return nil, err

View File

@ -221,14 +221,14 @@ var _ = Describe("host-local ip allocator", func() {
It("should not allocate the broadcast address", func() {
alloc := mkalloc()
for i := 2; i < 7; i++ {
res, err := alloc.Get("ID", "eth0", nil)
res, err := alloc.Get(fmt.Sprintf("ID%d", i), "eth0", nil)
Expect(err).ToNot(HaveOccurred())
s := fmt.Sprintf("192.168.1.%d/29", i)
Expect(s).To(Equal(res.Address.String()))
fmt.Fprintln(GinkgoWriter, "got ip", res.Address.String())
}
x, err := alloc.Get("ID", "eth0", nil)
x, err := alloc.Get("ID8", "eth0", nil)
fmt.Fprintln(GinkgoWriter, "got ip", x)
Expect(err).To(HaveOccurred())
})

View File

@ -19,10 +19,10 @@ import (
"net"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
"runtime"
)
const lastIPFilePrefix = "last_reserved_ip."
@ -172,6 +172,35 @@ func (s *Store) ReleaseByID(id string, ifname string) error {
return err
}
// GetByID returns the IPs which have been allocated to the specific ID
func (s *Store) GetByID(id string, ifname string) []net.IP {
var ips []net.IP
match := strings.TrimSpace(id) + LineBreak + ifname
// matchOld for backwards compatibility
matchOld := strings.TrimSpace(id)
// walk through all ips in this network to get the ones which belong to a specific ID
_ = filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil
}
if strings.TrimSpace(string(data)) == match || strings.TrimSpace(string(data)) == matchOld {
_, ipString := filepath.Split(path)
if ip := net.ParseIP(ipString); ip != nil {
ips = append(ips, ip)
}
}
return nil
})
return ips
}
func GetEscapedPath(dataDir string, fname string) string {
if runtime.GOOS == "windows" {
fname = strings.Replace(fname, ":", "_", -1)

View File

@ -24,4 +24,5 @@ type Store interface {
LastReservedIP(rangeID string) (net.IP, error)
Release(ip net.IP) error
ReleaseByID(id string, ifname string) error
GetByID(id string, ifname string) []net.IP
}

View File

@ -81,6 +81,16 @@ func (s *FakeStore) ReleaseByID(id string, ifname string) error {
return nil
}
func (s *FakeStore) GetByID(id string, ifname string) []net.IP {
var ips []net.IP
for k, v := range s.ipMap {
if v == id {
ips = append(ips, net.ParseIP(k))
}
}
return ips
}
func (s *FakeStore) SetIPMap(m map[string]string) {
s.ipMap = m
}

View File

@ -65,12 +65,11 @@ options four
func parse(contents string) (*types.DNS, error) {
f, err := ioutil.TempFile("", "host_local_resolv")
defer f.Close()
defer os.Remove(f.Name())
if err != nil {
return nil, err
}
defer f.Close()
defer os.Remove(f.Name())
if _, err := f.WriteString(contents); err != nil {
return nil, err

View File

@ -250,6 +250,94 @@ var _ = Describe("host-local Operations", func() {
Expect(err).To(HaveOccurred())
})
It("repeat allocating addresses on specific interface for same container ID with ADD", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
tmpDir, err := getTmpDir()
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(tmpDir)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet0",
"type": "ipvlan",
"master": "foo0",
"ipam": {
"type": "host-local",
"dataDir": "%s",
"ranges": [
[{ "subnet": "10.1.2.0/24" }]
]
}
}`, tmpDir)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
args1 := &skel.CmdArgs{
ContainerID: "dummy1",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
// Allocate the IP
r0, raw, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result0, err := current.GetResult(r0)
Expect(err).NotTo(HaveOccurred())
Expect(len(result0.IPs)).Should(Equal(1))
Expect(result0.IPs[0].Address.String()).Should(Equal("10.1.2.2/24"))
// Allocate the IP with the same container ID
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(HaveOccurred())
// Allocate the IP with the another container ID
r1, raw, err := testutils.CmdAddWithArgs(args1, func() error {
return cmdAdd(args1)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result1, err := current.GetResult(r1)
Expect(err).NotTo(HaveOccurred())
Expect(len(result1.IPs)).Should(Equal(1))
Expect(result1.IPs[0].Address.String()).Should(Equal("10.1.2.3/24"))
// Allocate the IP with the same container ID again
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(HaveOccurred())
ipFilePath := filepath.Join(tmpDir, "mynet0", "10.1.2.2")
// Release the IPs
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
_, err = os.Stat(ipFilePath)
Expect(err).To(HaveOccurred())
err = testutils.CmdDelWithArgs(args1, func() error {
return cmdDel(args1)
})
Expect(err).NotTo(HaveOccurred())
})
It("Verify DEL works on backwards compatible allocate", func() {
const nspath string = "/some/where"
const ifname string = "eth0"

View File

@ -52,3 +52,17 @@ The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/
* `GATEWAY`: request a specific gateway address
(example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254")
The plugin also support following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md).
* `ips`: Pass IP addresses for CNI interface
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
* `ips` (array of strings): A list of custom IPs to attempt to allocate, with prefix (e.g. '10.10.0.1/24')
Notice: If some of above are used at same time, only one will work according to the priorities below
1. [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
1. [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config)
1. [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters)

View File

@ -34,6 +34,13 @@ type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
IPAM *IPAMConfig `json:"ipam"`
RuntimeConfig struct {
IPs []string `json:"ips,omitempty"`
} `json:"runtimeConfig,omitempty"`
Args *struct {
A *IPAMArgs `json:"cni"`
} `json:"args"`
}
type IPAMConfig struct {
@ -50,6 +57,10 @@ type IPAMEnvArgs struct {
GATEWAY types.UnmarshallableString `json:"gateway,omitempty"`
}
type IPAMArgs struct {
IPs []string `json:"ips"`
}
type Address struct {
AddressStr string `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
@ -134,6 +145,65 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
return nil, "", err
}
// load IP from CNI_ARGS
if envArgs != "" {
e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.IP != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)
ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
}
addr := Address{
Address: net.IPNet{IP: ip, Mask: subnet.Mask},
AddressStr: ipstr,
}
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
}
}
if e.GATEWAY != "" {
for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil {
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
}
for i := range n.IPAM.Addresses {
if n.IPAM.Addresses[i].Address.Contains(gwip) {
n.IPAM.Addresses[i].Gateway = gwip
}
}
}
}
}
// import address from args
if n.Args != nil && n.Args.A != nil && len(n.Args.A.IPs) != 0 {
// args IP overwrites IP, so clear IPAM Config
n.IPAM.Addresses = make([]Address, 0, len(n.Args.A.IPs))
for _, addr := range n.Args.A.IPs {
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
}
}
// import address from runtimeConfig
if len(n.RuntimeConfig.IPs) != 0 {
// runtimeConfig IP overwrites IP, so clear IPAM Config
n.IPAM.Addresses = make([]Address, 0, len(n.RuntimeConfig.IPs))
for _, addr := range n.RuntimeConfig.IPs {
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
}
}
if n.IPAM == nil {
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
}
@ -163,50 +233,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
}
}
if envArgs != "" {
e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.IP != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)
ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
}
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
if addr.Address.IP.To4() != nil {
addr.Version = "4"
numV4++
} else {
addr.Version = "6"
numV6++
}
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
}
}
if e.GATEWAY != "" {
for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil {
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
}
for i := range n.IPAM.Addresses {
if n.IPAM.Addresses[i].Address.Contains(gwip) {
n.IPAM.Addresses[i].Gateway = gwip
}
}
}
}
}
// CNI spec 0.2.0 and below supported only one v4 and v6 address
if numV4 > 1 || numV6 > 1 {
for _, v := range types020.SupportedVersions {

View File

@ -265,6 +265,221 @@ var _ = Describe("static Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ipvlan",
"master": "foo0",
"capabilities": {"ips": true},
"ipam": {
"type": "static",
"routes": [
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
{ "dst": "3ffe:ffff:0:01ff::1/64",
"gw": "3ffe:ffff:0::1" } ],
"dns": {
"nameservers" : ["8.8.8.8"],
"domain": "example.com",
"search": [ "example.com" ]
}
},
"RuntimeConfig": {
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
// Allocate the IP
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
// Gomega is cranky about slices with different caps
Expect(*result.IPs[0]).To(Equal(
current.IPConfig{
Version: "4",
Address: mustCIDR("10.10.0.1/24"),
}))
Expect(*result.IPs[1]).To(Equal(
current.IPConfig{
Version: "6",
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
},
))
Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
}))
// Release the IP
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
})
It("allocates and releases multiple addresses with ADD/DEL, from args", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ipvlan",
"master": "foo0",
"ipam": {
"type": "static",
"routes": [
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
{ "dst": "3ffe:ffff:0:01ff::1/64",
"gw": "3ffe:ffff:0::1" } ],
"dns": {
"nameservers" : ["8.8.8.8"],
"domain": "example.com",
"search": [ "example.com" ]
}
},
"args": {
"cni": {
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
}
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
// Allocate the IP
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
// Gomega is cranky about slices with different caps
Expect(*result.IPs[0]).To(Equal(
current.IPConfig{
Version: "4",
Address: mustCIDR("10.10.0.1/24"),
}))
Expect(*result.IPs[1]).To(Equal(
current.IPConfig{
Version: "6",
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
},
))
Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
}))
// Release the IP
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
})
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ipvlan",
"master": "foo0",
"capabilities": {"ips": true},
"ipam": {
"type": "static",
"routes": [
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
{ "dst": "3ffe:ffff:0:01ff::1/64",
"gw": "3ffe:ffff:0::1" } ],
"dns": {
"nameservers" : ["8.8.8.8"],
"domain": "example.com",
"search": [ "example.com" ]
}
},
"RuntimeConfig": {
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
},
"args": {
"cni": {
"ips" : ["10.10.0.2/24", "3ffe:ffff:0:01ff::2/64"]
}
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
Args: "IP=10.10.0.3/24,11.11.0.3/24;GATEWAY=10.10.0.254",
}
// Allocate the IP
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
// only addresses in runtimeConfig configured because of its priorities
Expect(*result.IPs[0]).To(Equal(
current.IPConfig{
Version: "4",
Address: mustCIDR("10.10.0.1/24"),
}))
Expect(*result.IPs[1]).To(Equal(
current.IPConfig{
Version: "6",
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
},
))
Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
}))
// Release the IP
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
})
})
func mustCIDR(s string) net.IPNet {

View File

@ -14,17 +14,18 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
## Example configuration
```
{
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}
```
@ -32,10 +33,10 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
```
{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
}
```
@ -56,4 +57,4 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
*Note:* The VLAN parameter configures the VLAN tag on the host end of the veth and also enables the vlan_filtering feature on the bridge interface.
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.

View File

@ -75,6 +75,9 @@ func loadNetConf(bytes []byte) (*NetConf, string, error) {
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
if n.Vlan < 0 || n.Vlan > 4094 {
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan)
}
return n, n.CNIVersion, nil
}
@ -175,7 +178,7 @@ func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool)
}
addr := &netlink.Addr{IPNet: ipn, Label: ""}
if err := netlink.AddrAdd(br, addr); err != nil {
if err := netlink.AddrAdd(br, addr); err != nil && err != syscall.EEXIST {
return fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err)
}

View File

@ -27,7 +27,7 @@ import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
types020 "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -1125,6 +1125,7 @@ var _ = Describe("bridge Operations", func() {
AfterEach(func() {
Expect(os.RemoveAll(dataDir)).To(Succeed())
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates a bridge", func() {
@ -1644,4 +1645,48 @@ var _ = Describe("bridge Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("check vlan id when loading net conf", func() {
tests := []struct {
tc testCase
err error
}{
{
tc: testCase{
cniVersion: "0.4.0",
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 0,
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: -100,
},
err: fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)"),
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 5000,
},
err: fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)"),
},
}
for _, test := range tests {
_, _, err := loadNetConf([]byte(test.tc.netConfJSON("")))
if test.err == nil {
Expect(err).To(BeNil())
} else {
Expect(err).To(Equal(test.err))
}
}
})
})

View File

@ -218,14 +218,23 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
}
// Devices can be renamed only when down
if err := netlink.LinkSetDown(dev); err != nil {
if err = netlink.LinkSetDown(dev); err != nil {
return fmt.Errorf("failed to set %q down: %v", ifName, err)
}
// Rename device to it's original name
if err := netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
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 {
defer func() {
if err != nil {
// if moving device to host namespace fails, we should revert device name
// to ifName to make sure that device can be found in retries
_ = netlink.LinkSetName(dev, ifName)
}
}()
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

View File

@ -233,14 +233,15 @@ var _ = Describe("base functionality", func() {
})
AfterEach(func() {
originalNS.Close()
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("Works with a valid config without IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@ -254,7 +255,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
@ -294,26 +294,24 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
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 {
_ = 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 {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@ -324,15 +322,16 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Works with a valid config with IPAM", func() {
var origLink netlink.Link
It("Test idempotence of CmdDel", func() {
var (
origLink netlink.Link
conflictLink netlink.Link
)
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
// prepare host device in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@ -346,8 +345,142 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
cniName := "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: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result is sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
// assert that dummy0 is now NOT in the original namespace anymore
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
// create another conflict host device with same name "dummy0"
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
conflictLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and fails
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).To(HaveOccurred())
return nil
})
// assert container interface "eth0" still exists in target namespace
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
// remove the conflict host device
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = netlink.LinkDel(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and succeed
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
// assert that dummy0 is now back in the original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
})
It("Works with a valid config with IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
_ = 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
})
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
@ -394,7 +527,7 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
@ -409,19 +542,17 @@ var _ = Describe("base functionality", func() {
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
_ = 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 {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@ -432,8 +563,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("fails an invalid config", func() {
@ -458,7 +587,7 @@ var _ = Describe("base functionality", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@ -472,7 +601,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
@ -512,23 +640,21 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
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 {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
@ -555,7 +681,7 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@ -566,15 +692,13 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Works with a valid 0.4.0 config with IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@ -588,7 +712,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
@ -636,7 +759,7 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
@ -651,16 +774,14 @@ var _ = Describe("base functionality", func() {
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
@ -690,7 +811,7 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@ -701,8 +822,144 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Test idempotence of CmdDel with 0.4.0 config", func() {
var (
origLink netlink.Link
conflictLink netlink.Link
)
// prepare host device in original namespace
_ = 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
})
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
cniName := "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"device": %q
}`, ifname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result is sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
// assert that dummy0 is now NOT in the original namespace anymore
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
// create another conflict host device with same name "dummy0"
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
conflictLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and fails
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).To(HaveOccurred())
return nil
})
// assert container interface "eth0" still exists in target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
Expect(err).NotTo(HaveOccurred())
// remove the conflict host device
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = netlink.LinkDel(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and succeed
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
// assert that dummy0 is now back in the original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
})
})

View File

@ -297,6 +297,7 @@ var _ = Describe("ipvlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an ipvlan link in a non-default namespace", func() {

View File

@ -15,9 +15,15 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"net"
"github.com/vishvananda/netlink"
"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"
@ -25,9 +31,34 @@ import (
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
func parseNetConf(bytes []byte) (*types.NetConf, error) {
conf := &types.NetConf{}
if err := json.Unmarshal(bytes, conf); err != nil {
return nil, fmt.Errorf("failed to parse network config: %v", err)
}
if conf.RawPrevResult != nil {
if err := version.ParsePrevResult(conf); err != nil {
return nil, fmt.Errorf("failed to parse prevResult: %v", err)
}
if _, err := current.NewResultFromResult(conf.PrevResult); err != nil {
return nil, fmt.Errorf("failed to convert result to current version: %v", err)
}
}
return conf, nil
}
func cmdAdd(args *skel.CmdArgs) error {
conf, err := parseNetConf(args.StdinData)
if err != nil {
return err
}
var v4Addr, v6Addr *net.IPNet
args.IfName = "lo" // ignore config, this only works for loopback
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err // not tested
@ -37,6 +68,33 @@ func cmdAdd(args *skel.CmdArgs) error {
if err != nil {
return err // not tested
}
v4Addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
if err != nil {
return err // not tested
}
if len(v4Addrs) != 0 {
v4Addr = v4Addrs[0].IPNet
// sanity check that this is a loopback address
for _, addr := range v4Addrs {
if !addr.IP.IsLoopback() {
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
}
}
}
v6Addrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
if err != nil {
return err // not tested
}
if len(v6Addrs) != 0 {
v6Addr = v6Addrs[0].IPNet
// sanity check that this is a loopback address
for _, addr := range v4Addrs {
if !addr.IP.IsLoopback() {
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
}
}
}
return nil
})
@ -44,8 +102,35 @@ func cmdAdd(args *skel.CmdArgs) error {
return err // not tested
}
result := current.Result{}
return result.Print()
var result types.Result
if conf.PrevResult != nil {
// If loopback has previous result which passes from previous CNI plugin,
// loopback should pass it transparently
result = conf.PrevResult
} else {
loopbackInterface := &current.Interface{Name: args.IfName, Mac: "00:00:00:00:00:00", Sandbox: args.Netns}
r := &current.Result{CNIVersion: conf.CNIVersion, Interfaces: []*current.Interface{loopbackInterface}}
if v4Addr != nil {
r.IPs = append(r.IPs, &current.IPConfig{
Version: "4",
Interface: current.Int(0),
Address: *v4Addr,
})
}
if v6Addr != nil {
r.IPs = append(r.IPs, &current.IPConfig{
Version: "6",
Interface: current.Int(0),
Address: *v6Addr,
})
}
result = r
}
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
@ -78,6 +163,18 @@ func main() {
}
func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return nil
args.IfName = "lo" // ignore config, this only works for loopback
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err
}
if link.Attrs().Flags&net.FlagUp != net.FlagUp {
return errors.New("loopback interface is down")
}
return nil
})
}

View File

@ -49,17 +49,16 @@ var _ = Describe("Loopback", func() {
fmt.Sprintf("CNI_ARGS=%s", "none"),
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
}
command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
command.Stdin = strings.NewReader(`{ "name": "loopback-test", "cniVersion": "0.1.0" }`)
})
AfterEach(func() {
Expect(networkNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(networkNS)).To(Succeed())
})
Context("when given a network namespace", func() {
It("sets the lo device to UP", func() {
Skip("TODO: add network name")
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "ADD"))
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
@ -80,8 +79,6 @@ var _ = Describe("Loopback", func() {
})
It("sets the lo device to DOWN", func() {
Skip("TODO: add network name")
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "DEL"))
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

View File

@ -23,9 +23,9 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi
* `name` (string, required): the name of the network
* `type` (string, required): "macvlan"
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interace.
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interface.
* `mode` (string, optional): one of "bridge", "private", "vepa", "passthru". Defaults to "bridge".
* `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. The value must be \[0, master's MTU\].
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary.
## Notes

View File

@ -85,9 +85,27 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
}
n.Master = defaultRouteInterface
}
// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
if err != nil {
return nil, "", err
}
if n.MTU < 0 || n.MTU > masterMTU {
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
}
return n, n.CNIVersion, nil
}
func getMTUByName(ifName string) (int, error) {
link, err := netlink.LinkByName(ifName)
if err != nil {
return 0, err
}
return link.Attrs().MTU, nil
}
func modeFromString(s string) (netlink.MacvlanMode, error) {
switch s {
case "", "bridge":

View File

@ -123,6 +123,7 @@ var _ = Describe("macvlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an macvlan link in a non-default namespace", func() {

View File

@ -247,12 +247,25 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
result.DNS = conf.DNS
// Only override the DNS settings in the previous result if any DNS fields
// were provided to the ptp plugin. This allows, for example, IPAM plugins
// to specify the DNS settings instead of the ptp plugin.
if dnsConfSet(conf.DNS) {
result.DNS = conf.DNS
}
result.Interfaces = []*current.Interface{hostInterface, containerInterface}
return types.PrintResult(result, conf.CNIVersion)
}
func dnsConfSet(dnsConf types.DNS) bool {
return dnsConf.Nameservers != nil ||
dnsConf.Search != nil ||
dnsConf.Options != nil ||
dnsConf.Domain != ""
}
func cmdDel(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {

View File

@ -17,6 +17,7 @@ package main
import (
"encoding/json"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
@ -98,9 +99,10 @@ var _ = Describe("ptp Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
doTest := func(conf string, numIPs int) {
doTest := func(conf string, numIPs int, expectedDNSConf types.DNS) {
const IFNAME = "ptp0"
targetNs, err := testutils.NewNS()
@ -175,6 +177,9 @@ var _ = Describe("ptp Operations", func() {
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
// make sure DNS is correct
Expect(res.DNS).To(Equal(expectedDNSConf))
// Call the plugins with the DEL command, deleting the veth endpoints
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
@ -327,7 +332,16 @@ var _ = Describe("ptp Operations", func() {
}
It("configures and deconfigures a ptp link with ADD/DEL", func() {
conf := `{
dnsConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
dnsConfBytes, err := json.Marshal(dnsConf)
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
@ -336,10 +350,11 @@ var _ = Describe("ptp Operations", func() {
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
},
"dns": %s
}`, string(dnsConfBytes))
doTest(conf, 1)
doTest(conf, 1, dnsConf)
})
It("configures and deconfigures a dual-stack ptp link with ADD/DEL", func() {
@ -358,7 +373,112 @@ var _ = Describe("ptp Operations", func() {
}
}`
doTest(conf, 2)
doTest(conf, 2, types.DNS{})
})
It("does not override IPAM DNS settings if no DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
}
}`, resolvConfPath)
doTest(conf, 1, ipamDNSConf)
})
It("overrides IPAM DNS settings if any DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
for _, ptpDNSConf := range []types.DNS{
{
Nameservers: []string{"10.1.2.234"},
},
{
Domain: "someother.domain.test",
},
{
Search: []string{"search.elsewhere.test"},
},
{
Options: []string{"option2:bar"},
},
} {
dnsConfBytes, err := json.Marshal(ptpDNSConf)
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
},
"dns": %s
}`, resolvConfPath, string(dnsConfBytes))
doTest(conf, 1, ptpDNSConf)
}
})
It("overrides IPAM DNS settings if any empty list DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
},
"dns": {
"nameservers": [],
"search": [],
"options": []
}
}`, resolvConfPath)
doTest(conf, 1, types.DNS{})
})
It("deconfigures an unconfigured ptp link with DEL", func() {

View File

@ -53,14 +53,32 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
if n.Master == "" {
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to create the VLAN for.`)
return nil, "", fmt.Errorf("\"master\" field is required. It specifies the host interface name to create the VLAN for.")
}
if n.VlanId < 0 || n.VlanId > 4094 {
return nil, "", fmt.Errorf(`invalid VLAN ID %d (must be between 0 and 4095 inclusive)`, n.VlanId)
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4095 inclusive)", n.VlanId)
}
// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
if err != nil {
return nil, "", err
}
if n.MTU < 0 || n.MTU > masterMTU {
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
}
return n, n.CNIVersion, nil
}
func getMTUByName(ifName string) (int, error) {
link, err := netlink.LinkByName(ifName)
if err != nil {
return 0, err
}
return link.Attrs().MTU, nil
}
func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
vlan := &current.Interface{}
@ -76,10 +94,6 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
return nil, err
}
if conf.MTU <= 0 {
conf.MTU = m.Attrs().MTU
}
v := &netlink.Vlan{
LinkAttrs: netlink.LinkAttrs{
MTU: conf.MTU,

View File

@ -121,6 +121,7 @@ var _ = Describe("vlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an vlan link in a non-default namespace with given MTU", func() {
@ -407,4 +408,75 @@ var _ = Describe("vlan Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
Describe("fails to create vlan link with invalid MTU", func() {
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "vlan",
"master": "%s",
"mtu": %d,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
BeforeEach(func() {
var err error
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// set master link's MTU to 1500
link, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(link, 1500)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("fails to create vlan link with greater MTU than master interface", func() {
var err error
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, 1600)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU 1600, must be [0, master MTU(1500)]")))
return nil
})
})
It("fails to create vlan link with negative MTU", func() {
var err error
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, -100)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU -100, must be [0, master MTU(1500)]")))
return nil
})
})
})
})

View File

@ -32,7 +32,8 @@ With win-bridge plugin, all containers (on the same host) are plugged into an L2
"NeedEncap": true
}
}
].
],
"loopbackDSR": true,
"capabilities": {
"dns": true
}
@ -51,5 +52,6 @@ With win-bridge plugin, all containers (on the same host) are plugged into an L2
* `ipam` (dictionary, optional): IPAM configuration to be used for this network.
* `Policies` (list, optional): List of hns policies to be used (only used when ApiVersion is < 2).
* `HcnPolicyArgs` (list, optional): List of hcn policies to be used (only used when ApiVersion is 2).
* `capabilities` (dictionary, optional): runtime capabilities to enable.
* `dns` (boolean, optional): if true will take the dns config supplied by the runtime and override other settings.
* `loopbackDSR` (bool, optional): If true, will add a policy to allow the interface to support loopback direct server return.
* `capabilities` (dictionary, optional): Runtime capabilities to enable.
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.

View File

@ -39,6 +39,7 @@
"NeedEncap":true
}
}
]
],
"loopbackDSR": true
}
}

View File

@ -19,7 +19,6 @@ import (
"fmt"
"runtime"
"strings"
"os"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/hcn"
@ -39,7 +38,6 @@ type NetConf struct {
hns.NetConf
IPMasqNetwork string `json:"ipMasqNetwork,omitempty"`
ApiVersion int `json:"ApiVersion"`
}
func init() {
@ -104,7 +102,7 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
return nil, fmt.Errorf("network %v not found", networkName)
}
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") {
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") && !strings.EqualFold(hnsNetwork.Type, "L2Tunnel") {
return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type)
}
@ -146,7 +144,7 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
return nil, fmt.Errorf("network %v not found", networkName)
}
if hcnNetwork.Type != hcn.L2Bridge {
if hcnNetwork.Type != hcn.L2Bridge && hcnNetwork.Type != hcn.L2Tunnel {
return nil, fmt.Errorf("network %v is of unexpected type: %v", networkName, hcnNetwork.Type)
}
@ -191,13 +189,11 @@ func cmdAdd(args *skel.CmdArgs) error {
}
if err != nil {
os.Setenv("CNI_COMMAND", "DEL")
ipam.ExecDel(n.IPAM.Type, args.StdinData)
os.Setenv("CNI_COMMAND", "ADD")
return errors.Annotate(err, "error while executing ADD command")
}
if (result == nil) {
if result == nil {
return errors.New("result for ADD not populated correctly")
}
return types.PrintResult(result, cniVersion)

View File

@ -14,11 +14,11 @@ With win-overlay plugin, all containers (on the same host) are plugged into an O
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
},
"loopbackDSR": true,
"capabilites": {
"dns": true
}
}
```
@ -33,5 +33,6 @@ With win-overlay plugin, all containers (on the same host) are plugged into an O
* `endpointMacPrefix` (string, optional): set to the MAC prefix configured for Flannel.
* `Policies` (list, optional): List of hns policies to be used.
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
* `loopbackDSR` (bool, optional): If true, will add a policy to allow the interface to support loopback direct server return.
* `capabilities` (dictionary, optional): runtime capabilities to be parsed and injected by runtime.
* `dns` (boolean, optional): if true will take the dns config supplied by the runtime and override other settings.
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.

View File

@ -19,7 +19,6 @@ import (
"fmt"
"runtime"
"strings"
"os"
"github.com/Microsoft/hcsshim"
"github.com/juju/errors"
@ -119,7 +118,9 @@ func cmdAdd(args *skel.CmdArgs) error {
}
result.DNS = n.GetDNS()
if n.LoopbackDSR {
n.ApplyLoopbackDSR(&ipAddr)
}
hnsEndpoint := &hcsshim.HNSEndpoint{
Name: epName,
VirtualNetwork: hnsNetwork.Id,
@ -135,9 +136,7 @@ func cmdAdd(args *skel.CmdArgs) error {
})
defer func() {
if !success {
os.Setenv("CNI_COMMAND", "DEL")
ipam.ExecDel(n.IPAM.Type, args.StdinData)
os.Setenv("CNI_COMMAND", "ADD")
}
}()
if err != nil {

View File

@ -105,7 +105,7 @@ var _ = Describe("bandwidth test", func() {
hostIP = net.IP{169, 254, 0, 1}
containerIP = net.IP{10, 254, 0, 1}
hostIfaceMTU = 1024
ifbDeviceName = "5b6c"
ifbDeviceName = "bwpa8eda89404b7"
createVeth(hostNs.Path(), hostIfname, containerNs.Path(), containerIfname, hostIP, containerIP, hostIfaceMTU)
})
@ -621,6 +621,21 @@ var _ = Describe("bandwidth test", func() {
})
})
Describe("Validating input", func() {
It("Should allow only 4GB burst rate", func() {
err := validateRateAndBurst(5000, 4*1024*1024*1024*8-16) // 2 bytes less than the max should pass
Expect(err).NotTo(HaveOccurred())
err = validateRateAndBurst(5000, 4*1024*1024*1024*8) // we're 1 bit above MaxUint32
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(0, 1)
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(1, 0)
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(0, 0)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("Getting the host interface which plugin should work on from veth peer of container interface", func() {
It("Should work with multiple host veth interfaces", func() {
conf := `{
@ -874,8 +889,8 @@ var _ = Describe("bandwidth test", func() {
Context("when chaining bandwidth plugin with PTP using 0.3.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var rateInBits uint64
var burstInBits uint64
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
@ -889,7 +904,7 @@ var _ = Describe("bandwidth test", func() {
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
rateInBits = uint64(rateInBytes * 8)
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25
@ -1019,8 +1034,8 @@ var _ = Describe("bandwidth test", func() {
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var rateInBits uint64
var burstInBits uint64
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
@ -1034,7 +1049,7 @@ var _ = Describe("bandwidth test", func() {
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
rateInBits = uint64(rateInBytes * 8)
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25

View File

@ -50,7 +50,7 @@ func TeardownIfb(deviceName string) error {
return err
}
func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) error {
func CreateIngressQdisc(rateInBits, burstInBits uint64, hostDeviceName string) error {
hostDevice, err := netlink.LinkByName(hostDeviceName)
if err != nil {
return fmt.Errorf("get host device: %s", err)
@ -58,7 +58,7 @@ func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) erro
return createTBF(rateInBits, burstInBits, hostDevice.Attrs().Index)
}
func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDeviceName string) error {
func CreateEgressQdisc(rateInBits, burstInBits uint64, hostDeviceName string, ifbDeviceName string) error {
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {
return fmt.Errorf("get ifb device: %s", err)
@ -113,7 +113,7 @@ func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDe
return nil
}
func createTBF(rateInBits, burstInBits, linkIndex int) error {
func createTBF(rateInBits, burstInBits uint64, linkIndex int) error {
// Equivalent to
// tc qdisc add dev link root tbf
// rate netConf.BandwidthLimits.Rate

View File

@ -15,9 +15,9 @@
package main
import (
"crypto/sha1"
"encoding/json"
"fmt"
"math"
"github.com/vishvananda/netlink"
@ -28,17 +28,21 @@ import (
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const maxIfbDeviceLength = 15
const ifbDevicePrefix = "bwp"
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
// see CONVENTIONS.md
type BandwidthEntry struct {
IngressRate int `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
IngressBurst int `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
IngressRate uint64 `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
IngressBurst uint64 `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
EgressRate int `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
EgressBurst int `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
EgressRate uint64 `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
EgressBurst uint64 `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
}
func (bw *BandwidthEntry) isZero() bool {
@ -98,7 +102,7 @@ func getBandwidth(conf *PluginConf) *BandwidthEntry {
return conf.BandwidthEntry
}
func validateRateAndBurst(rate int, burst int) error {
func validateRateAndBurst(rate, burst uint64) error {
switch {
case burst < 0 || rate < 0:
return fmt.Errorf("rate and burst must be a positive integer")
@ -106,19 +110,15 @@ func validateRateAndBurst(rate int, burst int) error {
return fmt.Errorf("if rate is set, burst must also be set")
case rate == 0 && burst != 0:
return fmt.Errorf("if burst is set, rate must also be set")
case burst/8 >= math.MaxUint32:
return fmt.Errorf("burst cannot be more than 4GB")
}
return nil
}
func getIfbDeviceName(networkName string, containerId string) (string, error) {
hash := sha1.New()
_, err := hash.Write([]byte(networkName + containerId))
if err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil))[:4], nil
func getIfbDeviceName(networkName string, containerId string) string {
return utils.MustFormatHashWithPrefix(maxIfbDeviceLength, ifbDevicePrefix, networkName+containerId)
}
func getMTU(deviceName string) (int, error) {
@ -205,10 +205,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
err = CreateIfb(ifbDeviceName, mtu)
if err != nil {
@ -239,10 +236,7 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
if err := TeardownIfb(ifbDeviceName); err != nil {
return err
@ -343,10 +337,7 @@ func cmdCheck(args *skel.CmdArgs) error {
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDeviceName := getIfbDeviceName(bwConf.Name, args.ContainerID)
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {

View File

@ -27,7 +27,6 @@ import (
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
@ -68,9 +67,15 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
}
// Default the firewalld zone to trusted
if conf.FirewalldZone == "" {
conf.FirewalldZone = "trusted"
}
// Parse previous result.
if conf.RawPrevResult == nil {
return nil, nil, fmt.Errorf("missing prevResult from earlier plugin")
// return early if there was no previous result, which is allowed for DEL calls
return &conf, &current.Result{}, nil
}
// Parse previous result.
@ -85,11 +90,6 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
}
// Default the firewalld zone to trusted
if conf.FirewalldZone == "" {
conf.FirewalldZone = "trusted"
}
return &conf, result, nil
}
@ -116,6 +116,10 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
if conf.PrevResult == nil {
return fmt.Errorf("missing prevResult from earlier plugin")
}
backend, err := getBackend(conf)
if err != nil {
return err
@ -142,12 +146,6 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
// Tolerate errors if the container namespace has been torn down already
containerNS, err := ns.GetNS(args.Netns)
if err == nil {
defer containerNS.Close()
}
// Runtime errors are ignored
if err := backend.Del(conf, result); err != nil {
return err
@ -167,8 +165,8 @@ func cmdCheck(args *skel.CmdArgs) error {
}
// Ensure we have previous result.
if result == nil {
return fmt.Errorf("Required prevResult missing")
if conf.PrevResult == nil {
return fmt.Errorf("missing prevResult from earlier plugin")
}
backend, err := getBackend(conf)

View File

@ -270,6 +270,13 @@ var _ = Describe("firewall plugin iptables backend", func() {
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
// ensure creation is idempotent
_, _, err = testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())

View File

@ -22,6 +22,7 @@ import (
"net"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/coreos/go-iptables/iptables"
)
@ -32,20 +33,6 @@ func getPrivChainRules(ip string) [][]string {
return rules
}
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
chains, err := ipt.ListChains(table)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
}
for _, ch := range chains {
if ch == chain {
return nil
}
}
return ipt.NewChain(table, chain)
}
func generateFilterRule(privChainName string) []string {
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
}
@ -73,10 +60,10 @@ func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
adminRule := generateFilterRule(ib.adminChainName)
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}
@ -160,10 +147,10 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
}
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}

View File

@ -18,6 +18,7 @@ import (
"fmt"
"strings"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/coreos/go-iptables/iptables"
"github.com/mattn/go-shellwords"
)
@ -35,16 +36,11 @@ type chain struct {
// setup idempotently creates the chain. It will not error if the chain exists.
func (c *chain) setup(ipt *iptables.IPTables) error {
// create the chain
exists, err := chainExists(ipt, c.table, c.name)
err := utils.EnsureChain(ipt, c.table, c.name)
if err != nil {
return err
}
if !exists {
if err := ipt.NewChain(c.table, c.name); err != nil {
return err
}
}
// Add the rules to the chain
for _, rule := range c.rules {
@ -74,7 +70,7 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
// flush the chain
// This will succeed *and create the chain* if it does not exist.
// If the chain doesn't exist, the next checks will fail.
if err := ipt.ClearChain(c.table, c.name); err != nil {
if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
return err
}
@ -94,17 +90,15 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
}
chainParts = chainParts[2:] // List results always include an -A CHAINNAME
if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
if err := utils.DeleteRule(ipt, c.table, entryChain, chainParts...); err != nil {
return err
}
}
}
}
if err := ipt.DeleteChain(c.table, c.name); err != nil {
return err
}
return nil
return utils.DeleteChain(ipt, c.table, c.name)
}
// insertUnique will add a rule to a chain if it does not already exist.
@ -125,24 +119,10 @@ func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rul
}
}
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
chains, err := ipt.ListChains(tableName)
if err != nil {
return false, err
}
for _, ch := range chains {
if ch == chainName {
return true, nil
}
}
return false, nil
}
// check the chain.
func (c *chain) check(ipt *iptables.IPTables) error {
exists, err := chainExists(ipt, c.table, c.name)
exists, err := utils.ChainExists(ipt, c.table, c.name)
if err != nil {
return err
}

View File

@ -18,6 +18,7 @@ import (
"fmt"
"math/rand"
"runtime"
"sync"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -32,6 +33,7 @@ const TABLE = "filter" // We'll monkey around here
var _ = Describe("chain tests", func() {
var testChain chain
var ipt *iptables.IPTables
var testNs ns.NetNS
var cleanup func()
BeforeEach(func() {
@ -41,7 +43,7 @@ var _ = Describe("chain tests", func() {
currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS()
testNs, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
tlChainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
@ -195,4 +197,38 @@ var _ = Describe("chain tests", func() {
}
}
})
It("deletes chains idempotently in parallel", func() {
defer cleanup()
// number of parallel executions
N := 10
var wg sync.WaitGroup
err := testChain.setup(ipt)
Expect(err).NotTo(HaveOccurred())
errCh := make(chan error, N)
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// teardown chain
errCh <- testNs.Do(func(ns.NetNS) error {
return testChain.teardown(ipt)
})
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
Expect(err).NotTo(HaveOccurred())
}
chains, err := ipt.ListChains(TABLE)
Expect(err).NotTo(HaveOccurred())
for _, chain := range chains {
if chain == testChain.name {
Fail("Chain was not deleted")
}
}
})
})

View File

@ -20,6 +20,7 @@ import (
"sort"
"strconv"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
"github.com/coreos/go-iptables/iptables"
)
@ -123,7 +124,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
}
if ip4t != nil {
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
exists, err := utils.ChainExists(ip4t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
@ -136,7 +137,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
}
if ip6t != nil {
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
exists, err := utils.ChainExists(ip6t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
@ -172,7 +173,7 @@ func genToplevelDnatChain() chain {
func genDnatChain(netName, containerID string) chain {
return chain{
table: "nat",
name: formatChainName("DN-", netName, containerID),
name: utils.MustFormatChainNameWithPrefix(netName, containerID, "DN-"),
entryChains: []string{TopLevelDNATChainName},
}
}
@ -223,6 +224,16 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
// the ordering is important here; the mark rules must be first.
c.rules = make([][]string, 0, 3*len(entries))
for _, entry := range entries {
// If a HostIP is given, only process the entry if host and container address families match
if entry.HostIP != "" {
hostIP := net.ParseIP(entry.HostIP)
isHostV6 := (hostIP.To4() == nil)
if isV6 != isHostV6 {
continue
}
}
ruleBase := []string{
"-p", entry.Protocol,
"--dport", strconv.Itoa(entry.HostPort)}
@ -323,11 +334,9 @@ func enableLocalnetRouting(ifName string) error {
// 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,
name: utils.MustFormatChainNameWithPrefix(netName, containerID, "SN-"),
entryChains: []string{OldTopLevelSNATChainName},
}
}

View File

@ -96,119 +96,133 @@ var _ = Describe("portmap integration tests", func() {
}
})
// This needs to be done using Ginkgo's asynchronous testing mode.
It("forwards a TCP port on ipv4", func(done Done) {
var err error
hostPort := rand.Intn(10000) + 1025
runtimeConfig := libcni.RuntimeConf{
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
NetNS: targetNS.Path(),
IfName: "eth0",
CapabilityArgs: map[string]interface{}{
"portMappings": []map[string]interface{}{
{
"hostPort": hostPort,
"containerPort": containerPort,
"protocol": "tcp",
Describe("Creating an interface in a namespace with the ptp plugin", func() {
// This needs to be done using Ginkgo's asynchronous testing mode.
It("forwards a TCP port on ipv4", func(done Done) {
var err error
hostPort := rand.Intn(10000) + 1025
runtimeConfig := libcni.RuntimeConf{
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
NetNS: targetNS.Path(),
IfName: "eth0",
CapabilityArgs: map[string]interface{}{
"portMappings": []map[string]interface{}{
{
"hostPort": hostPort,
"containerPort": containerPort,
"protocol": "tcp",
},
},
},
},
}
// Make delete idempotent, so we can clean up on failure
netDeleted := false
deleteNetwork := func() error {
if netDeleted {
return nil
}
netDeleted = true
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
}
// we'll also manually check the iptables chains
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
// Create the network
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
Expect(err).NotTo(HaveOccurred())
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
_, err = ipt.List("nat", dnatChainName)
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(resI)
Expect(err).NotTo(HaveOccurred())
var contIP net.IP
for _, ip := range result.IPs {
intfIndex := *ip.Interface
if result.Interfaces[intfIndex].Sandbox == "" {
continue
// Make delete idempotent, so we can clean up on failure
netDeleted := false
deleteNetwork := func() error {
if netDeleted {
return nil
}
netDeleted = true
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
}
contIP = ip.Address.IP
}
if contIP == nil {
Fail("could not determine container IP")
}
hostIP := getLocalIP()
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
hostIP, hostPort, contIP, containerPort)
// we'll also manually check the iptables chains
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
// dump iptables-save output for debugging
cmd = exec.Command("iptables-save")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Create the network
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
Expect(err).NotTo(HaveOccurred())
defer deleteNetwork()
// Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "")
// Undo Docker's forwarding policy
cmd := exec.Command("iptables", "-t", "filter",
"-P", "FORWARD", "ACCEPT")
cmd.Stderr = GinkgoWriter
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())
// Verify that a connection to the forwarded port works
dnatOK := testEchoServer(hostIP, hostPort, "")
// Check the chain exists
_, err = ipt.List("nat", dnatChainName)
Expect(err).NotTo(HaveOccurred())
// Verify that a connection to localhost works
snatOK := testEchoServer("127.0.0.1", hostPort, "")
result, err := current.GetResult(resI)
Expect(err).NotTo(HaveOccurred())
var contIP net.IP
// verify that hairpin works
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
for _, ip := range result.IPs {
intfIndex := *ip.Interface
if result.Interfaces[intfIndex].Sandbox == "" {
continue
}
contIP = ip.Address.IP
}
if contIP == nil {
Fail("could not determine container IP")
}
// Cleanup
session.Terminate()
err = deleteNetwork()
Expect(err).NotTo(HaveOccurred())
hostIP := getLocalIP()
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
hostIP, hostPort, contIP, containerPort)
// Verify iptables rules are gone
_, err = ipt.List("nat", dnatChainName)
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
// dump iptables-save output for debugging
cmd = exec.Command("iptables-save")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Check that everything succeeded *after* we clean up the network
if !contOK {
Fail("connection direct to " + contIP.String() + " failed")
}
if !dnatOK {
Fail("Connection to " + hostIP + " was not forwarded")
}
if !snatOK {
Fail("connection to 127.0.0.1 was not forwarded")
}
if !hairpinOK {
Fail("Hairpin connection failed")
}
// dump ip routes output for debugging
cmd = exec.Command("ip", "route")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
close(done)
// dump ip addresses output for debugging
cmd = exec.Command("ip", "addr")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
}, TIMEOUT*9)
// Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "")
// Verify that a connection to the forwarded port works
dnatOK := testEchoServer(hostIP, hostPort, "")
// Verify that a connection to localhost works
snatOK := testEchoServer("127.0.0.1", hostPort, "")
// verify that hairpin works
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
// Cleanup
session.Terminate()
err = deleteNetwork()
Expect(err).NotTo(HaveOccurred())
// Verify iptables rules are gone
_, err = ipt.List("nat", dnatChainName)
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
// Check that everything succeeded *after* we clean up the network
if !contOK {
Fail("connection direct to " + contIP.String() + " failed")
}
if !dnatOK {
Fail("Connection to " + hostIP + " was not forwarded")
}
if !snatOK {
Fail("connection to 127.0.0.1 was not forwarded")
}
if !hairpinOK {
Fail("Hairpin connection failed")
}
close(done)
}, TIMEOUT*9)
})
})
// testEchoServer returns true if we found an echo server on the port

View File

@ -15,7 +15,6 @@
package main
import (
"crypto/sha512"
"fmt"
"net"
"strconv"
@ -24,8 +23,6 @@ import (
"github.com/vishvananda/netlink"
)
const maxChainNameLength = 28
// fmtIpPort correctly formats ip:port literals for iptables and ip6tables -
// need to wrap v6 literals in a []
func fmtIpPort(ip net.IP, port int) string {
@ -62,12 +59,6 @@ func getRoutableHostIF(containerIP net.IP) string {
return ""
}
func formatChainName(prefix, name, id string) string {
chainBytes := sha512.Sum512([]byte(name + id))
chain := fmt.Sprintf("CNI-%s%x", prefix, chainBytes)
return chain[:maxChainNameLength]
}
// groupByProto groups port numbers by protocol
func groupByProto(entries []PortMapEntry) map[string][]int {
if len(entries) == 0 {

View File

@ -61,3 +61,13 @@ The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/
Note: You may add `IgnoreUnknown=true` to allow loose CNI argument verification (see CNI's issue[#560](https://github.com/containernetworking/cni/issues/560)).
The plugin also support following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md).
* `mac`: Pass MAC addresses for CNI interface
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
* `mac` (string, optional): MAC address (i.e. hardware address) of interface
* `mtu` (integer, optional): MTU of interface
* `promisc` (bool, optional): Change the promiscuous mode of interface
* `sysctl` (object, optional): Change system controls

View File

@ -25,6 +25,7 @@ import (
"path/filepath"
"strings"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
@ -43,9 +44,24 @@ type TuningConf struct {
Mac string `json:"mac,omitempty"`
Promisc bool `json:"promisc,omitempty"`
Mtu int `json:"mtu,omitempty"`
RuntimeConfig struct {
Mac string `json:"mac,omitempty"`
} `json:"runtimeConfig,omitempty"`
Args *struct {
A *IPAMArgs `json:"cni"`
} `json:"args"`
}
type MACEnvArgs struct {
type IPAMArgs struct {
SysCtl *map[string]string `json:"sysctl"`
Mac *string `json:"mac,omitempty"`
Promisc *bool `json:"promisc,omitempty"`
Mtu *int `json:"mtu,omitempty"`
}
// MacEnvArgs represents CNI_ARG
type MacEnvArgs struct {
types.CommonArgs
MAC types.UnmarshallableString `json:"mac,omitempty"`
}
@ -56,9 +72,9 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
return nil, fmt.Errorf("failed to load netconf: %v", err)
}
// Parse custom MAC from both env args
// Parse custom Mac from both env args
if envArgs != "" {
e := MACEnvArgs{}
e := MacEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, err
@ -69,6 +85,33 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
}
}
// Parse custom Mac from RuntimeConfig
if conf.RuntimeConfig.Mac != "" {
conf.Mac = conf.RuntimeConfig.Mac
}
// Get args
if conf.Args != nil && conf.Args.A != nil {
if conf.Args.A.SysCtl != nil {
for k, v := range *conf.Args.A.SysCtl {
conf.SysCtl[k] = v
}
}
if conf.Args.A.Mac != nil {
conf.Mac = *conf.Args.A.Mac
}
if conf.Args.A.Promisc != nil {
conf.Promisc = *conf.Args.A.Promisc
}
if conf.Args.A.Mtu != nil {
conf.Mtu = *conf.Args.A.Mtu
}
}
return &conf, nil
}
@ -83,15 +126,7 @@ func changeMacAddr(ifName string, newMacAddr string) error {
return fmt.Errorf("failed to get %q: %v", ifName, err)
}
err = netlink.LinkSetDown(link)
if err != nil {
return fmt.Errorf("failed to set %q down: %v", ifName, err)
}
err = netlink.LinkSetHardwareAddr(link, addr)
if err != nil {
return fmt.Errorf("failed to set %q address to %q: %v", ifName, newMacAddr, err)
}
return netlink.LinkSetUp(link)
return netlink.LinkSetHardwareAddr(link, addr)
}
func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) {
@ -145,7 +180,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
result, err := current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
@ -174,6 +209,13 @@ func cmdAdd(args *skel.CmdArgs) error {
if err = changeMacAddr(args.IfName, tuningConf.Mac); err != nil {
return err
}
for _, ipc := range result.IPs {
if ipc.Version == "4" {
_ = arping.GratuitousArpOverIfaceByName(ipc.Address.IP, args.IfName)
}
}
updateResultsMacAddr(*tuningConf, args.IfName, tuningConf.Mac)
}
@ -230,7 +272,7 @@ func cmdCheck(args *skel.CmdArgs) error {
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
// Check each configured value vs what's currently in the container
for key, conf_value := range tuningConf.SysCtl {
for key, confValue := range tuningConf.SysCtl {
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
fileName = filepath.Clean(fileName)
@ -238,9 +280,9 @@ func cmdCheck(args *skel.CmdArgs) error {
if err != nil {
return err
}
cur_value := strings.TrimSuffix(string(contents), "\n")
if conf_value != cur_value {
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, conf_value, cur_value)
curValue := strings.TrimSuffix(string(contents), "\n")
if confValue != curValue {
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, confValue, curValue)
}
}

View File

@ -215,6 +215,67 @@ var _ = Describe("tuning plugin", func() {
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures promiscuous mode from args with ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.3.1",
"args": {
"cni": {
"promisc": true
}
},
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Promisc).To(Equal(1))
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mtu with ADD/DEL", func() {
conf := []byte(`{
"name": "test",
@ -272,6 +333,67 @@ var _ = Describe("tuning plugin", func() {
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mtu from args with ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.3.1",
"args": {
"cni": {
"mtu": 1454
}
},
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().MTU).To(Equal(1454))
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from conf file) with ADD/DEL", func() {
conf := []byte(`{
"name": "test",
@ -331,6 +453,69 @@ var _ = Describe("tuning plugin", func() {
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from args) with ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.3.1",
"args": {
"cni": {
"mac": "c2:11:22:33:44:55"
}
},
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:55")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from CNI_ARGS) with ADD/DEL", func() {
conf := []byte(`{
"name": "test",
@ -379,6 +564,7 @@ var _ = Describe("tuning plugin", func() {
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:66")
Expect(err).NotTo(HaveOccurred())
fmt.Printf("%v, %v\n", link.Attrs().HardwareAddr, hw)
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
err = testutils.CmdDel(originalNS.Path(),
@ -681,4 +867,67 @@ var _ = Describe("tuning plugin", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from RuntimeConfig) with ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.3.1",
"capabilities": {"mac": true},
"RuntimeConfig": {
"mac": "c2:11:22:33:44:55"
},
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:55")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})

View File

@ -21,7 +21,7 @@ $DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --r
apk --no-cache add bash tar;
cd /go/src/github.com/containernetworking/plugins; umask 0022;
for arch in amd64 arm arm64 ppc64le s390x; do \
for arch in amd64 arm arm64 ppc64le s390x mips64le; do \
rm -f ${OUTPUT_DIR}/*; \
CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \
for format in tgz; do \

View File

@ -57,3 +57,7 @@ if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}"
exit 255
fi
# Run the pkg/ns tests as non root user
mkdir /tmp/cni-rootless
(export XDG_RUNTIME_DIR=/tmp/cni-rootless; cd pkg/ns/; unshare -rmn go test)

View File

@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,22 +0,0 @@
runhcs is a fork of runc.
The following is runc's legal notice.
---
runc
Copyright 2012-2015 Docker, Inc.
This product includes software developed at Docker, Inc. (http://www.docker.com).
The following is courtesy of our legal counsel:
Use and transfer of Docker may be subject to certain restrictions by the
United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not
violate applicable laws.
For more information, please see http://www.bis.doc.gov
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.

View File

@ -1,848 +0,0 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/cni"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/hcsoci"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/regstate"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/Microsoft/hcsshim/osversion"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/sys/windows"
)
var errContainerStopped = errors.New("container is stopped")
type persistedState struct {
// ID is the id of this container/UVM.
ID string `json:",omitempty"`
// Owner is the owner value passed into the runhcs command and may be `""`.
Owner string `json:",omitempty"`
// SandboxID is the sandbox identifer passed in via OCI specifications. This
// can either be the sandbox itself or the sandbox this container should run
// in. See `parseSandboxAnnotations`.
SandboxID string `json:",omitempty"`
// HostID will be VM ID hosting this container. If a sandbox is used it will
// match the `SandboxID`.
HostID string `json:",omitempty"`
// Bundle is the folder path on disk where the container state and spec files
// reside.
Bundle string `json:",omitempty"`
Created time.Time `json:",omitempty"`
Rootfs string `json:",omitempty"`
// Spec is the in memory deserialized values found on `Bundle\config.json`.
Spec *specs.Spec `json:",omitempty"`
RequestedNetNS string `json:",omitempty"`
// IsHost is `true` when this is a VM isolated config.
IsHost bool `json:",omitempty"`
// UniqueID is a unique ID generated per container config.
UniqueID guid.GUID `json:",omitempty"`
// HostUniqueID is the unique ID of the hosting VM if this container is
// hosted.
HostUniqueID guid.GUID `json:",omitempty"`
}
type containerStatus string
const (
containerRunning containerStatus = "running"
containerStopped containerStatus = "stopped"
containerCreated containerStatus = "created"
containerPaused containerStatus = "paused"
containerUnknown containerStatus = "unknown"
keyState = "state"
keyResources = "resources"
keyShimPid = "shim"
keyInitPid = "pid"
keyNetNS = "netns"
// keyPidMapFmt is the format to use when mapping a host OS pid to a guest
// pid.
keyPidMapFmt = "pid-%d"
)
type container struct {
persistedState
ShimPid int
hc *hcs.System
resources *hcsoci.Resources
}
func startProcessShim(id, pidFile, logFile string, spec *specs.Process) (_ *os.Process, err error) {
// Ensure the stdio handles inherit to the child process. This isn't undone
// after the StartProcess call because the caller never launches another
// process before exiting.
for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} {
err = windows.SetHandleInformation(windows.Handle(f.Fd()), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT)
if err != nil {
return nil, err
}
}
args := []string{
"--stdin", strconv.Itoa(int(os.Stdin.Fd())),
"--stdout", strconv.Itoa(int(os.Stdout.Fd())),
"--stderr", strconv.Itoa(int(os.Stderr.Fd())),
}
if spec != nil {
args = append(args, "--exec")
}
if strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
args = append(args, "--log-pipe", logFile)
}
args = append(args, id)
return launchShim("shim", pidFile, logFile, args, spec)
}
func launchShim(cmd, pidFile, logFile string, args []string, data interface{}) (_ *os.Process, err error) {
executable, err := os.Executable()
if err != nil {
return nil, err
}
// Create a pipe to use as stderr for the shim process. This is used to
// retrieve early error information, up to the point that the shim is ready
// to launch a process in the container.
rp, wp, err := os.Pipe()
if err != nil {
return nil, err
}
defer rp.Close()
defer wp.Close()
// Create a pipe to send the data, if one is provided.
var rdatap, wdatap *os.File
if data != nil {
rdatap, wdatap, err = os.Pipe()
if err != nil {
return nil, err
}
defer rdatap.Close()
defer wdatap.Close()
}
var log *os.File
fullargs := []string{os.Args[0]}
if logFile != "" {
if !strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
log, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666)
if err != nil {
return nil, err
}
defer log.Close()
}
fullargs = append(fullargs, "--log-format", logFormat)
if logrus.GetLevel() == logrus.DebugLevel {
fullargs = append(fullargs, "--debug")
}
}
fullargs = append(fullargs, cmd)
fullargs = append(fullargs, args...)
attr := &os.ProcAttr{
Files: []*os.File{rdatap, wp, log},
}
p, err := os.StartProcess(executable, fullargs, attr)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
p.Kill()
}
}()
wp.Close()
// Write the data if provided.
if data != nil {
rdatap.Close()
dataj, err := json.Marshal(data)
if err != nil {
return nil, err
}
_, err = wdatap.Write(dataj)
if err != nil {
return nil, err
}
wdatap.Close()
}
err = runhcs.GetErrorFromPipe(rp, p)
if err != nil {
return nil, err
}
if pidFile != "" {
if err = createPidFile(pidFile, p.Pid); err != nil {
return nil, err
}
}
return p, nil
}
// parseSandboxAnnotations searches `a` for various annotations used by
// different runtimes to represent a sandbox ID, and sandbox type.
//
// If found returns the tuple `(sandboxID, isSandbox)` where `isSandbox == true`
// indicates the identifer is the sandbox itself; `isSandbox == false` indicates
// the identifer is the sandbox in which to place this container. Otherwise
// returns `("", false)`.
func parseSandboxAnnotations(a map[string]string) (string, bool) {
var t, id string
if t = a["io.kubernetes.cri.container-type"]; t != "" {
id = a["io.kubernetes.cri.sandbox-id"]
} else if t = a["io.kubernetes.cri-o.ContainerType"]; t != "" {
id = a["io.kubernetes.cri-o.SandboxID"]
} else if t = a["io.kubernetes.docker.type"]; t != "" {
id = a["io.kubernetes.sandbox.id"]
if t == "podsandbox" {
t = "sandbox"
}
}
if t == "container" {
return id, false
}
if t == "sandbox" {
return id, true
}
return "", false
}
// parseAnnotationsBool searches `a` for `key` and if found verifies that the
// value is `true` or `false` in any case. If `key` is not found returns `def`.
func parseAnnotationsBool(a map[string]string, key string, def bool) bool {
if v, ok := a[key]; ok {
switch strings.ToLower(v) {
case "true":
return true
case "false":
return false
default:
logrus.WithFields(logrus.Fields{
logfields.OCIAnnotation: key,
logfields.Value: v,
logfields.ExpectedType: logfields.Bool,
}).Warning("annotation could not be parsed")
}
}
return def
}
// parseAnnotationsCPU searches `s.Annotations` for the CPU annotation. If
// not found searches `s` for the Windows CPU section. If neither are found
// returns `def`.
func parseAnnotationsCPU(s *specs.Spec, annotation string, def int32) int32 {
if m := parseAnnotationsUint64(s.Annotations, annotation, 0); m != 0 {
return int32(m)
}
if s.Windows != nil &&
s.Windows.Resources != nil &&
s.Windows.Resources.CPU != nil &&
s.Windows.Resources.CPU.Count != nil &&
*s.Windows.Resources.CPU.Count > 0 {
return int32(*s.Windows.Resources.CPU.Count)
}
return def
}
// parseAnnotationsMemory searches `s.Annotations` for the memory annotation. If
// not found searches `s` for the Windows memory section. If neither are found
// returns `def`.
func parseAnnotationsMemory(s *specs.Spec, annotation string, def int32) int32 {
if m := parseAnnotationsUint64(s.Annotations, annotation, 0); m != 0 {
return int32(m)
}
if s.Windows != nil &&
s.Windows.Resources != nil &&
s.Windows.Resources.Memory != nil &&
s.Windows.Resources.Memory.Limit != nil &&
*s.Windows.Resources.Memory.Limit > 0 {
return int32(*s.Windows.Resources.Memory.Limit)
}
return def
}
// parseAnnotationsPreferredRootFSType searches `a` for `key` and verifies that the
// value is in the set of allowed values. If `key` is not found returns `def`.
func parseAnnotationsPreferredRootFSType(a map[string]string, key string, def uvm.PreferredRootFSType) uvm.PreferredRootFSType {
if v, ok := a[key]; ok {
switch v {
case "initrd":
return uvm.PreferredRootFSTypeInitRd
case "vhd":
return uvm.PreferredRootFSTypeVHD
default:
logrus.Warningf("annotation: '%s', with value: '%s' must be 'initrd' or 'vhd'", key, v)
}
}
return def
}
// parseAnnotationsUint32 searches `a` for `key` and if found verifies that the
// value is a 32 bit unsigned integer. If `key` is not found returns `def`.
func parseAnnotationsUint32(a map[string]string, key string, def uint32) uint32 {
if v, ok := a[key]; ok {
countu, err := strconv.ParseUint(v, 10, 32)
if err == nil {
v := uint32(countu)
return v
}
logrus.WithFields(logrus.Fields{
logfields.OCIAnnotation: key,
logfields.Value: v,
logfields.ExpectedType: logfields.Uint32,
logrus.ErrorKey: err,
}).Warning("annotation could not be parsed")
}
return def
}
// parseAnnotationsUint64 searches `a` for `key` and if found verifies that the
// value is a 64 bit unsigned integer. If `key` is not found returns `def`.
func parseAnnotationsUint64(a map[string]string, key string, def uint64) uint64 {
if v, ok := a[key]; ok {
countu, err := strconv.ParseUint(v, 10, 64)
if err == nil {
return countu
}
logrus.WithFields(logrus.Fields{
logfields.OCIAnnotation: key,
logfields.Value: v,
logfields.ExpectedType: logfields.Uint64,
logrus.ErrorKey: err,
}).Warning("annotation could not be parsed")
}
return def
}
// startVMShim starts a vm-shim command with the specified `opts`. `opts` can be `uvm.OptionsWCOW` or `uvm.OptionsLCOW`
func (c *container) startVMShim(logFile string, opts interface{}) (*os.Process, error) {
var os string
if _, ok := opts.(*uvm.OptionsLCOW); ok {
os = "linux"
} else {
os = "windows"
}
args := []string{"--os", os}
if strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
args = append(args, "--log-pipe", logFile)
}
args = append(args, c.VMPipePath())
return launchShim("vmshim", "", logFile, args, opts)
}
type containerConfig struct {
ID string
Owner string
HostID string
PidFile string
ShimLogFile, VMLogFile string
Spec *specs.Spec
VMConsolePipe string
}
func createContainer(cfg *containerConfig) (_ *container, err error) {
// Store the container information in a volatile registry key.
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
vmisolated := cfg.Spec.Linux != nil || (cfg.Spec.Windows != nil && cfg.Spec.Windows.HyperV != nil)
sandboxID, isSandbox := parseSandboxAnnotations(cfg.Spec.Annotations)
hostID := cfg.HostID
if isSandbox {
if sandboxID != cfg.ID {
return nil, errors.New("sandbox ID must match ID")
}
} else if sandboxID != "" {
// Validate that the sandbox container exists.
sandbox, err := getContainer(sandboxID, false)
if err != nil {
return nil, err
}
defer sandbox.Close()
if sandbox.SandboxID != sandboxID {
return nil, fmt.Errorf("container %s is not a sandbox", sandboxID)
}
if hostID == "" {
// Use the sandbox's host.
hostID = sandbox.HostID
} else if sandbox.HostID == "" {
return nil, fmt.Errorf("sandbox container %s is not running in a VM host, but host %s was specified", sandboxID, hostID)
} else if hostID != sandbox.HostID {
return nil, fmt.Errorf("sandbox container %s has a different host %s from the requested host %s", sandboxID, sandbox.HostID, hostID)
}
if vmisolated && hostID == "" {
return nil, fmt.Errorf("container %s is not a VM isolated sandbox", sandboxID)
}
}
uniqueID := guid.New()
newvm := false
var hostUniqueID guid.GUID
if hostID != "" {
host, err := getContainer(hostID, false)
if err != nil {
return nil, err
}
defer host.Close()
if !host.IsHost {
return nil, fmt.Errorf("host container %s is not a VM host", hostID)
}
hostUniqueID = host.UniqueID
} else if vmisolated && (isSandbox || cfg.Spec.Linux != nil || osversion.Get().Build >= osversion.RS5) {
// This handles all LCOW, Pod Sandbox, and (Windows Xenon V2 for RS5+)
hostID = cfg.ID
newvm = true
hostUniqueID = uniqueID
}
// Make absolute the paths in Root.Path and Windows.LayerFolders.
rootfs := ""
if cfg.Spec.Root != nil {
rootfs = cfg.Spec.Root.Path
if rootfs != "" && !filepath.IsAbs(rootfs) && !strings.HasPrefix(rootfs, `\\?\`) {
rootfs = filepath.Join(cwd, rootfs)
cfg.Spec.Root.Path = rootfs
}
}
netNS := ""
if cfg.Spec.Windows != nil {
for i, f := range cfg.Spec.Windows.LayerFolders {
if !filepath.IsAbs(f) && !strings.HasPrefix(rootfs, `\\?\`) {
cfg.Spec.Windows.LayerFolders[i] = filepath.Join(cwd, f)
}
}
// Determine the network namespace to use.
if cfg.Spec.Windows.Network != nil {
if cfg.Spec.Windows.Network.NetworkSharedContainerName != "" {
// RS4 case
err = stateKey.Get(cfg.Spec.Windows.Network.NetworkSharedContainerName, keyNetNS, &netNS)
if err != nil {
if _, ok := err.(*regstate.NoStateError); !ok {
return nil, err
}
}
} else if cfg.Spec.Windows.Network.NetworkNamespace != "" {
// RS5 case
netNS = cfg.Spec.Windows.Network.NetworkNamespace
}
}
}
// Store the initial container state in the registry so that the delete
// command can clean everything up if something goes wrong.
c := &container{
persistedState: persistedState{
ID: cfg.ID,
Owner: cfg.Owner,
Bundle: cwd,
Rootfs: rootfs,
Created: time.Now(),
Spec: cfg.Spec,
SandboxID: sandboxID,
HostID: hostID,
IsHost: newvm,
RequestedNetNS: netNS,
UniqueID: uniqueID,
HostUniqueID: hostUniqueID,
},
}
err = stateKey.Create(cfg.ID, keyState, &c.persistedState)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
c.Remove()
}
}()
if isSandbox && vmisolated {
cnicfg := cni.NewPersistedNamespaceConfig(netNS, cfg.ID, hostUniqueID)
err = cnicfg.Store()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
cnicfg.Remove()
}
}()
}
// Start a VM if necessary.
if newvm {
var opts interface{}
const (
annotationAllowOvercommit = "io.microsoft.virtualmachine.computetopology.memory.allowovercommit"
annotationEnableDeferredCommit = "io.microsoft.virtualmachine.computetopology.memory.enabledeferredcommit"
annotationMemorySizeInMB = "io.microsoft.virtualmachine.computetopology.memory.sizeinmb"
annotationProcessorCount = "io.microsoft.virtualmachine.computetopology.processor.count"
annotationVPMemCount = "io.microsoft.virtualmachine.devices.virtualpmem.maximumcount"
annotationVPMemSize = "io.microsoft.virtualmachine.devices.virtualpmem.maximumsizebytes"
annotationPreferredRootFSType = "io.microsoft.virtualmachine.lcow.preferredrootfstype"
)
if cfg.Spec.Linux != nil {
lopts := uvm.NewDefaultOptionsLCOW(vmID(c.ID), cfg.Owner)
lopts.MemorySizeInMB = parseAnnotationsMemory(cfg.Spec, annotationMemorySizeInMB, lopts.MemorySizeInMB)
lopts.AllowOvercommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationAllowOvercommit, lopts.AllowOvercommit)
lopts.EnableDeferredCommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationEnableDeferredCommit, lopts.EnableDeferredCommit)
lopts.ProcessorCount = parseAnnotationsCPU(cfg.Spec, annotationProcessorCount, lopts.ProcessorCount)
lopts.ConsolePipe = cfg.VMConsolePipe
lopts.VPMemDeviceCount = parseAnnotationsUint32(cfg.Spec.Annotations, annotationVPMemCount, lopts.VPMemDeviceCount)
lopts.VPMemSizeBytes = parseAnnotationsUint64(cfg.Spec.Annotations, annotationVPMemSize, lopts.VPMemSizeBytes)
lopts.PreferredRootFSType = parseAnnotationsPreferredRootFSType(cfg.Spec.Annotations, annotationPreferredRootFSType, lopts.PreferredRootFSType)
switch lopts.PreferredRootFSType {
case uvm.PreferredRootFSTypeInitRd:
lopts.RootFSFile = uvm.InitrdFile
case uvm.PreferredRootFSTypeVHD:
lopts.RootFSFile = uvm.VhdFile
}
opts = lopts
} else {
wopts := uvm.NewDefaultOptionsWCOW(vmID(c.ID), cfg.Owner)
wopts.MemorySizeInMB = parseAnnotationsMemory(cfg.Spec, annotationMemorySizeInMB, wopts.MemorySizeInMB)
wopts.AllowOvercommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationAllowOvercommit, wopts.AllowOvercommit)
wopts.EnableDeferredCommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationEnableDeferredCommit, wopts.EnableDeferredCommit)
wopts.ProcessorCount = parseAnnotationsCPU(cfg.Spec, annotationProcessorCount, wopts.ProcessorCount)
// In order for the UVM sandbox.vhdx not to collide with the actual
// nested Argon sandbox.vhdx we append the \vm folder to the last entry
// in the list.
layersLen := len(cfg.Spec.Windows.LayerFolders)
layers := make([]string, layersLen)
copy(layers, cfg.Spec.Windows.LayerFolders)
vmPath := filepath.Join(layers[layersLen-1], "vm")
err := os.MkdirAll(vmPath, 0)
if err != nil {
return nil, err
}
layers[layersLen-1] = vmPath
wopts.LayerFolders = layers
opts = wopts
}
shim, err := c.startVMShim(cfg.VMLogFile, opts)
if err != nil {
return nil, err
}
shim.Release()
}
if c.HostID != "" {
// Call to the VM shim process to create the container. This is done so
// that the VM process can keep track of the VM's virtual hardware
// resource use.
err = c.issueVMRequest(runhcs.OpCreateContainer)
if err != nil {
return nil, err
}
c.hc, err = hcs.OpenComputeSystem(cfg.ID)
if err != nil {
return nil, err
}
} else {
// Create the container directly from this process.
err = createContainerInHost(c, nil)
if err != nil {
return nil, err
}
}
// Create the shim process for the container.
err = startContainerShim(c, cfg.PidFile, cfg.ShimLogFile)
if err != nil {
if e := c.Kill(); e == nil {
c.Remove()
}
return nil, err
}
return c, nil
}
func (c *container) ShimPipePath() string {
return runhcs.SafePipePath("runhcs-shim-" + c.UniqueID.String())
}
func (c *container) VMPipePath() string {
return runhcs.VMPipePath(c.HostUniqueID)
}
func (c *container) VMIsolated() bool {
return c.HostID != ""
}
func (c *container) unmountInHost(vm *uvm.UtilityVM, all bool) error {
resources := &hcsoci.Resources{}
err := stateKey.Get(c.ID, keyResources, resources)
if _, ok := err.(*regstate.NoStateError); ok {
return nil
}
if err != nil {
return err
}
err = hcsoci.ReleaseResources(resources, vm, all)
if err != nil {
stateKey.Set(c.ID, keyResources, resources)
return err
}
err = stateKey.Clear(c.ID, keyResources)
if err != nil {
return err
}
return nil
}
func (c *container) Unmount(all bool) error {
if c.VMIsolated() {
op := runhcs.OpUnmountContainerDiskOnly
if all {
op = runhcs.OpUnmountContainer
}
err := c.issueVMRequest(op)
if err != nil {
if _, ok := err.(*noVMError); ok {
logrus.WithFields(logrus.Fields{
logfields.ContainerID: c.ID,
logfields.UVMID: c.HostID,
logrus.ErrorKey: errors.New("failed to unmount container resources"),
}).Warning("VM shim could not be contacted")
} else {
return err
}
}
} else {
c.unmountInHost(nil, false)
}
return nil
}
func createContainerInHost(c *container, vm *uvm.UtilityVM) (err error) {
if c.hc != nil {
return errors.New("container already created")
}
// Create the container without starting it.
opts := &hcsoci.CreateOptions{
ID: c.ID,
Owner: c.Owner,
Spec: c.Spec,
HostingSystem: vm,
NetworkNamespace: c.RequestedNetNS,
}
vmid := ""
if vm != nil {
vmid = vm.ID()
}
logrus.WithFields(logrus.Fields{
logfields.ContainerID: c.ID,
logfields.UVMID: vmid,
}).Info("creating container in UVM")
hc, resources, err := hcsoci.CreateContainer(opts)
if err != nil {
return err
}
defer func() {
if err != nil {
hc.Terminate()
hc.Wait()
hcsoci.ReleaseResources(resources, vm, true)
}
}()
// Record the network namespace to support namespace sharing by container ID.
if resources.NetNS() != "" {
err = stateKey.Set(c.ID, keyNetNS, resources.NetNS())
if err != nil {
return err
}
}
err = stateKey.Set(c.ID, keyResources, resources)
if err != nil {
return err
}
c.hc = hc
return nil
}
func startContainerShim(c *container, pidFile, logFile string) error {
// Launch a shim process to later execute a process in the container.
shim, err := startProcessShim(c.ID, pidFile, logFile, nil)
if err != nil {
return err
}
defer shim.Release()
defer func() {
if err != nil {
shim.Kill()
}
}()
c.ShimPid = shim.Pid
err = stateKey.Set(c.ID, keyShimPid, shim.Pid)
if err != nil {
return err
}
if pidFile != "" {
if err = createPidFile(pidFile, shim.Pid); err != nil {
return err
}
}
return nil
}
func (c *container) Close() error {
if c.hc == nil {
return nil
}
return c.hc.Close()
}
func (c *container) Exec() error {
err := c.hc.Start()
if err != nil {
return err
}
if c.Spec.Process == nil {
return nil
}
// Alert the shim that the container is ready.
pipe, err := winio.DialPipe(c.ShimPipePath(), nil)
if err != nil {
return err
}
defer pipe.Close()
shim, err := os.FindProcess(c.ShimPid)
if err != nil {
return err
}
defer shim.Release()
err = runhcs.GetErrorFromPipe(pipe, shim)
if err != nil {
return err
}
return nil
}
func getContainer(id string, notStopped bool) (*container, error) {
var c container
err := stateKey.Get(id, keyState, &c.persistedState)
if err != nil {
return nil, err
}
err = stateKey.Get(id, keyShimPid, &c.ShimPid)
if err != nil {
if _, ok := err.(*regstate.NoStateError); !ok {
return nil, err
}
c.ShimPid = -1
}
if notStopped && c.ShimPid == 0 {
return nil, errContainerStopped
}
hc, err := hcs.OpenComputeSystem(c.ID)
if err == nil {
c.hc = hc
} else if !hcs.IsNotExist(err) {
return nil, err
} else if notStopped {
return nil, errContainerStopped
}
return &c, nil
}
func (c *container) Remove() error {
// Unmount any layers or mapped volumes.
err := c.Unmount(!c.IsHost)
if err != nil {
return err
}
// Follow kata's example and delay tearing down the VM until the owning
// container is removed.
if c.IsHost {
vm, err := hcs.OpenComputeSystem(vmID(c.ID))
if err == nil {
if err := vm.Terminate(); hcs.IsPending(err) {
vm.Wait()
}
}
}
return stateKey.Remove(c.ID)
}
func (c *container) Kill() error {
if c.hc == nil {
return nil
}
err := c.hc.Terminate()
if hcs.IsPending(err) {
err = c.hc.Wait()
}
if hcs.IsAlreadyStopped(err) {
err = nil
}
return err
}
func (c *container) Status() (containerStatus, error) {
if c.hc == nil || c.ShimPid == 0 {
return containerStopped, nil
}
props, err := c.hc.Properties()
if err != nil {
if !strings.Contains(err.Error(), "operation is not valid in the current state") {
return "", err
}
return containerUnknown, nil
}
state := containerUnknown
switch props.State {
case "", "Created":
state = containerCreated
case "Running":
state = containerRunning
case "Paused":
state = containerPaused
case "Stopped":
state = containerStopped
}
return state, nil
}

View File

@ -1,71 +0,0 @@
package main
import (
"os"
"path/filepath"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/lcow"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/Microsoft/hcsshim/osversion"
gcsclient "github.com/Microsoft/opengcs/client"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var createScratchCommand = cli.Command{
Name: "create-scratch",
Usage: "creates a scratch vhdx at 'destpath' that is ext4 formatted",
Description: "Creates a scratch vhdx at 'destpath' that is ext4 formatted",
Flags: []cli.Flag{
cli.StringFlag{
Name: "destpath",
Usage: "Required: describes the destination vhd path",
},
},
Before: appargs.Validate(),
Action: func(context *cli.Context) error {
dest := context.String("destpath")
if dest == "" {
return errors.New("'destpath' is required")
}
// If we only have v1 lcow support do it the old way.
if osversion.Get().Build < osversion.RS5 {
cfg := gcsclient.Config{
Options: gcsclient.Options{
KirdPath: filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers"),
KernelFile: "kernel",
InitrdFile: uvm.InitrdFile,
},
Name: "createscratch-uvm",
UvmTimeoutSeconds: 5 * 60, // 5 Min
}
if err := cfg.StartUtilityVM(); err != nil {
return errors.Wrapf(err, "failed to start '%s'", cfg.Name)
}
defer cfg.Uvm.Terminate()
if err := cfg.CreateExt4Vhdx(dest, lcow.DefaultScratchSizeGB, ""); err != nil {
return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", cfg.Name)
}
} else {
opts := uvm.NewDefaultOptionsLCOW("createscratch-uvm", context.GlobalString("owner"))
convertUVM, err := uvm.CreateLCOW(opts)
if err != nil {
return errors.Wrapf(err, "failed to create '%s'", opts.ID)
}
defer convertUVM.Close()
if err := convertUVM.Start(); err != nil {
return errors.Wrapf(err, "failed to start '%s'", opts.ID)
}
if err := lcow.CreateScratch(convertUVM, dest, lcow.DefaultScratchSizeGB, "", ""); err != nil {
return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", opts.ID)
}
}
return nil
},
}

View File

@ -1,100 +0,0 @@
package main
import (
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var createRunFlags = []cli.Flag{
cli.StringFlag{
Name: "bundle, b",
Value: "",
Usage: `path to the root of the bundle directory, defaults to the current directory`,
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
cli.StringFlag{
Name: "shim-log",
Value: "",
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-shim-log) for the launched shim process`,
},
cli.StringFlag{
Name: "vm-log",
Value: "",
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-vm-log) for the launched VM shim process`,
},
cli.StringFlag{
Name: "vm-console",
Value: "",
Usage: `path to the pipe for the VM's console (e.g. \\.\pipe\debugpipe)`,
},
cli.StringFlag{
Name: "host",
Value: "",
Usage: "host container whose VM this container should run in",
},
}
var createCommand = cli.Command{
Name: "create",
Usage: "create a container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`,
Description: `The create command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "` + specConfig + `" and a root
filesystem.
The specification file includes an args parameter. The args parameter is used
to specify command(s) that get run when the container is started. To change the
command(s) that get executed on start, edit the args parameter of the spec. See
"runc spec --help" for more explanation.`,
Flags: append(createRunFlags),
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
cfg, err := containerConfigFromContext(context)
if err != nil {
return err
}
_, err = createContainer(cfg)
if err != nil {
return err
}
return nil
},
}
func containerConfigFromContext(context *cli.Context) (*containerConfig, error) {
id := context.Args().First()
pidFile, err := absPathOrEmpty(context.String("pid-file"))
if err != nil {
return nil, err
}
shimLog, err := absPathOrEmpty(context.String("shim-log"))
if err != nil {
return nil, err
}
vmLog, err := absPathOrEmpty(context.String("vm-log"))
if err != nil {
return nil, err
}
spec, err := setupSpec(context)
if err != nil {
return nil, err
}
return &containerConfig{
ID: id,
Owner: context.GlobalString("owner"),
PidFile: pidFile,
ShimLogFile: shimLog,
VMLogFile: vmLog,
VMConsolePipe: context.String("vm-console"),
Spec: spec,
HostID: context.String("host"),
}, nil
}

View File

@ -1,73 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/regstate"
"github.com/urfave/cli"
)
var deleteCommand = cli.Command{
Name: "delete",
Usage: "delete any resources held by the container often used with detached container",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container.
EXAMPLE:
For example, if the container id is "ubuntu01" and runhcs list currently shows the
status of "ubuntu01" as "stopped" the following will delete resources held for
"ubuntu01" removing "ubuntu01" from the runhcs list of containers:
# runhcs delete ubuntu01`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
force := context.Bool("force")
container, err := getContainer(id, false)
if err != nil {
if _, ok := err.(*regstate.NoStateError); ok {
if e := stateKey.Remove(id); e != nil {
fmt.Fprintf(os.Stderr, "remove %s: %v\n", id, e)
}
if force {
return nil
}
}
return err
}
defer container.Close()
s, err := container.Status()
if err != nil {
return err
}
kill := false
switch s {
case containerStopped:
case containerCreated:
kill = true
default:
if !force {
return fmt.Errorf("cannot delete container %s that is not stopped: %s\n", id, s)
}
kill = true
}
if kill {
err = container.Kill()
if err != nil {
return err
}
}
return container.Remove()
},
}

View File

@ -1,160 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
var execCommand = cli.Command{
Name: "exec",
Usage: "execute new process inside the container",
ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
Where "<container-id>" is the name for the instance of the container and
"<command>" is the command to be executed in the container.
"<command>" can't be empty unless a "-p" flag provided.
EXAMPLE:
For example, if the container is configured to run the linux ps command the
following will output a list of processes running in the container:
# runhcs exec <container-id> ps`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "cwd",
Usage: "current working directory in the container",
},
cli.StringSliceFlag{
Name: "env, e",
Usage: "set environment variables",
},
cli.BoolFlag{
Name: "tty, t",
Usage: "allocate a pseudo-TTY",
},
cli.StringFlag{
Name: "user, u",
},
cli.StringFlag{
Name: "process, p",
Usage: "path to the process.json",
},
cli.BoolFlag{
Name: "detach,d",
Usage: "detach from the container's process",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
cli.StringFlag{
Name: "shim-log",
Value: "",
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-<exec-id>-log) for the launched shim process`,
},
},
Before: appargs.Validate(argID, appargs.Rest(appargs.String)),
Action: func(context *cli.Context) error {
id := context.Args().First()
pidFile, err := absPathOrEmpty(context.String("pid-file"))
if err != nil {
return err
}
shimLog, err := absPathOrEmpty(context.String("shim-log"))
if err != nil {
return err
}
c, err := getContainer(id, false)
if err != nil {
return err
}
defer c.Close()
status, err := c.Status()
if err != nil {
return err
}
if status != containerRunning {
return errContainerStopped
}
spec, err := getProcessSpec(context, c)
if err != nil {
return err
}
p, err := startProcessShim(id, pidFile, shimLog, spec)
if err != nil {
return err
}
if !context.Bool("detach") {
state, err := p.Wait()
if err != nil {
return err
}
os.Exit(int(state.Sys().(syscall.WaitStatus).ExitCode))
}
return nil
},
SkipArgReorder: true,
}
func getProcessSpec(context *cli.Context, c *container) (*specs.Process, error) {
if path := context.String("process"); path != "" {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var p specs.Process
if err := json.NewDecoder(f).Decode(&p); err != nil {
return nil, err
}
return &p, validateProcessSpec(&p)
}
// process via cli flags
p := c.Spec.Process
if len(context.Args()) == 1 {
return nil, fmt.Errorf("process args cannot be empty")
}
p.Args = context.Args()[1:]
// override the cwd, if passed
if context.String("cwd") != "" {
p.Cwd = context.String("cwd")
}
// append the passed env variables
p.Env = append(p.Env, context.StringSlice("env")...)
// set the tty
if context.IsSet("tty") {
p.Terminal = context.Bool("tty")
}
// override the user, if passed
if context.String("user") != "" {
p.User.Username = context.String("user")
}
return p, nil
}
func validateProcessSpec(spec *specs.Process) error {
if spec.Cwd == "" {
return fmt.Errorf("Cwd property must not be empty")
}
// IsAbs doesnt recognize Unix paths on Windows builds so handle that case
// here.
if !filepath.IsAbs(spec.Cwd) && !strings.HasPrefix(spec.Cwd, "/") {
return fmt.Errorf("Cwd must be an absolute path")
}
if len(spec.Args) == 0 {
return fmt.Errorf("args must not be empty")
}
return nil
}

View File

@ -1,178 +0,0 @@
package main
import (
"strconv"
"strings"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/guestrequest"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/schema1"
"github.com/Microsoft/hcsshim/osversion"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var killCommand = cli.Command{
Name: "kill",
Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process",
ArgsUsage: `<container-id> [signal]
Where "<container-id>" is the name for the instance of the container and
"[signal]" is the signal to be sent to the init process.
EXAMPLE:
For example, if the container id is "ubuntu01" the following will send a "KILL"
signal to the init process of the "ubuntu01" container:
# runhcs kill ubuntu01 KILL`,
Flags: []cli.Flag{},
Before: appargs.Validate(argID, appargs.Optional(appargs.String)),
Action: func(context *cli.Context) error {
id := context.Args().First()
c, err := getContainer(id, true)
if err != nil {
return err
}
defer c.Close()
status, err := c.Status()
if err != nil {
return err
}
if status != containerRunning {
return errContainerStopped
}
signalsSupported := false
// The Signal feature was added in RS5
if osversion.Get().Build >= osversion.RS5 {
if c.IsHost || c.HostID != "" {
var hostID string
if c.IsHost {
// This is the LCOW, Pod Sandbox, or Windows Xenon V2 for RS5+
hostID = vmID(c.ID)
} else {
// This is the Nth container in a Pod
hostID = c.HostID
}
uvm, err := hcs.OpenComputeSystem(hostID)
if err != nil {
return err
}
defer uvm.Close()
if props, err := uvm.Properties(schema1.PropertyTypeGuestConnection); err == nil &&
props.GuestConnectionInfo.GuestDefinedCapabilities.SignalProcessSupported {
signalsSupported = true
}
} else if c.Spec.Linux == nil && c.Spec.Windows.HyperV == nil {
// RS5+ Windows Argon
signalsSupported = true
}
}
signal := 0
if signalsSupported {
signal, err = validateSigstr(context.Args().Get(1), signalsSupported, c.Spec.Linux != nil)
if err != nil {
return err
}
}
var pid int
if err := stateKey.Get(id, keyInitPid, &pid); err != nil {
return err
}
p, err := c.hc.OpenProcess(pid)
if err != nil {
return err
}
defer p.Close()
if signalsSupported && (c.Spec.Linux != nil || !c.Spec.Process.Terminal) {
opts := guestrequest.SignalProcessOptions{
Signal: signal,
}
return p.Signal(opts)
}
// Legacy signal issue a kill
return p.Kill()
},
}
func validateSigstr(sigstr string, signalsSupported bool, isLcow bool) (int, error) {
errInvalidSignal := errors.Errorf("invalid signal '%s'", sigstr)
// All flavors including legacy default to SIGTERM on LCOW CtrlC on Windows
if sigstr == "" {
if isLcow {
return 0xf, nil
}
return 0, nil
}
sigstr = strings.ToUpper(sigstr)
if !signalsSupported {
// If signals arent supported we just validate that its a known signal.
// We already return 0 since we only supported a platform Kill() at that
// time.
if isLcow {
switch sigstr {
case "15":
fallthrough
case "TERM":
fallthrough
case "SIGTERM":
return 0, nil
default:
return 0, errInvalidSignal
}
}
switch sigstr {
// Docker sends a UNIX term in the supported Windows Signal map.
case "15":
fallthrough
case "TERM":
fallthrough
case "0":
fallthrough
case "CTRLC":
return 0, nil
case "9":
fallthrough
case "KILL":
return 0, nil
default:
return 0, errInvalidSignal
}
}
var sigmap map[string]int
if isLcow {
sigmap = signalMapLcow
} else {
sigmap = signalMapWindows
}
signal, err := strconv.Atoi(sigstr)
if err != nil {
// Signal might still match the string value
for k, v := range sigmap {
if k == sigstr {
return v, nil
}
}
return 0, errInvalidSignal
}
// Match signal by value
for _, v := range sigmap {
if signal == v {
return signal, nil
}
}
return 0, errInvalidSignal
}

View File

@ -1,95 +0,0 @@
package main
import (
"fmt"
"strconv"
"strings"
"testing"
)
func runValidateSigstrTest(sigstr string, signalsSupported, isLcow bool,
expectedSignal int, expectedError bool, t *testing.T) {
signal, err := validateSigstr(sigstr, signalsSupported, isLcow)
if expectedError {
if err == nil {
t.Fatalf("Expected err: %v, got: nil", expectedError)
} else if err.Error() != fmt.Sprintf("invalid signal '%s'", sigstr) {
t.Fatalf("Expected err: %v, got: %v", expectedError, err)
}
}
if signal != expectedSignal {
t.Fatalf("Test - Signal: %s, Support: %v, LCOW: %v\nExpected signal: %v, got: %v",
sigstr, signalsSupported, isLcow,
expectedSignal, signal)
}
}
func TestValidateSigstrEmpty(t *testing.T) {
runValidateSigstrTest("", false, false, 0, false, t)
runValidateSigstrTest("", false, true, 0xf, false, t)
runValidateSigstrTest("", true, false, 0, false, t)
runValidateSigstrTest("", true, true, 0xf, false, t)
}
func TestValidateSigstrDefaultLCOW(t *testing.T) {
runValidateSigstrTest("15", false, true, 0, false, t)
runValidateSigstrTest("TERM", false, true, 0, false, t)
runValidateSigstrTest("SIGTERM", false, true, 0, false, t)
}
func TestValidateSigstrDefaultLCOWInvalid(t *testing.T) {
runValidateSigstrTest("2", false, true, 0, true, t)
runValidateSigstrTest("test", false, true, 0, true, t)
}
func TestValidateSigstrDefaultWCOW(t *testing.T) {
runValidateSigstrTest("15", false, false, 0, false, t)
runValidateSigstrTest("TERM", false, false, 0, false, t)
runValidateSigstrTest("0", false, false, 0, false, t)
runValidateSigstrTest("CTRLC", false, false, 0, false, t)
runValidateSigstrTest("9", false, false, 0, false, t)
runValidateSigstrTest("KILL", false, false, 0, false, t)
}
func TestValidateSigstrDefaultWCOWInvalid(t *testing.T) {
runValidateSigstrTest("2", false, false, 0, true, t)
runValidateSigstrTest("test", false, false, 0, true, t)
}
func TestValidateSignalStringLCOW(t *testing.T) {
for k, v := range signalMapLcow {
runValidateSigstrTest(k, true, true, v, false, t)
// run it again with a case not in the map
lc := strings.ToLower(k)
if k == lc {
t.Fatalf("Expected lower casing - map: %v, got: %v", k, lc)
}
runValidateSigstrTest(lc, true, true, v, false, t)
}
}
func TestValidateSignalStringWCOW(t *testing.T) {
for k, v := range signalMapWindows {
runValidateSigstrTest(k, true, false, v, false, t)
// run it again with a case not in the map
lc := strings.ToLower(k)
if k == lc {
t.Fatalf("Expected lower casing - map: %v, got: %v", k, lc)
}
runValidateSigstrTest(lc, true, false, v, false, t)
}
}
func TestValidateSignalValueLCOW(t *testing.T) {
for _, v := range signalMapLcow {
str := strconv.Itoa(v)
runValidateSigstrTest(str, true, true, v, false, t)
}
}
func TestValidateSignalValueWCOW(t *testing.T) {
for _, v := range signalMapWindows {
str := strconv.Itoa(v)
runValidateSigstrTest(str, true, false, v, false, t)
}
}

View File

@ -1,116 +0,0 @@
package main
import (
"fmt"
"os"
"text/tabwriter"
"time"
"encoding/json"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/urfave/cli"
)
const formatOptions = `table or json`
var listCommand = cli.Command{
Name: "list",
Usage: "lists containers started by runhcs with the given root",
ArgsUsage: `
Where the given root is specified via the global option "--root"
(default: "/run/runhcs").
EXAMPLE 1:
To list containers created via the default "--root":
# runhcs list
EXAMPLE 2:
To list containers created using a non-default value for "--root":
# runhcs --root value list`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "format, f",
Value: "table",
Usage: `select one of: ` + formatOptions,
},
cli.BoolFlag{
Name: "quiet, q",
Usage: "display only container IDs",
},
},
Before: appargs.Validate(),
Action: func(context *cli.Context) error {
s, err := getContainers(context)
if err != nil {
return err
}
if context.Bool("quiet") {
for _, item := range s {
fmt.Println(item.ID)
}
return nil
}
switch context.String("format") {
case "table":
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER\n")
for _, item := range s {
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\n",
item.ID,
item.InitProcessPid,
item.Status,
item.Bundle,
item.Created.Format(time.RFC3339Nano),
item.Owner)
}
if err := w.Flush(); err != nil {
return err
}
case "json":
if err := json.NewEncoder(os.Stdout).Encode(s); err != nil {
return err
}
default:
return fmt.Errorf("invalid format option")
}
return nil
},
}
func getContainers(context *cli.Context) ([]runhcs.ContainerState, error) {
ids, err := stateKey.Enumerate()
if err != nil {
return nil, err
}
var s []runhcs.ContainerState
for _, id := range ids {
c, err := getContainer(id, false)
if err != nil {
fmt.Fprintf(os.Stderr, "reading state for %s: %v\n", id, err)
continue
}
status, err := c.Status()
if err != nil {
fmt.Fprintf(os.Stderr, "reading status for %s: %v\n", id, err)
}
s = append(s, runhcs.ContainerState{
ID: id,
Version: c.Spec.Version,
InitProcessPid: c.ShimPid,
Status: string(status),
Bundle: c.Bundle,
Rootfs: c.Rootfs,
Created: c.Created,
Annotations: c.Spec.Annotations,
})
c.Close()
}
return s, nil
}

View File

@ -1,174 +0,0 @@
package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/pkg/etwlogrus"
"github.com/Microsoft/hcsshim/internal/regstate"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
// Add a manifest to get proper Windows version detection.
//
// goversioninfo can be installed with "go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo"
//go:generate goversioninfo -platform-specific
// version will be populated by the Makefile, read from
// VERSION file of the source code.
var version = ""
// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""
var stateKey *regstate.Key
var logFormat string
const (
specConfig = "config.json"
usage = `Open Container Initiative runtime for Windows
runhcs is a fork of runc, modified to run containers on Windows with or without Hyper-V isolation. Like runc, it is a command line client for running applications packaged according to the Open Container Initiative (OCI) format.
runhcs integrates with existing process supervisors to provide a production container runtime environment for applications. It can be used with your existing process monitoring tools and the container will be spawned as a direct child of the process supervisor.
Containers are configured using bundles. A bundle for a container is a directory that includes a specification file named "` + specConfig + `". Bundle contents will depend on the container type.
To start a new instance of a container:
# runhcs run [ -b bundle ] <container-id>
Where "<container-id>" is your name for the instance of the container that you are starting. The name you provide for the container instance must be unique on your host. Providing the bundle directory using "-b" is optional. The default value for "bundle" is the current directory.`
)
func main() {
// Provider ID: 0b52781f-b24d-5685-ddf6-69830ed40ec3
// Hook isn't closed explicitly, as it will exist until process exit.
if hook, err := etwlogrus.NewHook("Microsoft.Virtualization.RunHCS"); err == nil {
logrus.AddHook(hook)
} else {
logrus.Error(err)
}
app := cli.NewApp()
app.Name = "runhcs"
app.Usage = usage
var v []string
if version != "" {
v = append(v, version)
}
if gitCommit != "" {
v = append(v, fmt.Sprintf("commit: %s", gitCommit))
}
v = append(v, fmt.Sprintf("spec: %s", specs.Version))
app.Version = strings.Join(v, "\n")
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug",
Usage: "enable debug output for logging",
},
cli.StringFlag{
Name: "log",
Value: "nul",
Usage: `set the log file path or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-log) where internal debug information is written`,
},
cli.StringFlag{
Name: "log-format",
Value: "text",
Usage: "set the format used by logs ('text' (default), or 'json')",
},
cli.StringFlag{
Name: "owner",
Value: "runhcs",
Usage: "compute system owner",
},
cli.StringFlag{
Name: "root",
Value: "default",
Usage: "registry key for storage of container state",
},
}
app.Commands = []cli.Command{
createCommand,
createScratchCommand,
deleteCommand,
// eventsCommand,
execCommand,
killCommand,
listCommand,
pauseCommand,
psCommand,
resizeTtyCommand,
resumeCommand,
runCommand,
shimCommand,
startCommand,
stateCommand,
// updateCommand,
vmshimCommand,
}
app.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
}
if path := context.GlobalString("log"); path != "" {
var f io.Writer
var err error
if strings.HasPrefix(path, runhcs.SafePipePrefix) {
f, err = winio.DialPipe(path, nil)
} else {
f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666)
}
if err != nil {
return err
}
logrus.SetOutput(f)
}
switch logFormat = context.GlobalString("log-format"); logFormat {
case "text":
// retain logrus's default.
case "json":
logrus.SetFormatter(new(logrus.JSONFormatter))
default:
return fmt.Errorf("unknown log-format %q", logFormat)
}
var err error
stateKey, err = regstate.Open(context.GlobalString("root"), false)
if err != nil {
return err
}
return nil
}
// If the command returns an error, cli takes upon itself to print
// the error on cli.ErrWriter and exit.
// Use our own writer here to ensure the log gets sent to the right location.
fatalWriter.Writer = cli.ErrWriter
cli.ErrWriter = &fatalWriter
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(cli.ErrWriter, err)
os.Exit(1)
}
}
type logErrorWriter struct {
Writer io.Writer
}
var fatalWriter logErrorWriter
func (f *logErrorWriter) Write(p []byte) (n int, err error) {
logrus.Error(string(p))
return f.Writer.Write(p)
}

View File

@ -1,58 +0,0 @@
package main
import (
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var pauseCommand = cli.Command{
Name: "pause",
Usage: "pause suspends all processes inside the container",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container to be
paused. `,
Description: `The pause command suspends all processes in the instance of the container.
Use runhcs list to identify instances of containers and their current status.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, true)
if err != nil {
return err
}
defer container.Close()
if err := container.hc.Pause(); err != nil {
return err
}
return nil
},
}
var resumeCommand = cli.Command{
Name: "resume",
Usage: "resumes all processes that have been previously paused",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container to be
resumed.`,
Description: `The resume command resumes all processes in the instance of the container.
Use runhcs list to identify instances of containers and their current status.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, true)
if err != nil {
return err
}
defer container.Close()
if err := container.hc.Resume(); err != nil {
return err
}
return nil
},
}

View File

@ -1,51 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/schema1"
"github.com/urfave/cli"
)
var psCommand = cli.Command{
Name: "ps",
Usage: "ps displays the processes running inside a container",
ArgsUsage: `<container-id> [ps options]`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "format, f",
Value: "json",
Usage: `select one of: ` + formatOptions,
},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, true)
if err != nil {
return err
}
defer container.Close()
props, err := container.hc.Properties(schema1.PropertyTypeProcessList)
if err != nil {
return err
}
var pids []int
for _, p := range props.ProcessList {
pids = append(pids, int(p.ProcessId))
}
switch context.String("format") {
case "json":
return json.NewEncoder(os.Stdout).Encode(pids)
default:
return fmt.Errorf("invalid format option")
}
},
SkipArgReorder: true,
}

View File

@ -1,64 +0,0 @@
package main
import (
"os"
"syscall"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
// default action is to start a container
var runCommand = cli.Command{
Name: "run",
Usage: "create and run a container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`,
Description: `The run command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "` + specConfig + `" and a root
filesystem.
The specification file includes an args parameter. The args parameter is used
to specify command(s) that get run when the container is started. To change the
command(s) that get executed on start, edit the args parameter of the spec.`,
Flags: append(createRunFlags,
cli.BoolFlag{
Name: "detach, d",
Usage: "detach from the container's process",
},
),
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
cfg, err := containerConfigFromContext(context)
if err != nil {
return err
}
c, err := createContainer(cfg)
if err != nil {
return err
}
if err != nil {
return err
}
p, err := os.FindProcess(c.ShimPid)
if err != nil {
return err
}
err = c.Exec()
if err != nil {
return err
}
if !context.Bool("detach") {
state, err := p.Wait()
if err != nil {
return err
}
c.Remove()
os.Exit(int(state.Sys().(syscall.WaitStatus).ExitCode))
}
return nil
},
}

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<description>runhcs</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View File

@ -1,323 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"
"sync"
"time"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/lcow"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/Microsoft/hcsshim/internal/schema2"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/sys/windows"
)
func containerPipePath(id string) string {
return runhcs.SafePipePath("runhcs-shim-" + id)
}
func newFile(context *cli.Context, param string) *os.File {
fd := uintptr(context.Int(param))
if fd == 0 {
return nil
}
return os.NewFile(fd, "")
}
var shimCommand = cli.Command{
Name: "shim",
Usage: `launch the process and proxy stdio (do not call it outside of runhcs)`,
Hidden: true,
Flags: []cli.Flag{
&cli.IntFlag{Name: "stdin", Hidden: true},
&cli.IntFlag{Name: "stdout", Hidden: true},
&cli.IntFlag{Name: "stderr", Hidden: true},
&cli.BoolFlag{Name: "exec", Hidden: true},
cli.StringFlag{Name: "log-pipe", Hidden: true},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
logPipe := context.String("log-pipe")
if logPipe != "" {
lpc, err := winio.DialPipe(logPipe, nil)
if err != nil {
return err
}
defer lpc.Close()
logrus.SetOutput(lpc)
} else {
logrus.SetOutput(os.Stderr)
}
fatalWriter.Writer = os.Stdout
id := context.Args().First()
c, err := getContainer(id, true)
if err != nil {
return err
}
defer c.Close()
// Asynchronously wait for the container to exit.
containerExitCh := make(chan error)
go func() {
containerExitCh <- c.hc.Wait()
}()
// Get File objects for the open stdio files passed in as arguments.
stdin := newFile(context, "stdin")
stdout := newFile(context, "stdout")
stderr := newFile(context, "stderr")
exec := context.Bool("exec")
terminateOnFailure := false
errorOut := io.WriteCloser(os.Stdout)
var spec *specs.Process
if exec {
// Read the process spec from stdin.
specj, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
os.Stdin.Close()
spec = new(specs.Process)
err = json.Unmarshal(specj, spec)
if err != nil {
return err
}
} else {
// Stdin is not used.
os.Stdin.Close()
// Listen on the named pipe associated with this container.
l, err := winio.ListenPipe(c.ShimPipePath(), nil)
if err != nil {
return err
}
// Alert the parent process that initialization has completed
// successfully.
errorOut.Write(runhcs.ShimSuccess)
errorOut.Close()
fatalWriter.Writer = ioutil.Discard
// When this process exits, clear this process's pid in the registry.
defer func() {
stateKey.Set(id, keyShimPid, 0)
}()
defer func() {
if terminateOnFailure {
if err = c.hc.Terminate(); hcs.IsPending(err) {
<-containerExitCh
}
}
}()
terminateOnFailure = true
// Wait for a connection to the named pipe, exiting if the container
// exits before this happens.
var pipe net.Conn
pipeCh := make(chan error)
go func() {
var err error
pipe, err = l.Accept()
pipeCh <- err
}()
select {
case err = <-pipeCh:
if err != nil {
return err
}
case err = <-containerExitCh:
if err != nil {
return err
}
return cli.NewExitError("", 1)
}
// The next set of errors goes to the open pipe connection.
errorOut = pipe
fatalWriter.Writer = pipe
// The process spec comes from the original container spec.
spec = c.Spec.Process
}
// Create the process in the container.
var wpp *hcsschema.ProcessParameters // Windows Process Parameters
var lpp *lcow.ProcessParameters // Linux Process Parameters
var p *hcs.Process
if c.Spec.Linux == nil {
environment := make(map[string]string)
for _, v := range spec.Env {
s := strings.SplitN(v, "=", 2)
if len(s) == 2 && len(s[1]) > 0 {
environment[s[0]] = s[1]
}
}
wpp = &hcsschema.ProcessParameters{
WorkingDirectory: spec.Cwd,
EmulateConsole: spec.Terminal,
Environment: environment,
User: spec.User.Username,
}
for i, arg := range spec.Args {
e := windows.EscapeArg(arg)
if i == 0 {
wpp.CommandLine = e
} else {
wpp.CommandLine += " " + e
}
}
if spec.ConsoleSize != nil {
wpp.ConsoleSize = []int32{
int32(spec.ConsoleSize.Height),
int32(spec.ConsoleSize.Width),
}
}
wpp.CreateStdInPipe = stdin != nil
wpp.CreateStdOutPipe = stdout != nil
wpp.CreateStdErrPipe = stderr != nil
p, err = c.hc.CreateProcess(wpp)
} else {
lpp = &lcow.ProcessParameters{}
if exec {
lpp.OCIProcess = spec
}
lpp.CreateStdInPipe = stdin != nil
lpp.CreateStdOutPipe = stdout != nil
lpp.CreateStdErrPipe = stderr != nil
p, err = c.hc.CreateProcess(lpp)
}
if err != nil {
return err
}
cstdin, cstdout, cstderr, err := p.Stdio()
if err != nil {
return err
}
if !exec {
err = stateKey.Set(c.ID, keyInitPid, p.Pid())
if err != nil {
return err
}
}
// Store the Guest pid map
err = stateKey.Set(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid()), p.Pid())
if err != nil {
return err
}
defer func() {
// Remove the Guest pid map when this process is cleaned up
stateKey.Clear(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid()))
}()
terminateOnFailure = false
// Alert the connected process that the process was launched
// successfully.
errorOut.Write(runhcs.ShimSuccess)
errorOut.Close()
fatalWriter.Writer = ioutil.Discard
// Relay stdio.
var wg sync.WaitGroup
if cstdin != nil {
go func() {
io.Copy(cstdin, stdin)
cstdin.Close()
p.CloseStdin()
}()
}
if cstdout != nil {
wg.Add(1)
go func() {
io.Copy(stdout, cstdout)
stdout.Close()
cstdout.Close()
wg.Done()
}()
}
if cstderr != nil {
wg.Add(1)
go func() {
io.Copy(stderr, cstderr)
stderr.Close()
cstderr.Close()
wg.Done()
}()
}
err = p.Wait()
wg.Wait()
// Attempt to get the exit code from the process.
code := 1
if err == nil {
code, err = p.ExitCode()
if err != nil {
code = 1
}
}
if !exec {
// Shutdown the container, waiting 5 minutes before terminating is
// forcefully.
const shutdownTimeout = time.Minute * 5
waited := false
err = c.hc.Shutdown()
if hcs.IsPending(err) {
select {
case err = <-containerExitCh:
waited = true
case <-time.After(shutdownTimeout):
err = hcs.ErrTimeout
}
}
if hcs.IsAlreadyStopped(err) {
err = nil
}
if err != nil {
err = c.hc.Terminate()
if waited {
err = c.hc.Wait()
} else {
err = <-containerExitCh
}
}
}
return cli.NewExitError("", code)
},
}

View File

@ -1,48 +0,0 @@
package main
var signalMapLcow = map[string]int{
"ABRT": 0x6,
"ALRM": 0xe,
"BUS": 0x7,
"CHLD": 0x11,
"CLD": 0x11,
"CONT": 0x12,
"FPE": 0x8,
"HUP": 0x1,
"ILL": 0x4,
"INT": 0x2,
"IO": 0x1d,
"IOT": 0x6,
"KILL": 0x9,
"PIPE": 0xd,
"POLL": 0x1d,
"PROF": 0x1b,
"PWR": 0x1e,
"QUIT": 0x3,
"SEGV": 0xb,
"STKFLT": 0x10,
"STOP": 0x13,
"SYS": 0x1f,
"TERM": 0xf,
"TRAP": 0x5,
"TSTP": 0x14,
"TTIN": 0x15,
"TTOU": 0x16,
"URG": 0x17,
"USR1": 0xa,
"USR2": 0xc,
"VTALRM": 0x1a,
"WINCH": 0x1c,
"XCPU": 0x18,
"XFSZ": 0x19,
}
var signalMapWindows = map[string]int{
"CTRLC": 0x0,
"CTRLBREAK": 0x1,
"CTRLCLOSE": 0x2,
"CTRLLOGOFF": 0x5,
"CTRLSHUTDOWN": 0x6,
"TERM": 0x0, // Docker sends the UNIX signal. Convert to CTRLC
"KILL": 0x6, // Docker sends the UNIX signal. Convert to CTRLSHUTDOWN
}

View File

@ -1,42 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
// loadSpec loads the specification from the provided path.
func loadSpec(cPath string) (spec *specs.Spec, err error) {
cf, err := os.Open(cPath)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("JSON specification file %s not found", cPath)
}
return nil, err
}
defer cf.Close()
if err = json.NewDecoder(cf).Decode(&spec); err != nil {
return nil, err
}
return spec, nil
}
// setupSpec performs initial setup based on the cli.Context for the container
func setupSpec(context *cli.Context) (*specs.Spec, error) {
bundle := context.String("bundle")
if bundle != "" {
if err := os.Chdir(bundle); err != nil {
return nil, err
}
}
spec, err := loadSpec(specConfig)
if err != nil {
return nil, err
}
return spec, nil
}

View File

@ -1,43 +0,0 @@
package main
import (
"errors"
"fmt"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var startCommand = cli.Command{
Name: "start",
Usage: "executes the user defined process in a created container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`,
Description: `The start command executes the user defined process in a created container.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, false)
if err != nil {
return err
}
defer container.Close()
status, err := container.Status()
if err != nil {
return err
}
switch status {
case containerCreated:
return container.Exec()
case containerStopped:
return errors.New("cannot start a container that has stopped")
case containerRunning:
return errors.New("cannot start an already running container")
default:
return fmt.Errorf("cannot start a container in the '%s' state", status)
}
},
}

View File

@ -1,49 +0,0 @@
package main
import (
"encoding/json"
"os"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/urfave/cli"
)
var stateCommand = cli.Command{
Name: "state",
Usage: "output the state of a container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container.`,
Description: `The state command outputs current state information for the
instance of a container.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
c, err := getContainer(id, false)
if err != nil {
return err
}
defer c.Close()
status, err := c.Status()
if err != nil {
return err
}
cs := runhcs.ContainerState{
Version: c.Spec.Version,
ID: c.ID,
InitProcessPid: c.ShimPid,
Status: string(status),
Bundle: c.Bundle,
Rootfs: c.Rootfs,
Created: c.Created,
Annotations: c.Spec.Annotations,
}
data, err := json.MarshalIndent(cs, "", " ")
if err != nil {
return err
}
os.Stdout.Write(data)
return nil
},
}

View File

@ -1,56 +0,0 @@
package main
import (
"fmt"
"strconv"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var resizeTtyCommand = cli.Command{
Name: "resize-tty",
Usage: "resize-tty updates the terminal size for a container process",
ArgsUsage: `<container-id> <width> <height>`,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "pid, p",
Usage: "the process pid (defaults to init pid)",
},
},
Before: appargs.Validate(
argID,
appargs.Int(10, 1, 65535),
appargs.Int(10, 1, 65535),
),
Action: func(context *cli.Context) error {
id := context.Args()[0]
width, _ := strconv.ParseUint(context.Args()[1], 10, 16)
height, _ := strconv.ParseUint(context.Args()[2], 10, 16)
c, err := getContainer(id, true)
if err != nil {
return err
}
defer c.Close()
pid := context.Int("pid")
if pid == 0 {
if err := stateKey.Get(id, keyInitPid, &pid); err != nil {
return err
}
} else {
// If a pid was provided map it to its hcs pid.
if err := stateKey.Get(id, fmt.Sprintf(keyPidMapFmt, pid), &pid); err != nil {
return err
}
}
p, err := c.hc.OpenProcess(pid)
if err != nil {
return err
}
defer p.Close()
return p.ResizeConsole(uint16(width), uint16(height))
},
}

View File

@ -1,52 +0,0 @@
package main
import (
"fmt"
"net"
"os"
"path/filepath"
"strings"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/runhcs"
)
var argID = appargs.NonEmptyString
func absPathOrEmpty(path string) (string, error) {
if path == "" {
return "", nil
}
if strings.HasPrefix(path, runhcs.SafePipePrefix) {
if len(path) > len(runhcs.SafePipePrefix) {
return runhcs.SafePipePath(path[len(runhcs.SafePipePrefix):]), nil
}
}
return filepath.Abs(path)
}
// createPidFile creates a file with the processes pid inside it atomically
// it creates a temp file with the paths filename + '.' infront of it
// then renames the file
func createPidFile(path string, pid int) error {
var (
tmpDir = filepath.Dir(path)
tmpName = filepath.Join(tmpDir, fmt.Sprintf(".%s", filepath.Base(path)))
)
f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
if err != nil {
return err
}
_, err = fmt.Fprintf(f, "%d", pid)
f.Close()
if err != nil {
return err
}
return os.Rename(tmpName, path)
}
func closeWritePipe(pipe net.Conn) error {
return pipe.(interface {
CloseWrite() error
}).CloseWrite()
}

View File

@ -1,39 +0,0 @@
package main
import (
"os"
"testing"
"github.com/Microsoft/hcsshim/internal/runhcs"
)
func Test_AbsPathOrEmpty(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get test wd: %v", err)
}
tests := []string{
"",
runhcs.SafePipePrefix + "test",
runhcs.SafePipePrefix + "test with spaces",
"test",
"C:\\test..\\test",
}
expected := []string{
"",
runhcs.SafePipePrefix + "test",
runhcs.SafePipePrefix + "test%20with%20spaces",
wd + "\\test",
"C:\\test..\\test",
}
for i, test := range tests {
actual, err := absPathOrEmpty(test)
if err != nil {
t.Fatalf("absPathOrEmpty: error '%v'", err)
}
if actual != expected[i] {
t.Fatalf("absPathOrEmpty: actual '%s' != '%s'", actual, expected[i])
}
}
}

View File

@ -1,43 +0,0 @@
{
"FixedFileInfo": {
"FileVersion": {
"Major": 1,
"Minor": 0,
"Patch": 0,
"Build": 0
},
"ProductVersion": {
"Major": 1,
"Minor": 0,
"Patch": 0,
"Build": 0
},
"FileFlagsMask": "3f",
"FileFlags ": "00",
"FileOS": "040004",
"FileType": "01",
"FileSubType": "00"
},
"StringFileInfo": {
"Comments": "",
"CompanyName": "",
"FileDescription": "",
"FileVersion": "",
"InternalName": "",
"LegalCopyright": "",
"LegalTrademarks": "",
"OriginalFilename": "",
"PrivateBuild": "",
"ProductName": "",
"ProductVersion": "v1.0.0.0",
"SpecialBuild": ""
},
"VarFileInfo": {
"Translation": {
"LangID": "0409",
"CharsetID": "04B0"
}
},
"IconPath": "",
"ManifestPath": "runhcs.exe.manifest"
}

View File

@ -1,209 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"syscall"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
func vmID(id string) string {
return id + "@vm"
}
var vmshimCommand = cli.Command{
Name: "vmshim",
Usage: `launch a VM and containers inside it (do not call it outside of runhcs)`,
Hidden: true,
Flags: []cli.Flag{
cli.StringFlag{Name: "log-pipe", Hidden: true},
cli.StringFlag{Name: "os", Hidden: true},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
logPipe := context.String("log-pipe")
if logPipe != "" {
lpc, err := winio.DialPipe(logPipe, nil)
if err != nil {
return err
}
defer lpc.Close()
logrus.SetOutput(lpc)
} else {
logrus.SetOutput(os.Stderr)
}
fatalWriter.Writer = os.Stdout
pipePath := context.Args().First()
optsj, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
os.Stdin.Close()
var opts interface{}
isLCOW := context.String("os") == "linux"
if isLCOW {
opts = &uvm.OptionsLCOW{}
} else {
opts = &uvm.OptionsWCOW{}
}
err = json.Unmarshal(optsj, opts)
if err != nil {
return err
}
// Listen on the named pipe associated with this VM.
l, err := winio.ListenPipe(pipePath, &winio.PipeConfig{MessageMode: true})
if err != nil {
return err
}
var vm *uvm.UtilityVM
if isLCOW {
vm, err = uvm.CreateLCOW(opts.(*uvm.OptionsLCOW))
} else {
vm, err = uvm.CreateWCOW(opts.(*uvm.OptionsWCOW))
}
if err != nil {
return err
}
defer vm.Close()
if err = vm.Start(); err != nil {
return err
}
// Asynchronously wait for the VM to exit.
exitCh := make(chan error)
go func() {
exitCh <- vm.Wait()
}()
defer vm.Terminate()
// Alert the parent process that initialization has completed
// successfully.
os.Stdout.Write(runhcs.ShimSuccess)
os.Stdout.Close()
fatalWriter.Writer = ioutil.Discard
pipeCh := make(chan net.Conn)
go func() {
for {
conn, err := l.Accept()
if err != nil {
logrus.Error(err)
continue
}
pipeCh <- conn
}
}()
for {
select {
case <-exitCh:
return nil
case pipe := <-pipeCh:
err = processRequest(vm, pipe)
if err == nil {
_, err = pipe.Write(runhcs.ShimSuccess)
// Wait until the pipe is closed before closing the
// container so that it is properly handed off to the other
// process.
if err == nil {
err = closeWritePipe(pipe)
}
if err == nil {
ioutil.ReadAll(pipe)
}
} else {
logrus.WithError(err).
Error("failed creating container in VM")
fmt.Fprintf(pipe, "%v", err)
}
pipe.Close()
}
}
},
}
func processRequest(vm *uvm.UtilityVM, pipe net.Conn) error {
var req runhcs.VMRequest
err := json.NewDecoder(pipe).Decode(&req)
if err != nil {
return err
}
logrus.WithFields(logrus.Fields{
logfields.ContainerID: req.ID,
logfields.VMShimOperation: req.Op,
}).Debug("process request")
c, err := getContainer(req.ID, false)
if err != nil {
return err
}
defer func() {
if c != nil {
c.Close()
}
}()
switch req.Op {
case runhcs.OpCreateContainer:
err = createContainerInHost(c, vm)
if err != nil {
return err
}
c2 := c
c = nil
go func() {
c2.hc.Wait()
c2.Close()
}()
case runhcs.OpUnmountContainer, runhcs.OpUnmountContainerDiskOnly:
err = c.unmountInHost(vm, req.Op == runhcs.OpUnmountContainer)
if err != nil {
return err
}
case runhcs.OpSyncNamespace:
return errors.New("Not implemented")
default:
panic("unknown operation")
}
return nil
}
type noVMError struct {
ID string
}
func (err *noVMError) Error() string {
return "VM " + err.ID + " cannot be contacted"
}
func (c *container) issueVMRequest(op runhcs.VMRequestOp) error {
req := runhcs.VMRequest{
ID: c.ID,
Op: op,
}
if err := runhcs.IssueVMRequest(c.VMPipePath(), &req); err != nil {
if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ERROR_FILE_NOT_FOUND {
return &noVMError{c.HostID}
}
return err
}
return nil
}

View File

@ -1,64 +0,0 @@
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/Microsoft/hcsshim/ext4/tar2ext4"
)
var (
input = flag.String("i", "", "input file")
output = flag.String("o", "", "output file")
overlay = flag.Bool("overlay", false, "produce overlayfs-compatible layer image")
vhd = flag.Bool("vhd", false, "add a VHD footer to the end of the image")
inlineData = flag.Bool("inline", false, "write small file data into the inode; not compatible with DAX")
)
func main() {
flag.Parse()
if flag.NArg() != 0 || len(*output) == 0 {
flag.Usage()
os.Exit(1)
}
err := func() (err error) {
in := os.Stdin
if *input != "" {
in, err = os.Open(*input)
if err != nil {
return err
}
}
out, err := os.Create(*output)
if err != nil {
return err
}
var opts []tar2ext4.Option
if *overlay {
opts = append(opts, tar2ext4.ConvertWhiteout)
}
if *vhd {
opts = append(opts, tar2ext4.AppendVhdFooter)
}
if *inlineData {
opts = append(opts, tar2ext4.InlineData)
}
err = tar2ext4.Convert(in, out, opts...)
if err != nil {
return err
}
// Exhaust the tar stream.
io.Copy(ioutil.Discard, in)
return nil
}()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@ -1,36 +0,0 @@
package main
import (
"path/filepath"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var createCommand = cli.Command{
Name: "create",
Usage: "creates a new writable container layer",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "layer, l",
Usage: "paths to the read-only parent layers",
},
},
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) error {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
layers, err := normalizeLayers(context.StringSlice("layer"), true)
if err != nil {
return err
}
di := driverInfo
return hcsshim.CreateScratchLayer(di, path, layers[len(layers)-1], layers)
},
}

View File

@ -1,66 +0,0 @@
package main
import (
"compress/gzip"
"io"
"os"
"path/filepath"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/ociwclayer"
"github.com/urfave/cli"
)
var exportCommand = cli.Command{
Name: "export",
Usage: "exports a layer to a tar file",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "layer, l",
Usage: "paths to the read-only parent layers",
},
cli.StringFlag{
Name: "output, o",
Usage: "output layer tar (defaults to stdout)",
},
cli.BoolFlag{
Name: "gzip, z",
Usage: "compress output with gzip compression",
},
},
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) (err error) {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
layers, err := normalizeLayers(context.StringSlice("layer"), true)
if err != nil {
return err
}
err = winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege})
if err != nil {
return err
}
fp := context.String("output")
f := os.Stdout
if fp != "" {
f, err = os.Create(fp)
if err != nil {
return err
}
defer f.Close()
}
w := io.Writer(f)
if context.Bool("gzip") {
w = gzip.NewWriter(w)
}
return ociwclayer.ExportLayer(w, path, layers)
},
}

View File

@ -1,74 +0,0 @@
package main
import (
"bufio"
"compress/gzip"
"io"
"os"
"path/filepath"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/ociwclayer"
"github.com/urfave/cli"
)
var importCommand = cli.Command{
Name: "import",
Usage: "imports a layer from a tar file",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "layer, l",
Usage: "paths to the read-only parent layers",
},
cli.StringFlag{
Name: "input, i",
Usage: "input layer tar (defaults to stdin)",
},
},
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) (err error) {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
layers, err := normalizeLayers(context.StringSlice("layer"), false)
if err != nil {
return err
}
fp := context.String("input")
f := os.Stdin
if fp != "" {
f, err = os.Open(fp)
if err != nil {
return err
}
defer f.Close()
}
r, err := addDecompressor(f)
if err != nil {
return err
}
err = winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege})
if err != nil {
return err
}
_, err = ociwclayer.ImportLayer(r, path, layers)
return err
},
}
func addDecompressor(r io.Reader) (io.Reader, error) {
b := bufio.NewReader(r)
hdr, err := b.Peek(3)
if err != nil {
return nil, err
}
if hdr[0] == 0x1f && hdr[1] == 0x8b && hdr[2] == 8 {
return gzip.NewReader(b)
}
return b, nil
}

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