conlink 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/push.yml +24 -0
- package/Dockerfile +2 -2
- package/README.md +81 -10
- package/examples/test4-multiple/modes/web/compose.yaml +5 -0
- package/examples/test9-compose.yaml +32 -0
- package/link-mirred.sh +114 -0
- package/mdc +2 -1
- package/package.json +1 -1
- package/run-tests.sh +191 -0
- package/schema.yaml +7 -0
- package/scripts/copy.sh +5 -1
- package/src/conlink/core.cljs +184 -95
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Push (compose tests)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push: {}
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [ master ]
|
|
7
|
+
workflow_dispatch: {}
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
compose-tests:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@v3
|
|
15
|
+
|
|
16
|
+
- name: npm install
|
|
17
|
+
run: npm install
|
|
18
|
+
|
|
19
|
+
- name: compose build of conlink
|
|
20
|
+
run: docker compose -f examples/test1-compose.yaml build
|
|
21
|
+
|
|
22
|
+
- name: "./run-tests.sh"
|
|
23
|
+
timeout-minutes: 5
|
|
24
|
+
run: time ./run-tests.sh
|
package/Dockerfile
CHANGED
|
@@ -24,11 +24,11 @@ FROM node:16-slim as run
|
|
|
24
24
|
RUN apt-get -y update
|
|
25
25
|
# Runtime deps and utilities
|
|
26
26
|
RUN apt-get -y install libpcap-dev tcpdump iproute2 iputils-ping curl \
|
|
27
|
-
iptables \
|
|
27
|
+
iptables bridge-utils ethtool \
|
|
28
28
|
openvswitch-switch openvswitch-testcontroller
|
|
29
29
|
|
|
30
30
|
COPY --from=build /app/ /app/
|
|
31
|
-
ADD link-add.sh link-del.sh /app/
|
|
31
|
+
ADD link-add.sh link-del.sh link-mirred.sh /app/
|
|
32
32
|
ADD schema.yaml /app/build/
|
|
33
33
|
|
|
34
34
|
ENV PATH /app:$PATH
|
package/README.md
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
# conlink: Declarative Low-Level Networking for Containers
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
Create (layer 2 and layer 3) networking between containers using
|
|
4
5
|
a declarative configuration.
|
|
5
6
|
|
|
6
7
|
## Prerequisites
|
|
7
8
|
|
|
9
|
+
General:
|
|
10
|
+
* docker
|
|
8
11
|
* docker-compose version 1.25.4 or later.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
|
|
13
|
+
Other:
|
|
14
|
+
* For Open vSwtich (OVS) bridging, the `openvswitch` kernel module
|
|
15
|
+
must loaded on the host system (where docker engine is running).
|
|
16
|
+
* For patch connections (`bridge: patch`), the kernel must support
|
|
17
|
+
tc qdisc mirred filtering via the `act_mirred` kernel module.
|
|
18
|
+
* For podman usage (e.g. second part of `test3`), podman is required.
|
|
19
|
+
* For remote connections/links (e.g. `test5`), the `geneve` (and/or
|
|
20
|
+
`vxlan`) kernel module must be loaded on the host system (where
|
|
21
|
+
docker engine is running)
|
|
22
|
+
* For CloudFormation deployment (e.g. `test6`), the AWS CLI is
|
|
23
|
+
required.
|
|
12
24
|
|
|
13
25
|
## Usage Notes
|
|
14
26
|
|
|
@@ -34,6 +46,29 @@ will also be required for the conlink container. In particular, if the
|
|
|
34
46
|
container uses systemd, then it will likely use `SYS_NICE` and
|
|
35
47
|
`NET_BROADCAST` and conlink will likewise need those capabilities.
|
|
36
48
|
|
|
49
|
+
### Bridging: Open vSwtich/OVS, Linux bridge, and patch
|
|
50
|
+
|
|
51
|
+
Conlink connects container veth links together via a bridge or via a
|
|
52
|
+
direct patch. All veth type links must have a `bridge` property that
|
|
53
|
+
defines which links will be connected together (i.e. the same
|
|
54
|
+
broadcast domain). The default bridge mode is defined by the
|
|
55
|
+
`--default-bridge-mode` parameter and defaults to "auto". If a bridge
|
|
56
|
+
is set to mode "auto" then conlink will check if the kernel has the
|
|
57
|
+
`openvswitch` kernel module loaded and if so it will create an Open
|
|
58
|
+
vSwitch/OVS bridge/switch for that bridge, otherwise it will create a
|
|
59
|
+
regular Linux bridge (e.g. brctl). If any bridges are explicitly
|
|
60
|
+
defined with an "ovs" mode and the kernel does not have support then
|
|
61
|
+
conlink will stop/error on startup.
|
|
62
|
+
|
|
63
|
+
The "patch" mode will connect two links together using tc qdisc
|
|
64
|
+
ingress filters. This type connection is equivalent to a patch panel
|
|
65
|
+
("bump-in-the-wire") connection and all traffic will be passed between
|
|
66
|
+
the two links unchanged unlike Linux and OVS bridges which typically
|
|
67
|
+
block certain bridge control broadcast traffic). The primary downside
|
|
68
|
+
of "patch" connections is that they limited to two links whereas "ovs"
|
|
69
|
+
and "linux" bridge modes can support many links connected into the
|
|
70
|
+
same bridge (broadcast domain).
|
|
71
|
+
|
|
37
72
|
## Network Configuration Syntax
|
|
38
73
|
|
|
39
74
|
Network configuration can either be loaded directly from configuration
|
|
@@ -71,8 +106,8 @@ The following table describes the link properties:
|
|
|
71
106
|
| route | * | string | | ip route add args |
|
|
72
107
|
| nat | * | IP | | DNAT/SNAT to IP |
|
|
73
108
|
| netem | * | string | | tc qdisc NetEm options |
|
|
74
|
-
| mode | 5 |
|
|
75
|
-
| vlanid | vlan |
|
|
109
|
+
| mode | 5 | string | | virt intf mode |
|
|
110
|
+
| vlanid | vlan | number | | VLAN ID |
|
|
76
111
|
|
|
77
112
|
- 1 - veth, dummy, vlan, ipvlan, macvlan, ipvtap, macvtap
|
|
78
113
|
- 2 - defaults to outer compose service
|
|
@@ -100,6 +135,21 @@ than the MTU of the parent (outer-dev) device.
|
|
|
100
135
|
For the `netem` property, refer to the `netem` man page. The `OPTIONS`
|
|
101
136
|
grammar defines the valid strings for the `netem` property.
|
|
102
137
|
|
|
138
|
+
### Bridges
|
|
139
|
+
|
|
140
|
+
The bridge settings currently only support the "mode" setting. If
|
|
141
|
+
the mode is not specified in this section or the section is omitted
|
|
142
|
+
entirely, then bridges specified in the links configuration will
|
|
143
|
+
default to the value of the `--default-bridge-mode` parameter (which
|
|
144
|
+
itself defaults to "auto").
|
|
145
|
+
|
|
146
|
+
The following table describes the bridge properties:
|
|
147
|
+
|
|
148
|
+
| property | format | description |
|
|
149
|
+
|-----------|---------|--------------------------------|
|
|
150
|
+
| bridge | string | conlink bridge / domain name |
|
|
151
|
+
| mode | string | auto, ovs, or linux |
|
|
152
|
+
|
|
103
153
|
### Tunnels
|
|
104
154
|
|
|
105
155
|
Tunnels links/interfaces will be created and attached to the specified
|
|
@@ -186,11 +236,11 @@ From the second node ping an address in the internet service:
|
|
|
186
236
|
docker-compose -f examples/test2-compose.yaml exec --index 2 node ping 8.8.8.8
|
|
187
237
|
```
|
|
188
238
|
|
|
189
|
-
Scale the nodes from 2 to 5 and then ping
|
|
239
|
+
Scale the nodes from 2 to 5 and then ping the fifth node from the second:
|
|
190
240
|
|
|
191
241
|
```
|
|
192
242
|
docker-compose -f examples/test2-compose.yaml up -d --scale node=5
|
|
193
|
-
docker-compose -f examples/test2-compose.yaml exec --index
|
|
243
|
+
docker-compose -f examples/test2-compose.yaml exec --index 2 node ping 10.0.1.5
|
|
194
244
|
```
|
|
195
245
|
|
|
196
246
|
|
|
@@ -261,7 +311,7 @@ defined in the first compose file.
|
|
|
261
311
|
MODES_DIR=./examples/test4-multiple/modes ./mdc node1 up --build --force-recreate
|
|
262
312
|
```
|
|
263
313
|
|
|
264
|
-
Ping the router host from `
|
|
314
|
+
Ping the router host from `node1`:
|
|
265
315
|
|
|
266
316
|
```
|
|
267
317
|
docker-compose exec node1 ping 10.0.0.100
|
|
@@ -282,6 +332,14 @@ docker-compose exec --index 1 node2 ping 10.1.0.1
|
|
|
282
332
|
docker-compose exec --index 2 node2 ping 10.1.0.1
|
|
283
333
|
```
|
|
284
334
|
|
|
335
|
+
From `node1`, ping both `node2` replicas across the switches and `r0` router:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
docker-compose exec node1 ping 10.2.0.1
|
|
339
|
+
docker-compose exec node1 ping 10.2.0.2
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
|
|
285
343
|
Restart the compose instance and add another compose file that starts
|
|
286
344
|
conlink using an addition network file `web-network.yaml`. The network
|
|
287
345
|
file starts up a simple web server on the router.
|
|
@@ -395,8 +453,8 @@ Show the links in both node containers to see that the MAC addresses
|
|
|
395
453
|
are `00:0a:0b:0c:0d:0*` and the MTUs are set to `4111`.
|
|
396
454
|
|
|
397
455
|
```
|
|
398
|
-
docker-compose -f examples/test7-compose.yaml exec --index 1 ip link
|
|
399
|
-
docker-compose -f examples/test7-compose.yaml exec --index 2 ip link
|
|
456
|
+
docker-compose -f examples/test7-compose.yaml exec --index 1 node ip link
|
|
457
|
+
docker-compose -f examples/test7-compose.yaml exec --index 2 node ip link
|
|
400
458
|
```
|
|
401
459
|
|
|
402
460
|
Ping the second node from the first to show the the NetEm setting is
|
|
@@ -446,6 +504,19 @@ Note: to connect to the vlan node (NODE2_HOST_ADDRESS) you will need
|
|
|
446
504
|
to configure your physical switch/router with routing/connectivity to
|
|
447
505
|
VLAN 5 on the same physical link to your host.
|
|
448
506
|
|
|
507
|
+
### test9: bridge modes
|
|
508
|
+
|
|
509
|
+
This example demonstrates the supported bridge modes.
|
|
510
|
+
|
|
511
|
+
Start the test9 compose configuration using different bridge modes and
|
|
512
|
+
validate connectivity using ping:
|
|
513
|
+
|
|
514
|
+
```
|
|
515
|
+
export BRIDGE_MODE="linux" # "ovs", "patch", "auto"
|
|
516
|
+
docker-compose -f examples/test9-compose.yaml up --build --force-recreate
|
|
517
|
+
docker-compose -f examples/test9-compose.yaml exec node ping 10.0.1.2
|
|
518
|
+
```
|
|
519
|
+
|
|
449
520
|
## GraphViz network configuration rendering
|
|
450
521
|
|
|
451
522
|
You can use d3 and GraphViz to create a visual graph rendering of
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# This file demonstrates using different bridge modes and
|
|
2
|
+
# usage of the --default-brige-mode parameter.
|
|
3
|
+
|
|
4
|
+
version: "2.4"
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
network:
|
|
8
|
+
build: {context: ../}
|
|
9
|
+
image: conlink
|
|
10
|
+
pid: host
|
|
11
|
+
network_mode: none
|
|
12
|
+
cap_add: [SYS_ADMIN, NET_ADMIN, SYS_NICE, NET_BROADCAST, IPC_LOCK]
|
|
13
|
+
security_opt: [ 'apparmor:unconfined' ] # needed on Ubuntu 18.04
|
|
14
|
+
volumes:
|
|
15
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
16
|
+
- /var/lib/docker:/var/lib/docker
|
|
17
|
+
- ./:/test
|
|
18
|
+
environment:
|
|
19
|
+
- BRIDGE_MODE
|
|
20
|
+
command: /app/build/conlink.js --default-bridge-mode linux --compose-file /test/test9-compose.yaml
|
|
21
|
+
|
|
22
|
+
node:
|
|
23
|
+
image: alpine
|
|
24
|
+
network_mode: none
|
|
25
|
+
scale: 2
|
|
26
|
+
command: sleep Infinity
|
|
27
|
+
|
|
28
|
+
x-network:
|
|
29
|
+
links:
|
|
30
|
+
- {bridge: s1, service: node, ip: 10.0.1.1/24}
|
|
31
|
+
bridges:
|
|
32
|
+
- {bridge: s1, mode: "${BRIDGE_MODE:-auto}"}
|
package/link-mirred.sh
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2024, Equinix, Inc
|
|
4
|
+
# Licensed under MPL 2.0
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
usage () {
|
|
9
|
+
echo >&2 "${0} [OPTIONS] INTF0 INTF1"
|
|
10
|
+
echo >&2 ""
|
|
11
|
+
echo >&2 "Create traffic mirror/redirect between INTF0 and INTF1."
|
|
12
|
+
echo >&2 ""
|
|
13
|
+
echo >&2 "Positional arguments:"
|
|
14
|
+
echo >&2 " INTF0 is the first interface name"
|
|
15
|
+
echo >&2 " INTF1 is the second interface name"
|
|
16
|
+
echo >&2 ""
|
|
17
|
+
echo >&2 "INTF0 must exist, but if INTF1 is missing, then exit with 0."
|
|
18
|
+
echo >&2 "Each interface will be checked for correct ingress/mirred config"
|
|
19
|
+
echo >&2 "and configured if the configuration is missing."
|
|
20
|
+
echo >&2 "These two aspect make this script idempotent. It can be called"
|
|
21
|
+
echo >&2 "whenever either interface appears and when the second appears,"
|
|
22
|
+
echo >&2 "the mirror/redirect action will be setup fully/bidirectionally."
|
|
23
|
+
echo >&2 ""
|
|
24
|
+
echo >&2 "OPTIONS:"
|
|
25
|
+
echo >&2 " --verbose - Verbose output (set -x)"
|
|
26
|
+
exit 2
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
info() { echo "link-mirred [${LOG_ID}] ${*}"; }
|
|
30
|
+
warn() { >&2 echo "link-mirred [${LOG_ID}] ${*}"; }
|
|
31
|
+
die() { warn "ERROR: ${*}"; exit 1; }
|
|
32
|
+
|
|
33
|
+
# Idempotently add ingress qdisc to an interface
|
|
34
|
+
add_ingress() {
|
|
35
|
+
local IF=$1 res=
|
|
36
|
+
|
|
37
|
+
res=$(tc qdisc show dev ${IF} 2>&1)
|
|
38
|
+
case "${res}" in
|
|
39
|
+
*"qdisc ingress ffff:"*)
|
|
40
|
+
info "${IF0} already has ingress qdisc"
|
|
41
|
+
;;
|
|
42
|
+
""|*"qdisc noqueue"*)
|
|
43
|
+
info "Adding ingress qdisc to ${IF}"
|
|
44
|
+
tc qdisc add dev "${IF}" ingress \
|
|
45
|
+
|| die "Could not add ingress qdisc to ${IF}"
|
|
46
|
+
;;
|
|
47
|
+
*)
|
|
48
|
+
die "${IF} has invalid ingress qdisc or could not be queried"
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Idempotently add mirred filter redirect rule to an interface
|
|
54
|
+
add_mirred() {
|
|
55
|
+
local IF0=$1 IF1=$2 res=
|
|
56
|
+
|
|
57
|
+
res=$(tc filter show dev ${IF0} parent ffff: 2>&1)
|
|
58
|
+
case "${res}" in
|
|
59
|
+
*"action order 1: mirred (Egress Redirect to device ${IF1}"*)
|
|
60
|
+
info "${IF0} already has filter redirect action"
|
|
61
|
+
;;
|
|
62
|
+
"")
|
|
63
|
+
info "Adding filter redirect action from ${IF0} to ${IF1}"
|
|
64
|
+
tc filter add dev ${IF0} parent ffff: protocol all u32 match u8 0 0 action \
|
|
65
|
+
mirred egress redirect dev ${IF1} \
|
|
66
|
+
|| die "Could not add filter redirect action from ${IF0} to ${IF1}"
|
|
67
|
+
;;
|
|
68
|
+
*)
|
|
69
|
+
die "${IF0} has invalid filter redirect action or could not be queried"
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Parse arguments
|
|
75
|
+
VERBOSE=${VERBOSE:-}
|
|
76
|
+
positional=
|
|
77
|
+
while [ "${*}" ]; do
|
|
78
|
+
param=$1; OPTARG=$2
|
|
79
|
+
case ${param} in
|
|
80
|
+
--verbose) VERBOSE=1 ;;
|
|
81
|
+
-h|--help) usage ;;
|
|
82
|
+
*) positional="${positional} $1" ;;
|
|
83
|
+
esac
|
|
84
|
+
shift
|
|
85
|
+
done
|
|
86
|
+
set -- ${positional}
|
|
87
|
+
IF0=$1 IF1=$2
|
|
88
|
+
|
|
89
|
+
[ "${VERBOSE}" ] && set -x || true
|
|
90
|
+
|
|
91
|
+
# Sanity check arguments
|
|
92
|
+
[ "${IF0}" -a "${IF1}" ] || usage
|
|
93
|
+
|
|
94
|
+
LOG_ID="mirred ${IF0}:${IF1}"
|
|
95
|
+
|
|
96
|
+
# Sanity checks
|
|
97
|
+
if ! ip link show ${IF0} >/dev/null; then
|
|
98
|
+
die "${IF0} does not exist"
|
|
99
|
+
fi
|
|
100
|
+
if ! ip link show ${IF1} >/dev/null; then
|
|
101
|
+
info "${IF1} missing, exiting"
|
|
102
|
+
exit 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
### Do the work
|
|
106
|
+
|
|
107
|
+
info "Creating filter rediction action between ${IF0} and ${IF1}"
|
|
108
|
+
|
|
109
|
+
add_ingress ${IF0}
|
|
110
|
+
add_ingress ${IF1}
|
|
111
|
+
add_mirred ${IF0} ${IF1}
|
|
112
|
+
add_mirred ${IF1} ${IF0}
|
|
113
|
+
|
|
114
|
+
info "Created filter rediction action between ${IF0} and ${IF1}"
|
package/mdc
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
3
|
set -e
|
|
4
4
|
shopt -s dotglob # recursive copy of dot files too
|
|
@@ -17,6 +17,7 @@ LS=$(which ls)
|
|
|
17
17
|
RESOLVE_DEPS="${RESOLVE_DEPS-./node_modules/@lonocloud/resolve-deps/resolve-deps.py}"
|
|
18
18
|
DOCKER_COMPOSE="${DOCKER_COMPOSE:-docker-compose}"
|
|
19
19
|
|
|
20
|
+
[ -f "${RESOLVE_DEPS}" ] || die "Missing ${RESOLVE_DEPS}. Perhaps 'npm install'?"
|
|
20
21
|
MODE_SPEC="${1}"; shift
|
|
21
22
|
if [ "${RESOLVE_DEPS}" ]; then
|
|
22
23
|
MODES="$(${RESOLVE_DEPS} "${MODES_DIR}" ${MODE_SPEC})"
|
package/package.json
CHANGED
package/run-tests.sh
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
export VERBOSE=${VERBOSE:-}
|
|
4
|
+
export COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-conlink-test}
|
|
5
|
+
declare TEST_NUM=0
|
|
6
|
+
declare -A RESULTS
|
|
7
|
+
declare PASS=0
|
|
8
|
+
declare FAIL=0
|
|
9
|
+
|
|
10
|
+
die() { echo >&2 "${*}"; exit 1; }
|
|
11
|
+
vecho() { [ "${VERBOSE}" ] && echo "${*}" || true; }
|
|
12
|
+
dc() { ${DOCKER_COMPOSE} "${@}"; }
|
|
13
|
+
mdc() { ./mdc "${@}" || die "mdc invocation failed"; }
|
|
14
|
+
|
|
15
|
+
# Determine compose command
|
|
16
|
+
for dc in "docker compose" "docker-compose"; do
|
|
17
|
+
${dc} version 2>/dev/null >&2 && DOCKER_COMPOSE="${dc}" && break
|
|
18
|
+
done
|
|
19
|
+
[ "${DOCKER_COMPOSE}" ] || die "No compose command found"
|
|
20
|
+
echo >&2 "Using compose command '${DOCKER_COMPOSE}'"
|
|
21
|
+
|
|
22
|
+
dc_init() {
|
|
23
|
+
local cont="${1}" idx="${2}"
|
|
24
|
+
dc down --remove-orphans -t1
|
|
25
|
+
dc up -d --force-recreate "${@}"
|
|
26
|
+
while ! dc logs network | grep "All links connected"; do
|
|
27
|
+
vecho "waiting for conlink startup"
|
|
28
|
+
sleep 1
|
|
29
|
+
done
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
dc_wait() {
|
|
33
|
+
local tries="${1}" cont="${2}" try=1 svc= idx= result=
|
|
34
|
+
case "${cont}" in
|
|
35
|
+
*_[0-9]|*_[0-9][0-9]) svc="${cont%_*}" idx="${cont##*_}" ;;
|
|
36
|
+
*) svc="${cont}" idx=1 ;;
|
|
37
|
+
esac
|
|
38
|
+
shift; shift
|
|
39
|
+
|
|
40
|
+
#echo "target: ${1}, service: ${svc}, index: ${idx}"
|
|
41
|
+
while true; do
|
|
42
|
+
result=0
|
|
43
|
+
if [ "${VERBOSE}" ]; then
|
|
44
|
+
vecho "Running: dc exec -T --index ${idx} ${svc} sh -c ${*}"
|
|
45
|
+
dc exec -T --index ${idx} ${svc} sh -c "${*}" || result=$?
|
|
46
|
+
else
|
|
47
|
+
dc exec -T --index ${idx} ${svc} sh -c "${*}" > /dev/null || result=$?
|
|
48
|
+
fi
|
|
49
|
+
[ "${result}" -eq 0 -o "${try}" -ge "${tries}" ] && break
|
|
50
|
+
echo " command failed (${result}), sleeping 2s before retry (${try}/${tries})"
|
|
51
|
+
sleep 2
|
|
52
|
+
try=$(( try + 1 ))
|
|
53
|
+
done
|
|
54
|
+
return ${result}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
dc_test() {
|
|
58
|
+
name="${TEST_NUM} ${GROUP}: ${@}"
|
|
59
|
+
TEST_NUM=$(( TEST_NUM + 1 ))
|
|
60
|
+
vecho " > Running test: ${name}"
|
|
61
|
+
dc_wait 1 "${@}"
|
|
62
|
+
RESULTS["${name}"]=$?
|
|
63
|
+
if [ "${RESULTS["${name}"]}" = 0 ]; then
|
|
64
|
+
PASS=$(( PASS + 1 ))
|
|
65
|
+
vecho " > PASS (0 for ${*})"
|
|
66
|
+
else
|
|
67
|
+
FAIL=$(( FAIL + 1 ))
|
|
68
|
+
echo " > FAIL (${RESULTS[${name}]} for ${*})"
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
echo -e "\n\n>>> test1: combined config"
|
|
74
|
+
GROUP=test1
|
|
75
|
+
echo "COMPOSE_FILE=examples/test1-compose.yaml" > .env
|
|
76
|
+
dc_init || die "test1 startup failed"
|
|
77
|
+
|
|
78
|
+
echo " >> Ping nodes from other nodes"
|
|
79
|
+
dc_test h1 ping -c1 10.0.0.100
|
|
80
|
+
dc_test h2 ping -c1 192.168.1.100
|
|
81
|
+
dc_test h3 ping -c1 172.16.0.100
|
|
82
|
+
|
|
83
|
+
echo -e "\n\n>>> test2: separate config and scaling"
|
|
84
|
+
GROUP=test2
|
|
85
|
+
echo "COMPOSE_FILE=examples/test2-compose.yaml" > .env
|
|
86
|
+
dc_init || die "test2 startup failed"
|
|
87
|
+
|
|
88
|
+
echo " >> Cross-node ping and ping the 'internet'"
|
|
89
|
+
dc_test node_1 ping -c1 10.0.1.2
|
|
90
|
+
dc_test node_2 ping -c1 10.0.1.1
|
|
91
|
+
dc_test node_1 ping -c1 8.8.8.8
|
|
92
|
+
dc_test node_2 ping -c1 8.8.8.8
|
|
93
|
+
|
|
94
|
+
echo " >> Scale the nodes from 2 to 5"
|
|
95
|
+
dc up -d --scale node=5
|
|
96
|
+
dc_wait 10 node_5 'ip addr | grep "10\.0\.1\.5"' || die "test2 scale-up failed"
|
|
97
|
+
echo " >> Ping the fifth node from the second"
|
|
98
|
+
dc_test node_2 ping -c1 10.0.1.5
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
echo -e "\n\n>>> test4: multiple compose / mdc"
|
|
102
|
+
GROUP=test4
|
|
103
|
+
export MODES_DIR=./examples/test4-multiple/modes
|
|
104
|
+
|
|
105
|
+
mdc node1
|
|
106
|
+
dc_init; dc_wait 10 r0_1 'ip addr | grep "10\.1\.0\.100"' \
|
|
107
|
+
|| die "test4 node1 startup failed"
|
|
108
|
+
echo " >> Ping the r0 router host from node1"
|
|
109
|
+
dc_test node1_1 ping -c1 10.0.0.100
|
|
110
|
+
|
|
111
|
+
mdc node1,nodes2
|
|
112
|
+
dc_init; dc_wait 10 node2_2 'ip addr | grep "10\.2\.0\.2"' \
|
|
113
|
+
|| die "test4 node1,nodes2 startup failed"
|
|
114
|
+
echo " >> From both node2 replicas, ping node1 across the r0 router"
|
|
115
|
+
dc_test node2_1 ping -c1 10.1.0.1
|
|
116
|
+
dc_test node2_2 ping -c1 10.1.0.1
|
|
117
|
+
echo " >> From node1, ping both node2 replicas across the r0 router"
|
|
118
|
+
dc_test node1 ping -c1 10.2.0.1
|
|
119
|
+
dc_test node1 ping -c1 10.2.0.2
|
|
120
|
+
|
|
121
|
+
mdc all
|
|
122
|
+
dc_init; dc exec -T r0 /scripts/wait.sh -t 10.0.0.100:80 \
|
|
123
|
+
|| die "test4 all startup failed"
|
|
124
|
+
echo " >> From node2, download from the web server in r0"
|
|
125
|
+
dc_test node2_1 wget -O- 10.0.0.100
|
|
126
|
+
dc_test node2_2 wget -O- 10.0.0.100
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
echo -e "\n\n>>> test7: MAC, MTU, and NetEm settings"
|
|
130
|
+
GROUP=test7
|
|
131
|
+
echo "COMPOSE_FILE=examples/test7-compose.yaml" > .env
|
|
132
|
+
|
|
133
|
+
dc_init; dc_wait 10 node_1 'ip addr | grep "10\.0\.1\.1"' \
|
|
134
|
+
|| die "test7 startup failed"
|
|
135
|
+
echo " >> Ensure MAC and MTU are set correctly"
|
|
136
|
+
dc_test node_1 'ip link show eth0 | grep "ether 00:0a:0b:0c:0d:01"'
|
|
137
|
+
dc_test node_2 'ip link show eth0 | grep "ether 00:0a:0b:0c:0d:02"'
|
|
138
|
+
dc_test node_1 'ip link show eth0 | grep "mtu 4111"'
|
|
139
|
+
dc_test node_2 'ip link show eth0 | grep "mtu 4111"'
|
|
140
|
+
echo " >> Check for round-trip ping delay of 80ms"
|
|
141
|
+
dc_test node_1 'ping -c2 10.0.1.2 | tail -n1 | grep "max = 80\."'
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
echo -e "\n\n>>> test9: bridge modes and variable templating"
|
|
145
|
+
echo "COMPOSE_FILE=examples/test9-compose.yaml" > .env
|
|
146
|
+
|
|
147
|
+
echo -e "\n\n >> test9: bridge mode: auto"
|
|
148
|
+
GROUP=test9-auto
|
|
149
|
+
export BRIDGE_MODE=auto
|
|
150
|
+
dc_init; dc_wait 10 node_1 'ip addr | grep "10\.0\.1\.1"' \
|
|
151
|
+
|| die "test9 (auto) startup failed"
|
|
152
|
+
echo " >> Check for round-trip ping connectivity (BRIDGE_MODE=auto)"
|
|
153
|
+
dc_test node_1 'ping -c2 10.0.1.2'
|
|
154
|
+
|
|
155
|
+
echo -e "\n\n >> test9: bridge mode: linux"
|
|
156
|
+
GROUP=test9-linux
|
|
157
|
+
export BRIDGE_MODE=linux
|
|
158
|
+
dc_init; dc_wait 10 node_1 'ip addr | grep "10\.0\.1\.1"' \
|
|
159
|
+
|| die "test9 (linux) startup failed"
|
|
160
|
+
echo " >> Check for round-trip ping connectivity (BRIDGE_MODE=linux)"
|
|
161
|
+
dc_test node_1 'ping -c2 10.0.1.2'
|
|
162
|
+
|
|
163
|
+
echo -e "\n\n >> test9: bridge mode: patch"
|
|
164
|
+
GROUP=test9-patch
|
|
165
|
+
export BRIDGE_MODE=patch
|
|
166
|
+
dc_init; dc_wait 10 node_1 'ip addr | grep "10\.0\.1\.1"' \
|
|
167
|
+
|| die "test9 startup failed"
|
|
168
|
+
echo " >> Ensure ingest filter rules exist (BRIDGE_MODE=patch)"
|
|
169
|
+
dc_test network 'tc filter show dev node_1-eth0 parent ffff: | grep "action order 1: mirred"'
|
|
170
|
+
echo " >> Check for round-trip ping connectivity (BRIDGE_MODE=patch)"
|
|
171
|
+
dc_test node_1 'ping -c2 10.0.1.2'
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
echo -e "\n\n>>> Cleaning up"
|
|
175
|
+
dc down -t1 --remove-orphans
|
|
176
|
+
rm -f .env
|
|
177
|
+
|
|
178
|
+
if [ "${VERBOSE}" ]; then
|
|
179
|
+
for t in "${!RESULTS[@]}"; do
|
|
180
|
+
echo "RESULT: '${t}' -> ${RESULTS[${t}]}"
|
|
181
|
+
done
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
if [ "${FAIL}" = 0 ]; then
|
|
185
|
+
echo -e "\n\n>>> ALL ${PASS} TESTS PASSED"
|
|
186
|
+
exit 0
|
|
187
|
+
else
|
|
188
|
+
echo -e "\n\n>>> ${FAIL} TESTS FAILED, ${PASS} TESTS PASSED"
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
|
package/schema.yaml
CHANGED
package/scripts/copy.sh
CHANGED
|
@@ -26,9 +26,11 @@ dst_dir="${1}"; shift || die 2 "Usage: ${0} [-T|--template] SRC_DIR DST_DIR"
|
|
|
26
26
|
cp -a "${src}" "${dst}" || die 1 "Failed to copy file"
|
|
27
27
|
# TODO: make this configurable
|
|
28
28
|
chown root.root "${dst}" || die 1 "Unable to set ownership"
|
|
29
|
+
chmod +w "${dst}" || die 1 "Unable to make writable"
|
|
29
30
|
|
|
30
31
|
[ -z "${TEMPLATE}" ] && continue
|
|
31
32
|
|
|
33
|
+
tmpfile="$(mktemp)"
|
|
32
34
|
# match all {{FOO}} style variables and replace from environment
|
|
33
35
|
for v in $(cat "${dst}" | grep -o '{{[^ }{]*}}' | sed 's/[}{]//g' | sort -u); do
|
|
34
36
|
if set | grep -qs "^${v}="; then
|
|
@@ -36,9 +38,11 @@ dst_dir="${1}"; shift || die 2 "Usage: ${0} [-T|--template] SRC_DIR DST_DIR"
|
|
|
36
38
|
| sed "s/^['\"]\(.*\)['\"]$/\1/" \
|
|
37
39
|
| sed 's/[\/&]/\\&/g')
|
|
38
40
|
echo "Replacing '{{${v}}}' with '${val}' in '${dst}'"
|
|
39
|
-
sed
|
|
41
|
+
sed "s/{{${v}}}/${val}/g" "${dst}" > "${tmpfile}"
|
|
42
|
+
cp "${tmpfile}" "${dst}"
|
|
40
43
|
fi
|
|
41
44
|
done
|
|
45
|
+
rm -f "${tmpfile}"
|
|
42
46
|
done
|
|
43
47
|
|
|
44
48
|
if [ "${*}" ]; then
|
package/src/conlink/core.cljs
CHANGED
|
@@ -27,9 +27,9 @@ General Options:
|
|
|
27
27
|
-v, --verbose Show verbose output (stderr)
|
|
28
28
|
[env: VERBOSE]
|
|
29
29
|
--show-config Print loaded network config JSON and exit
|
|
30
|
-
--bridge-mode BRIDGE-MODE
|
|
31
|
-
bridge/switch connections
|
|
32
|
-
[default:
|
|
30
|
+
--default-bridge-mode BRIDGE-MODE Default bridge mode (ovs, linux, patch, or auto)
|
|
31
|
+
to use for bridge/switch connections
|
|
32
|
+
[default: auto] [env: CONLINK_BRIDGE_MODE]
|
|
33
33
|
--network-file NETWORK-FILE... Network config file
|
|
34
34
|
--compose-file COMPOSE-FILE... Docker compose file with network config
|
|
35
35
|
--compose-project NAME Docker compose project name for resolving
|
|
@@ -58,10 +58,12 @@ General Options:
|
|
|
58
58
|
(def LINK-ADD-OPTS [:ip :mac :route :mtu :nat :netem :mode :vlanid :remote :vni])
|
|
59
59
|
(def INTF-MAX-LEN 15)
|
|
60
60
|
|
|
61
|
-
(def ctx (atom {:error
|
|
62
|
-
:warn
|
|
63
|
-
:log
|
|
64
|
-
:info
|
|
61
|
+
(def ctx (atom {:error #(apply Eprintln "ERROR:" %&)
|
|
62
|
+
:warn #(apply Eprintln "WARNING:" %&)
|
|
63
|
+
:log Eprintln
|
|
64
|
+
:info #(identity nil)
|
|
65
|
+
:kmod-ovs? false
|
|
66
|
+
:kmod-mirred? false}))
|
|
65
67
|
|
|
66
68
|
;; Simple utility functions
|
|
67
69
|
(defn json-str [obj]
|
|
@@ -94,12 +96,13 @@ General Options:
|
|
|
94
96
|
net-cfg))
|
|
95
97
|
|
|
96
98
|
(defn enrich-link
|
|
97
|
-
"
|
|
99
|
+
"Resolve bridge name to full bridge map.
|
|
100
|
+
Add default values to a link:
|
|
98
101
|
- type: veth
|
|
99
102
|
- dev: eth0
|
|
100
103
|
- mtu: 9000 (for non *vlan type)
|
|
101
104
|
- base: :conlink for veth type, :host for *vlan types, :local otherwise"
|
|
102
|
-
[{:as link :keys [type base bridge ip vlanid]}]
|
|
105
|
+
[{:as link :keys [type base bridge ip vlanid]} bridges]
|
|
103
106
|
(let [type (keyword (or type "veth"))
|
|
104
107
|
base-default (cond (= :veth type) :conlink
|
|
105
108
|
(VLAN-TYPES type) :host
|
|
@@ -110,18 +113,57 @@ General Options:
|
|
|
110
113
|
{:type type
|
|
111
114
|
:dev (get link :dev "eth0")
|
|
112
115
|
:base base}
|
|
116
|
+
(when bridge
|
|
117
|
+
{:bridge (get bridges bridge)})
|
|
113
118
|
(when (not (VLAN-TYPES type))
|
|
114
119
|
{:mtu (get link :mtu 9000)}))]
|
|
115
120
|
link))
|
|
116
121
|
|
|
122
|
+
(defn enrich-bridge
|
|
123
|
+
"If bridge mode is :auto then return :ovs if the 'openvswitch' kernel module
|
|
124
|
+
is loaded otherwise fall back to :linux. Exit with an error if mode is :ovs
|
|
125
|
+
or :patch and the 'openvswitch' or 'act_mirred' kernel modules are not
|
|
126
|
+
loaded respectively."
|
|
127
|
+
[{:as bridge-opts :keys [bridge mode]}]
|
|
128
|
+
(let [{:keys [warn default-bridge-mode kmod-ovs? kmod-mirred?]} @ctx
|
|
129
|
+
mode (keyword (or mode default-bridge-mode))
|
|
130
|
+
_ (when (and (= :ovs mode) (not kmod-ovs?))
|
|
131
|
+
(fatal 1 (str "bridge " bridge " mode is 'ovs', "
|
|
132
|
+
"but no 'openvswitch' kernel module loaded")))
|
|
133
|
+
_ (when (and (= :patch mode) (not kmod-mirred?))
|
|
134
|
+
(warn (str "bridge " bridge " mode is 'patch', "
|
|
135
|
+
"but no 'act_mirred' kernel module loaded, "
|
|
136
|
+
" assuming it will load when needed.")))
|
|
137
|
+
_ (when (and (= :auto mode) (not kmod-ovs?))
|
|
138
|
+
(warn (str "bridge " bridge " mode is 'auto', "
|
|
139
|
+
" but no 'openvswitch' kernel module loaded, "
|
|
140
|
+
" so falling back to 'linux'")))
|
|
141
|
+
mode (if (= :auto mode)
|
|
142
|
+
(if kmod-ovs? :ovs :linux)
|
|
143
|
+
mode)]
|
|
144
|
+
(assoc bridge-opts :mode mode)))
|
|
145
|
+
|
|
117
146
|
(defn enrich-network-config
|
|
118
|
-
"Validate and update each link (enrich-link) and
|
|
119
|
-
:containers and :services maps with restructured link
|
|
120
|
-
configuration to provide a more efficient structure for looking
|
|
121
|
-
configuration later."
|
|
122
|
-
[{:as cfg :keys [links commands]}]
|
|
123
|
-
(let [
|
|
124
|
-
|
|
147
|
+
"Validate and update each bridge (enrich-bridge) and link (enrich-link) and
|
|
148
|
+
add :bridges, :containers, and :services maps with restructured bridge, link,
|
|
149
|
+
and command configuration to provide a more efficient structure for looking
|
|
150
|
+
up configuration later."
|
|
151
|
+
[{:as cfg :keys [links commands bridges]}]
|
|
152
|
+
(let [bridge-map (reduce (fn [acc b] (assoc acc (:bridge b) b))
|
|
153
|
+
{} bridges)
|
|
154
|
+
;; Add bridges specified in links only
|
|
155
|
+
all-bridges (reduce (fn [bs b]
|
|
156
|
+
(assoc bs b (get bs b {:bridge b})))
|
|
157
|
+
bridge-map
|
|
158
|
+
(keep :bridge links))
|
|
159
|
+
;; Enrich each bridge
|
|
160
|
+
bridges (reduce (fn [bs [k v]] (assoc bs k (enrich-bridge v)))
|
|
161
|
+
{} all-bridges)
|
|
162
|
+
links (mapv #(enrich-link % bridges) links)
|
|
163
|
+
cfg (merge cfg {:links links
|
|
164
|
+
:bridges bridges
|
|
165
|
+
:containers {}
|
|
166
|
+
:services {}})
|
|
125
167
|
rfn (fn [kind cfg {:as x :keys [container service]}]
|
|
126
168
|
(cond-> cfg
|
|
127
169
|
container (update-in [:containers container kind] conjv x)
|
|
@@ -153,15 +195,17 @@ General Options:
|
|
|
153
195
|
"\nUser config:\n" (indent-pprint-str data " "))
|
|
154
196
|
"\nValidation errors:\n" msg))))))
|
|
155
197
|
|
|
198
|
+
|
|
199
|
+
;;; Runtime state related
|
|
200
|
+
|
|
156
201
|
(defn gen-network-state
|
|
157
202
|
"Generate network state/context from network configuration. Adds
|
|
158
203
|
empty :devices map and :bridges map containing nil status for
|
|
159
204
|
each bridge mentioned in the network config :links and :tunnels."
|
|
160
|
-
[{:keys [links tunnels]}]
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
(keep :bridge (concat links tunnels))))
|
|
205
|
+
[{:keys [links tunnels bridges]}]
|
|
206
|
+
{:devices {}
|
|
207
|
+
:bridges (into {} (for [[k v] bridges]
|
|
208
|
+
[k (merge v {:status nil :links #{}})]))})
|
|
165
209
|
|
|
166
210
|
(defn link-outer-dev
|
|
167
211
|
"outer-dev format:
|
|
@@ -298,81 +342,127 @@ General Options:
|
|
|
298
342
|
res (run cmd {:quiet true})]
|
|
299
343
|
(and (= 0 (:code res)) (= kmod (trim (:stdout res))))))
|
|
300
344
|
|
|
301
|
-
;;;
|
|
345
|
+
;;; Bridge commands
|
|
302
346
|
|
|
303
347
|
(defn check-no-bridge
|
|
304
348
|
"Check that no bridge named 'bridge' is currently configured.
|
|
305
|
-
Bridge type is dependent on
|
|
349
|
+
Bridge type is dependent on mode (:ovs or :linux). Exit with
|
|
306
350
|
error if the bridge already exists."
|
|
307
|
-
[bridge]
|
|
308
|
-
(P/let [{:keys [info
|
|
351
|
+
[{:keys [bridge mode]}]
|
|
352
|
+
(P/let [{:keys [info]} @ctx
|
|
309
353
|
cmd (get {:ovs (str "ovs-vsctl list-ifaces " bridge)
|
|
310
|
-
:linux (str "ip link show type bridge " bridge)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
(if (
|
|
314
|
-
|
|
315
|
-
(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
354
|
+
:linux (str "ip link show type bridge " bridge)
|
|
355
|
+
:patch nil}
|
|
356
|
+
mode)]
|
|
357
|
+
(if (not cmd)
|
|
358
|
+
true
|
|
359
|
+
(P/let [res (run cmd {:quiet true})]
|
|
360
|
+
(if (= 0 (:code res))
|
|
361
|
+
;; TODO: maybe mark as :exists and use without cleanup
|
|
362
|
+
(fatal 1 (str "Bridge " bridge " already exists"))
|
|
363
|
+
(if (re-seq #"(does not exist|no bridge named)" (:stderr res))
|
|
364
|
+
true
|
|
365
|
+
(fatal 1 (str "Unable to run '" cmd "': " (:stderr res)))))))))
|
|
319
366
|
|
|
320
367
|
|
|
321
368
|
(defn bridge-create
|
|
322
369
|
"Create a bridge named 'bridge'.
|
|
323
|
-
Bridge type is dependent on
|
|
324
|
-
[bridge]
|
|
325
|
-
(P/let [{:keys [info error
|
|
326
|
-
_ (info "Creating bridge/switch" bridge)
|
|
370
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
371
|
+
[{:keys [bridge mode]}]
|
|
372
|
+
(P/let [{:keys [info error]} @ctx
|
|
327
373
|
cmd (get {:ovs (str "ovs-vsctl add-br " bridge)
|
|
328
|
-
:linux (str "ip link add " bridge " up type bridge")
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
(if (not
|
|
332
|
-
(
|
|
333
|
-
(
|
|
334
|
-
|
|
374
|
+
:linux (str "ip link add " bridge " up type bridge")
|
|
375
|
+
:patch nil}
|
|
376
|
+
mode)]
|
|
377
|
+
(if (not cmd)
|
|
378
|
+
(info (str "Ignoring bridge/switch " bridge " for mode " mode))
|
|
379
|
+
(P/let [_ (info "Creating bridge/switch" bridge)
|
|
380
|
+
res (run cmd)]
|
|
381
|
+
(if (not= 0 (:code res))
|
|
382
|
+
(error (str "Unable to create bridge/switch " bridge))
|
|
383
|
+
(swap! ctx assoc-in [:network-state :bridges bridge :status] :created))
|
|
384
|
+
true))))
|
|
335
385
|
|
|
336
386
|
(defn bridge-del
|
|
337
387
|
"Delete the bridge named 'bridge'.
|
|
338
|
-
Bridge type is dependent on
|
|
339
|
-
[bridge]
|
|
340
|
-
(P/let [{:keys [info error
|
|
341
|
-
_ (info "Deleting bridge/switch" bridge)
|
|
388
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
389
|
+
[{:keys [bridge mode]}]
|
|
390
|
+
(P/let [{:keys [info error]} @ctx
|
|
342
391
|
cmd (get {:ovs (str "ovs-vsctl del-br " bridge)
|
|
343
|
-
:linux (str "ip link del " bridge)
|
|
344
|
-
|
|
345
|
-
(if (not
|
|
346
|
-
(
|
|
347
|
-
(
|
|
348
|
-
|
|
392
|
+
:linux (str "ip link del " bridge)
|
|
393
|
+
:patch nil} mode)]
|
|
394
|
+
(if (not cmd)
|
|
395
|
+
(info (str "Ignoring bridge/switch " bridge " for mode " mode))
|
|
396
|
+
(P/let [_ (info "Deleting bridge/switch" bridge)
|
|
397
|
+
res (run cmd)]
|
|
398
|
+
(if (not= 0 (:code res))
|
|
399
|
+
(error (str "Unable to delete bridge " bridge))
|
|
400
|
+
(swap! ctx assoc-in [:network-state :bridges bridge :status] nil))
|
|
401
|
+
true))))
|
|
349
402
|
|
|
350
403
|
(defn bridge-add-link
|
|
351
404
|
"Add the link/interface 'dev' to the bridge 'bridge'.
|
|
352
|
-
Bridge type is dependent on
|
|
353
|
-
[bridge dev]
|
|
354
|
-
(P/let [{:keys [error
|
|
405
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
406
|
+
[{:keys [bridge mode]} dev]
|
|
407
|
+
(P/let [{:keys [error]} @ctx
|
|
355
408
|
cmd (get {:ovs (str "ovs-vsctl add-port " bridge " " dev)
|
|
356
409
|
:linux (str "ip link set dev " dev " master " bridge)}
|
|
357
|
-
|
|
410
|
+
mode)
|
|
358
411
|
res (run cmd)]
|
|
359
|
-
(
|
|
360
|
-
(
|
|
361
|
-
|
|
412
|
+
(if (= 0 (:code res))
|
|
413
|
+
(swap! ctx update-in [:network-state :bridges bridge :links] conj dev)
|
|
414
|
+
(error (str "Unable to add link " dev " into " bridge)))))
|
|
362
415
|
|
|
363
416
|
(defn bridge-drop-link
|
|
364
417
|
"Remove the link/interface 'dev' from the bridge 'bridge'.
|
|
365
|
-
Bridge type is dependent on
|
|
366
|
-
[bridge dev]
|
|
367
|
-
(P/let [{:keys [error
|
|
418
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
419
|
+
[{:keys [bridge mode]} dev]
|
|
420
|
+
(P/let [{:keys [error]} @ctx
|
|
368
421
|
cmd (get {:ovs (str "ovs-vsctl del-port " bridge " " dev)
|
|
369
422
|
:linux (str "ip link set dev " dev " nomaster")}
|
|
370
|
-
|
|
423
|
+
mode)
|
|
371
424
|
res (run cmd)]
|
|
372
|
-
(
|
|
373
|
-
(
|
|
374
|
-
|
|
425
|
+
(if (= 0 (:code res))
|
|
426
|
+
(swap! ctx update-in [:network-state :bridges bridge :links] disj dev)
|
|
427
|
+
(error (str "Unable to drop link " dev " from " bridge)))))
|
|
428
|
+
|
|
429
|
+
(defn patch-add-link
|
|
430
|
+
"Setup patch between 'dev' and its peer link using tc qdisc mirred
|
|
431
|
+
filter action. Peer links are tracked in pseudo-bridge 'bridge'."
|
|
432
|
+
[{:keys [bridge mode]} dev]
|
|
433
|
+
(let [{:keys [info error]} @ctx
|
|
434
|
+
links-path [:network-state :bridges bridge :links]
|
|
435
|
+
links (get-in @ctx links-path)
|
|
436
|
+
peers (disj links dev)]
|
|
437
|
+
(condp = (count peers)
|
|
438
|
+
0
|
|
439
|
+
(P/do
|
|
440
|
+
(info (str "Registering first peer link "
|
|
441
|
+
dev " in :patch 'bridge' " bridge))
|
|
442
|
+
(swap! ctx update-in links-path conj dev))
|
|
443
|
+
|
|
444
|
+
1
|
|
445
|
+
(P/let [cmd (str "link-mirred.sh " dev " " (first peers))
|
|
446
|
+
res (run cmd)]
|
|
447
|
+
(if (= 0 (:code res))
|
|
448
|
+
(swap! ctx update-in links-path conj dev)
|
|
449
|
+
(error (str "Failed to setup tc filter action for "
|
|
450
|
+
dev " in :patch 'bridge' " bridge))))
|
|
451
|
+
|
|
452
|
+
(error "Cannot add third peer link "
|
|
453
|
+
dev " to :patch 'bridge' " bridge))))
|
|
375
454
|
|
|
455
|
+
(defn patch-drop-link
|
|
456
|
+
"Remove tracking of 'dev' from pseudo-bridge 'bridge'."
|
|
457
|
+
[{:keys [bridge mode]} dev]
|
|
458
|
+
(let [{:keys [info error]} @ctx
|
|
459
|
+
links-path [:network-state :bridges bridge :links]]
|
|
460
|
+
(info (str "Removing peer link "
|
|
461
|
+
dev " from :patch 'bridge' " bridge))
|
|
462
|
+
;; State is in the links, no extra cleanup
|
|
463
|
+
(swap! ctx update-in links-path conj dev)))
|
|
464
|
+
|
|
465
|
+
;;; Link commands
|
|
376
466
|
|
|
377
467
|
(defn link-add
|
|
378
468
|
"Create a link/interface defined by 'link' in a container by calling
|
|
@@ -519,7 +609,10 @@ General Options:
|
|
|
519
609
|
(P/do
|
|
520
610
|
(swap! ctx assoc-in status-path :creating)
|
|
521
611
|
(link-add link)
|
|
522
|
-
(when bridge
|
|
612
|
+
(when bridge
|
|
613
|
+
(if (= :patch (:mode bridge))
|
|
614
|
+
(patch-add-link bridge outer-dev)
|
|
615
|
+
(bridge-add-link bridge outer-dev)))
|
|
523
616
|
(swap! ctx assoc-in status-path :created)))
|
|
524
617
|
|
|
525
618
|
"die"
|
|
@@ -527,7 +620,10 @@ General Options:
|
|
|
527
620
|
(error (str "Link " dev-id " does not exist"))
|
|
528
621
|
(P/do
|
|
529
622
|
(swap! ctx assoc-in status-path :deleting)
|
|
530
|
-
(when bridge
|
|
623
|
+
(when bridge
|
|
624
|
+
(if (= :patch (:mode bridge))
|
|
625
|
+
(patch-drop-link bridge outer-dev)
|
|
626
|
+
(bridge-drop-link bridge outer-dev)))
|
|
531
627
|
(link-del link)
|
|
532
628
|
(swap! ctx assoc-in status-path nil))))))
|
|
533
629
|
|
|
@@ -635,7 +731,7 @@ General Options:
|
|
|
635
731
|
(when (seq bridges)
|
|
636
732
|
(P/do
|
|
637
733
|
(log (str "Removing bridges: " (S/join ", " (keys bridges))))
|
|
638
|
-
(P/all (map bridge-del (
|
|
734
|
+
(P/all (map bridge-del (vals bridges)))))
|
|
639
735
|
(js/process.exit 127))))
|
|
640
736
|
|
|
641
737
|
|
|
@@ -650,19 +746,6 @@ General Options:
|
|
|
650
746
|
(when (empty? config-schema)
|
|
651
747
|
(fatal 2 "Could not find config-schema" orig-config-schema)))
|
|
652
748
|
|
|
653
|
-
(defn startup-checks
|
|
654
|
-
"Check startup state and exit if openvswitch kernel module is not
|
|
655
|
-
loaded or if no docker or podman connection could be established."
|
|
656
|
-
[bridge-mode docker podman]
|
|
657
|
-
(P/let
|
|
658
|
-
[kmod-okay? (if (= :ovs bridge-mode)
|
|
659
|
-
(kmod-loaded? "openvswitch")
|
|
660
|
-
true)]
|
|
661
|
-
(when (not kmod-okay?)
|
|
662
|
-
(fatal 1 "bridge-mode is 'ovs', but no 'openvswitch' module loaded"))
|
|
663
|
-
(when (and (not docker) (not podman))
|
|
664
|
-
(fatal 1 "Failed to start either docker or podman client/listener"))))
|
|
665
|
-
|
|
666
749
|
(defn server
|
|
667
750
|
"Process:
|
|
668
751
|
- parse/validate command line options
|
|
@@ -671,7 +754,7 @@ General Options:
|
|
|
671
754
|
- determine our own container ID and compose properties (if any)
|
|
672
755
|
- generate runtime network state and other process context/state
|
|
673
756
|
- install exit/cleanup handlers
|
|
674
|
-
- start/init openvswitch daemons/config (if :ovs
|
|
757
|
+
- start/init openvswitch daemons/config (if any bridges use :ovs mode)
|
|
675
758
|
- check that any defined bridges do not already exist
|
|
676
759
|
- create any bridges defined in network config links
|
|
677
760
|
- start listening/handling docker/podman container events
|
|
@@ -683,7 +766,7 @@ General Options:
|
|
|
683
766
|
{:keys [log info]} (swap! ctx merge (when verbose {:info Eprintln}))
|
|
684
767
|
opts (merge
|
|
685
768
|
opts
|
|
686
|
-
{:bridge-mode (keyword (:bridge-mode opts))
|
|
769
|
+
{:default-bridge-mode (keyword (:default-bridge-mode opts))
|
|
687
770
|
:orig-config-schema (:config-schema opts)
|
|
688
771
|
:config-schema (resolve-path (:config-schema opts) SCHEMA-PATHS)
|
|
689
772
|
:network-file (mapcat #(S/split % #":") (:network-file opts))
|
|
@@ -691,10 +774,15 @@ General Options:
|
|
|
691
774
|
_ (arg-checks opts)
|
|
692
775
|
_ (info (str "User options:\n" (indent-pprint-str opts " ")))
|
|
693
776
|
|
|
694
|
-
{:keys [network-file compose-file compose-project
|
|
777
|
+
{:keys [network-file compose-file compose-project]} opts
|
|
695
778
|
env (js->clj (js/Object.assign #js {} js/process.env))
|
|
696
779
|
self-pid js/process.pid
|
|
697
780
|
schema (load-config (:config-schema opts))
|
|
781
|
+
kmod-ovs? (kmod-loaded? "openvswitch")
|
|
782
|
+
kmod-mirred? (kmod-loaded? "act_mirred")
|
|
783
|
+
_ (swap! ctx merge {:default-bridge-mode (:default-bridge-mode opts)
|
|
784
|
+
:kmod-ovs? kmod-ovs?
|
|
785
|
+
:kmod-mirred? kmod-mirred?})
|
|
698
786
|
network-config (P/-> (load-configs compose-file network-file)
|
|
699
787
|
(interpolate-walk env)
|
|
700
788
|
(check-schema schema verbose)
|
|
@@ -705,7 +793,9 @@ General Options:
|
|
|
705
793
|
|
|
706
794
|
docker (docker-client (:docker-socket opts))
|
|
707
795
|
podman (docker-client (:podman-socket opts))
|
|
708
|
-
_ (
|
|
796
|
+
_ (when (and (not docker) (not podman))
|
|
797
|
+
(fatal 1 "Failed to start either docker or podman client/listener"))
|
|
798
|
+
|
|
709
799
|
self-cid (get-container-id)
|
|
710
800
|
self-container-obj (when self-cid
|
|
711
801
|
(get-container (or docker podman) self-cid))
|
|
@@ -714,8 +804,7 @@ General Options:
|
|
|
714
804
|
{:project compose-project}
|
|
715
805
|
(get-compose-labels self-container))
|
|
716
806
|
network-state (gen-network-state network-config)
|
|
717
|
-
ctx-data {:
|
|
718
|
-
:network-config network-config
|
|
807
|
+
ctx-data {:network-config network-config
|
|
719
808
|
:network-state network-state
|
|
720
809
|
:compose-opts compose-opts
|
|
721
810
|
:docker docker
|
|
@@ -730,7 +819,6 @@ General Options:
|
|
|
730
819
|
(js/process.on "SIGTERM" #(exit-handler % "signal"))
|
|
731
820
|
(js/process.on "uncaughtException" #(exit-handler %1 %2))
|
|
732
821
|
|
|
733
|
-
(log "Bridge mode:" (name bridge-mode))
|
|
734
822
|
(log (str "Using schema at '" (:config-schema opts) "'"))
|
|
735
823
|
(info (str "Starting network config\n"
|
|
736
824
|
(indent-pprint-str network-config " ")))
|
|
@@ -745,15 +833,16 @@ General Options:
|
|
|
745
833
|
(when self-cid
|
|
746
834
|
(rename-docker-eth0))
|
|
747
835
|
|
|
748
|
-
(when (= :ovs
|
|
836
|
+
(when (some #(= :ovs (:mode %)) (-> network-config :bridges vals))
|
|
749
837
|
(start-ovs))
|
|
750
838
|
|
|
751
839
|
;; Check that bridges/switches do not already exist
|
|
752
|
-
(P/all (for [bridge (
|
|
840
|
+
(P/all (for [bridge (vals (:bridges network-state))]
|
|
753
841
|
(check-no-bridge bridge)))
|
|
842
|
+
|
|
754
843
|
;; Create bridges/switch configs
|
|
755
844
|
;; TODO: should be done on-demand
|
|
756
|
-
(P/all (for [bridge (
|
|
845
|
+
(P/all (for [bridge (vals (:bridges network-state))]
|
|
757
846
|
(bridge-create bridge)))
|
|
758
847
|
|
|
759
848
|
;; Create tunnels configs
|