conlink 2.1.0 → 2.2.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/Dockerfile +1 -1
- package/README.md +83 -18
- package/examples/test10-compose.yaml +38 -0
- package/examples/test7-compose.yaml +4 -1
- package/link-add.sh +2 -0
- package/link-forward.sh +45 -0
- package/package.json +1 -1
- package/run-tests.sh +46 -18
- package/schema.yaml +3 -0
- package/src/conlink/core.cljs +98 -38
package/Dockerfile
CHANGED
|
@@ -28,7 +28,7 @@ RUN apt-get -y install libpcap-dev tcpdump iproute2 iputils-ping curl \
|
|
|
28
28
|
openvswitch-switch openvswitch-testcontroller
|
|
29
29
|
|
|
30
30
|
COPY --from=build /app/ /app/
|
|
31
|
-
ADD link-add.sh link-del.sh link-mirred.sh /app/
|
|
31
|
+
ADD link-add.sh link-del.sh link-mirred.sh link-forward.sh /app/
|
|
32
32
|
ADD schema.yaml /app/build/
|
|
33
33
|
|
|
34
34
|
ENV PATH /app:$PATH
|
package/README.md
CHANGED
|
@@ -92,28 +92,31 @@ interfaces in the host.
|
|
|
92
92
|
|
|
93
93
|
The following table describes the link properties:
|
|
94
94
|
|
|
95
|
-
| property | link types | format
|
|
96
|
-
|
|
97
|
-
| type | * | string 1
|
|
98
|
-
| service | * | string
|
|
99
|
-
| container | * | string
|
|
100
|
-
| bridge | veth | string
|
|
101
|
-
| outer-dev | not dummy | string[15]
|
|
102
|
-
| dev | * | string[15]
|
|
103
|
-
| ip | * | CIDR
|
|
104
|
-
| mac | 3 | MAC
|
|
105
|
-
| mtu | * | number 4
|
|
106
|
-
| route | * | string
|
|
107
|
-
| nat | * | IP
|
|
108
|
-
| netem | * | string
|
|
109
|
-
| mode | 5 | string
|
|
110
|
-
| vlanid | vlan | number
|
|
95
|
+
| property | link types | format | default | description |
|
|
96
|
+
|-----------|------------|----------------|---------|--------------------------|
|
|
97
|
+
| type | * | string 1 | veth | link/interface type |
|
|
98
|
+
| service | * | string | 2 | compose service |
|
|
99
|
+
| container | * | string | | container name |
|
|
100
|
+
| bridge | veth | string | | conlink bridge / domain |
|
|
101
|
+
| outer-dev | not dummy | string[15] | | conlink/host intf name |
|
|
102
|
+
| dev | * | string[15] | eth0 | container intf name |
|
|
103
|
+
| ip | * | CIDR | | IP CIDR 7 |
|
|
104
|
+
| mac | 3 | MAC | | MAC addr 7 |
|
|
105
|
+
| mtu | * | number 4 | 65535 | intf MTU |
|
|
106
|
+
| route | * | string | | ip route add args |
|
|
107
|
+
| nat | * | IP | | DNAT/SNAT to IP |
|
|
108
|
+
| netem | * | string | | tc qdisc NetEm options |
|
|
109
|
+
| mode | 5 | string | | virt intf mode |
|
|
110
|
+
| vlanid | vlan | number | | VLAN ID |
|
|
111
|
+
| forward | veth | string array 6 | | forward conlink ports 7 |
|
|
111
112
|
|
|
112
113
|
- 1 - veth, dummy, vlan, ipvlan, macvlan, ipvtap, macvtap
|
|
113
114
|
- 2 - defaults to outer compose service
|
|
114
115
|
- 3 - not ipvlan/ipvtap
|
|
115
116
|
- 4 - max MTU of parent device for \*vlan, \*vtap types
|
|
116
117
|
- 5 - macvlan, macvtap, ipvlan, ipvtap
|
|
118
|
+
- 6 - string syntax: `conlink_port:container_port/proto`
|
|
119
|
+
- 7 - offset by scale/replica index
|
|
117
120
|
|
|
118
121
|
Each link has a 'type' key that defaults to "veth" and each link
|
|
119
122
|
definition must also have either a `service` key or a `container` key.
|
|
@@ -135,6 +138,18 @@ than the MTU of the parent (outer-dev) device.
|
|
|
135
138
|
For the `netem` property, refer to the `netem` man page. The `OPTIONS`
|
|
136
139
|
grammar defines the valid strings for the `netem` property.
|
|
137
140
|
|
|
141
|
+
The `forward` property is an array of strings that defines ports to
|
|
142
|
+
forward from the conlink container into the container over this link.
|
|
143
|
+
Traffic arriving on the conlink container's docker interface of type
|
|
144
|
+
`proto` and destined for port `conlink_port` is forwarded over this
|
|
145
|
+
link to the container IP and port `container_port` (`ip` is required).
|
|
146
|
+
The initial port (`conlink_port`) is offset by the service
|
|
147
|
+
replica/scale number (minus 1). So if the first replica has port 80
|
|
148
|
+
forwarded then the second replica will have port 81 forwarded.
|
|
149
|
+
For publicly publishing a port, the conlink container needs to be on
|
|
150
|
+
a docker network and the `conlink_port` should match the target port
|
|
151
|
+
of a docker published port (for the conlink container).
|
|
152
|
+
|
|
138
153
|
### Bridges
|
|
139
154
|
|
|
140
155
|
The bridge settings currently only support the "mode" setting. If
|
|
@@ -449,8 +464,10 @@ Start the test7 compose configuration:
|
|
|
449
464
|
docker-compose -f examples/test7-compose.yaml up --build --force-recreate
|
|
450
465
|
```
|
|
451
466
|
|
|
452
|
-
Show the links in both node containers to see that the
|
|
453
|
-
are `00:0a:0b:0c:0d:0*` and the MTUs are
|
|
467
|
+
Show the links in both node containers to see that on the eth0
|
|
468
|
+
interfaces the MAC addresses are `00:0a:0b:0c:0d:0*` and the MTUs are
|
|
469
|
+
set to `4111`. The eth1 interfaces should have the command line set
|
|
470
|
+
default MTU of `5111`.
|
|
454
471
|
|
|
455
472
|
```
|
|
456
473
|
docker-compose -f examples/test7-compose.yaml exec --index 1 node ip link
|
|
@@ -517,6 +534,54 @@ docker-compose -f examples/test9-compose.yaml up --build --force-recreate
|
|
|
517
534
|
docker-compose -f examples/test9-compose.yaml exec node ping 10.0.1.2
|
|
518
535
|
```
|
|
519
536
|
|
|
537
|
+
### test10: port forwarding
|
|
538
|
+
|
|
539
|
+
This example demonstrates port forwarding from the conlink container
|
|
540
|
+
to two containers running simple web servers.
|
|
541
|
+
|
|
542
|
+
Start the test10 compose configuration:
|
|
543
|
+
|
|
544
|
+
```
|
|
545
|
+
docker-compose -f examples/test10-compose.yaml up --build --force-recreate
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Ports 3080 and 8080 are both published on the host by the conlink
|
|
549
|
+
container using standard Docker port mapping. The internal mapping of
|
|
550
|
+
those ports (1080 and 1180 respectively) are both are forwarded to
|
|
551
|
+
port 80 in the node1 container using conlink's port forwarding
|
|
552
|
+
mechanism. The two paths look like this:
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
host:3080 --> 1080 (in conlink) --> node1:80
|
|
556
|
+
host:8080 --> 1180 (in conlink) --> node1:80
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Use curl on the host to query both of these paths to node1:
|
|
560
|
+
|
|
561
|
+
```
|
|
562
|
+
curl 0.0.0.0:3080
|
|
563
|
+
curl 0.0.0.0:8080
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Ports 80 and 81 are published on the host by the conlink container
|
|
567
|
+
using standard Docker port mapping. Then conlink forwards from ports
|
|
568
|
+
80 and 81 to the first and second replica (respectively) of node2,
|
|
569
|
+
each of which listen internally on port 80. The two paths look like
|
|
570
|
+
this:
|
|
571
|
+
|
|
572
|
+
```
|
|
573
|
+
host:80 -> 80 (in conlink) -> node2_1:80
|
|
574
|
+
host:81 -> 81 (in conlink) -> node2_2:80
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Use curl on the host to query both replicas of node2:
|
|
578
|
+
|
|
579
|
+
```
|
|
580
|
+
curl 0.0.0.0:80
|
|
581
|
+
curl 0.0.0.0:81
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
|
|
520
585
|
## GraphViz network configuration rendering
|
|
521
586
|
|
|
522
587
|
You can use d3 and GraphViz to create a visual graph rendering of
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
version: "2.4"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
node1:
|
|
5
|
+
image: python:3-alpine
|
|
6
|
+
network_mode: none
|
|
7
|
+
command: "python3 -m http.server -d /var 80"
|
|
8
|
+
x-network:
|
|
9
|
+
links:
|
|
10
|
+
- {bridge: s2, ip: "10.0.1.1/24", route: "default",
|
|
11
|
+
forward: ["1080:80/tcp", "1180:80/tcp"]}
|
|
12
|
+
|
|
13
|
+
node2:
|
|
14
|
+
image: python:3-alpine
|
|
15
|
+
network_mode: none
|
|
16
|
+
scale: 2
|
|
17
|
+
command: "python3 -m http.server -d /usr 80"
|
|
18
|
+
x-network:
|
|
19
|
+
links:
|
|
20
|
+
- {bridge: s1, ip: "10.0.2.1/24", route: "default",
|
|
21
|
+
forward: ["80:80/tcp"]}
|
|
22
|
+
|
|
23
|
+
network:
|
|
24
|
+
build: {context: ../}
|
|
25
|
+
image: conlink
|
|
26
|
+
pid: host
|
|
27
|
+
cap_add: [SYS_ADMIN, NET_ADMIN, SYS_NICE, NET_BROADCAST, IPC_LOCK]
|
|
28
|
+
security_opt: [ 'apparmor:unconfined' ] # needed on Ubuntu 18.04
|
|
29
|
+
volumes:
|
|
30
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
31
|
+
- /var/lib/docker:/var/lib/docker
|
|
32
|
+
- ../:/test
|
|
33
|
+
ports:
|
|
34
|
+
- "3080:1080/tcp"
|
|
35
|
+
- "8080:1180/tcp"
|
|
36
|
+
- "80:80/tcp"
|
|
37
|
+
- "81:81/tcp"
|
|
38
|
+
command: /app/build/conlink.js --compose-file /test/examples/test10-compose.yaml
|
|
@@ -15,7 +15,7 @@ services:
|
|
|
15
15
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
16
16
|
- /var/lib/docker:/var/lib/docker
|
|
17
17
|
- ./:/test
|
|
18
|
-
command: /app/build/conlink.js --compose-file /test/test7-compose.yaml
|
|
18
|
+
command: /app/build/conlink.js --default-mtu 5111 --compose-file /test/test7-compose.yaml
|
|
19
19
|
|
|
20
20
|
node:
|
|
21
21
|
image: alpine
|
|
@@ -29,3 +29,6 @@ services:
|
|
|
29
29
|
mac: 00:0a:0b:0c:0d:01
|
|
30
30
|
mtu: 4111
|
|
31
31
|
netem: "delay 40ms rate 10mbit"
|
|
32
|
+
- bridge: s2
|
|
33
|
+
ip: 100.0.1.1/16
|
|
34
|
+
dev: eth1
|
package/link-add.sh
CHANGED
package/link-forward.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
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] <add|del> INTF_A INTF_B PORT_A:IP:PORT_B/PROTO"
|
|
10
|
+
echo >&2 ""
|
|
11
|
+
echo >&2 "Match traffic on INTF_A that has destination port PORT_A and"
|
|
12
|
+
echo >&2 "protocol PROTO (tcp or udp). Forward/DNAT traffic to IP:PORT_B "
|
|
13
|
+
echo >&2 "via INTF_B."
|
|
14
|
+
exit 2
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
info() { echo "link-forward [${LOG_ID}] ${*}"; }
|
|
18
|
+
|
|
19
|
+
IPTABLES() {
|
|
20
|
+
case "${action}" in
|
|
21
|
+
add) iptables -C "${@}" 2>/dev/null || iptables -I "${@}" ;;
|
|
22
|
+
del) iptables -D "${@}" 2>/dev/null || true;;
|
|
23
|
+
esac
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
action=$1; shift || usage
|
|
27
|
+
intf_a=$1; shift || usage
|
|
28
|
+
intf_b=$1; shift || usage
|
|
29
|
+
spec=$1; shift || usage
|
|
30
|
+
read port_a ip port_b proto <<< "${spec//[:\/]/ }"
|
|
31
|
+
|
|
32
|
+
[ "${action}" -a "${intf_a}" -a "${intf_b}" ] || usage
|
|
33
|
+
[ "${port_a}" -a "${ip}" -a "${port_b}" -a "${proto}" ] || usage
|
|
34
|
+
|
|
35
|
+
LOG_ID="${spec}"
|
|
36
|
+
|
|
37
|
+
info "${action^} forwarding ${intf_a} -> ${intf_b}"
|
|
38
|
+
|
|
39
|
+
IPTABLES PREROUTING -t nat -i ${intf_a} -p ${proto} --dport ${port_a} -j DNAT --to-destination ${ip}:${port_b}
|
|
40
|
+
IPTABLES POSTROUTING -t nat -o ${intf_b} -j MASQUERADE
|
|
41
|
+
|
|
42
|
+
case "${action}" in
|
|
43
|
+
add) ip route replace ${ip} dev ${intf_b} ;;
|
|
44
|
+
del) ip route delete ${ip} dev ${intf_b} || true;;
|
|
45
|
+
esac
|
package/package.json
CHANGED
package/run-tests.sh
CHANGED
|
@@ -29,37 +29,47 @@ dc_init() {
|
|
|
29
29
|
done
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
local
|
|
32
|
+
dc_run() {
|
|
33
|
+
local cont="${1}" svc= idx= result=0
|
|
34
34
|
case "${cont}" in
|
|
35
35
|
*_[0-9]|*_[0-9][0-9]) svc="${cont%_*}" idx="${cont##*_}" ;;
|
|
36
36
|
*) svc="${cont}" idx=1 ;;
|
|
37
37
|
esac
|
|
38
|
-
shift
|
|
38
|
+
shift
|
|
39
39
|
|
|
40
40
|
#echo "target: ${1}, service: ${svc}, index: ${idx}"
|
|
41
|
+
if [ "${VERBOSE}" ]; then
|
|
42
|
+
vecho " Running: dc exec -T --index ${idx} ${svc} sh -c ${*}"
|
|
43
|
+
dc exec -T --index ${idx} ${svc} sh -c "${*}" || result=$?
|
|
44
|
+
else
|
|
45
|
+
dc exec -T --index ${idx} ${svc} sh -c "${*}" > /dev/null || result=$?
|
|
46
|
+
fi
|
|
47
|
+
return ${result}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
do_test() {
|
|
51
|
+
local tries="${1}"; shift
|
|
52
|
+
local name="${TEST_NUM} ${GROUP}: ${@}" try=1 result=
|
|
53
|
+
TEST_NUM=$(( TEST_NUM + 1 ))
|
|
54
|
+
vecho " > Running test ${name}"
|
|
41
55
|
while true; do
|
|
42
56
|
result=0
|
|
43
|
-
if [ "${
|
|
44
|
-
|
|
45
|
-
dc exec -T --index ${idx} ${svc} sh -c "${*}" || result=$?
|
|
57
|
+
if [ "${WITH_DC}" ]; then
|
|
58
|
+
dc_run "${@}" || result=$?
|
|
46
59
|
else
|
|
47
|
-
|
|
60
|
+
vecho " Running: eval ${*}"
|
|
61
|
+
if [ "${VERBOSE}" ]; then
|
|
62
|
+
sh -c "${*}" || result=$?
|
|
63
|
+
else
|
|
64
|
+
sh -c "${*}" >/dev/null || result=$?
|
|
65
|
+
fi
|
|
48
66
|
fi
|
|
49
67
|
[ "${result}" -eq 0 -o "${try}" -ge "${tries}" ] && break
|
|
50
68
|
echo " command failed (${result}), sleeping 2s before retry (${try}/${tries})"
|
|
51
69
|
sleep 2
|
|
52
70
|
try=$(( try + 1 ))
|
|
53
71
|
done
|
|
54
|
-
|
|
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}"]=$?
|
|
72
|
+
RESULTS["${name}"]=${result}
|
|
63
73
|
if [ "${RESULTS["${name}"]}" = 0 ]; then
|
|
64
74
|
PASS=$(( PASS + 1 ))
|
|
65
75
|
vecho " > PASS (0 for ${*})"
|
|
@@ -67,8 +77,12 @@ dc_test() {
|
|
|
67
77
|
FAIL=$(( FAIL + 1 ))
|
|
68
78
|
echo " > FAIL (${RESULTS[${name}]} for ${*})"
|
|
69
79
|
fi
|
|
80
|
+
return ${result}
|
|
70
81
|
}
|
|
71
82
|
|
|
83
|
+
dc_wait() { WITH_DC=1 do_test "${@}"; }
|
|
84
|
+
dc_test() { WITH_DC=1 do_test 1 "${@}"; }
|
|
85
|
+
|
|
72
86
|
|
|
73
87
|
echo -e "\n\n>>> test1: combined config"
|
|
74
88
|
GROUP=test1
|
|
@@ -137,8 +151,8 @@ dc_test node_1 'ip link show eth0 | grep "ether 00:0a:0b:0c:0d:01"'
|
|
|
137
151
|
dc_test node_2 'ip link show eth0 | grep "ether 00:0a:0b:0c:0d:02"'
|
|
138
152
|
dc_test node_1 'ip link show eth0 | grep "mtu 4111"'
|
|
139
153
|
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 -
|
|
154
|
+
echo " >> Check for min round-trip ping delay of about 80ms"
|
|
155
|
+
dc_test node_1 'ping -c5 10.0.1.2 | grep "min/avg/max = 8[012345]\."'
|
|
142
156
|
|
|
143
157
|
|
|
144
158
|
echo -e "\n\n>>> test9: bridge modes and variable templating"
|
|
@@ -170,6 +184,20 @@ dc_test network 'tc filter show dev node_1-eth0 parent ffff: | grep "action orde
|
|
|
170
184
|
echo " >> Check for round-trip ping connectivity (BRIDGE_MODE=patch)"
|
|
171
185
|
dc_test node_1 'ping -c2 10.0.1.2'
|
|
172
186
|
|
|
187
|
+
echo -e "\n\n>>> test10: port forwarding"
|
|
188
|
+
GROUP=test10
|
|
189
|
+
echo "COMPOSE_FILE=examples/test10-compose.yaml" > .env
|
|
190
|
+
|
|
191
|
+
dc_init; dc_wait 10 node1_1 'ip addr | grep "10\.0\.1\.1"' \
|
|
192
|
+
|| die "test10 startup failed"
|
|
193
|
+
echo " >> Check ping between replicas"
|
|
194
|
+
dc_test node2_1 'ping -c2 10.0.2.2'
|
|
195
|
+
echo " >> Ensure ports are forwarded correctly"
|
|
196
|
+
do_test 10 'curl -s -S "http://0.0.0.0:3080" | grep "log"'
|
|
197
|
+
do_test 10 'curl -s -S "http://0.0.0.0:8080" | grep "log"'
|
|
198
|
+
do_test 10 'curl -s -S "http://0.0.0.0:80" | grep "share"'
|
|
199
|
+
do_test 10 'curl -s -S "http://0.0.0.0:81" | grep "share"'
|
|
200
|
+
|
|
173
201
|
|
|
174
202
|
echo -e "\n\n>>> Cleaning up"
|
|
175
203
|
dc down -t1 --remove-orphans
|
package/schema.yaml
CHANGED
package/src/conlink/core.cljs
CHANGED
|
@@ -30,6 +30,8 @@ General Options:
|
|
|
30
30
|
--default-bridge-mode BRIDGE-MODE Default bridge mode (ovs, linux, patch, or auto)
|
|
31
31
|
to use for bridge/switch connections
|
|
32
32
|
[default: auto] [env: CONLINK_BRIDGE_MODE]
|
|
33
|
+
--default-mtu MTU Default link MTU (for non *vlan types)
|
|
34
|
+
[default: 65535]
|
|
33
35
|
--network-file NETWORK-FILE... Network config file
|
|
34
36
|
--compose-file COMPOSE-FILE... Docker compose file with network config
|
|
35
37
|
--compose-project NAME Docker compose project name for resolving
|
|
@@ -57,6 +59,7 @@ General Options:
|
|
|
57
59
|
(def VLAN-TYPES #{:vlan :macvlan :macvtap :ipvlan :ipvtap})
|
|
58
60
|
(def LINK-ADD-OPTS [:ip :mac :route :mtu :nat :netem :mode :vlanid :remote :vni])
|
|
59
61
|
(def INTF-MAX-LEN 15)
|
|
62
|
+
(def DOCKER-INTF "DOCKER-ETH0")
|
|
60
63
|
|
|
61
64
|
(def ctx (atom {:error #(apply Eprintln "ERROR:" %&)
|
|
62
65
|
:warn #(apply Eprintln "WARNING:" %&)
|
|
@@ -96,31 +99,52 @@ General Options:
|
|
|
96
99
|
net-cfg))
|
|
97
100
|
|
|
98
101
|
(defn enrich-link
|
|
99
|
-
"
|
|
100
|
-
|
|
102
|
+
"Check and enrich link config
|
|
103
|
+
- Resolve bridge name to full bridge map.
|
|
104
|
+
- Add default values to a link:
|
|
101
105
|
- type: veth
|
|
102
106
|
- dev: eth0
|
|
103
|
-
- mtu:
|
|
107
|
+
- mtu: --default-mtu (for non *vlan type)
|
|
104
108
|
- base: :conlink for veth type, :host for *vlan types, :local otherwise"
|
|
105
|
-
[{:as link :keys [type base bridge ip
|
|
106
|
-
(let [
|
|
109
|
+
[{:as link :keys [type base bridge ip forward]} bridges]
|
|
110
|
+
(let [{:keys [default-mtu docker-eth0?]} @ctx
|
|
111
|
+
type (keyword (or type "veth"))
|
|
112
|
+
dev (get link :dev "eth0")
|
|
107
113
|
base-default (cond (= :veth type) :conlink
|
|
108
114
|
(VLAN-TYPES type) :host
|
|
109
115
|
:else :local)
|
|
110
116
|
base (get link :base base-default)
|
|
117
|
+
bridge (get bridges bridge)
|
|
111
118
|
link (merge
|
|
112
119
|
link
|
|
113
120
|
{:type type
|
|
114
|
-
:dev
|
|
121
|
+
:dev dev
|
|
115
122
|
:base base}
|
|
116
123
|
(when bridge
|
|
117
|
-
{:bridge
|
|
124
|
+
{:bridge bridge})
|
|
118
125
|
(when (not (VLAN-TYPES type))
|
|
119
|
-
{:mtu (get link :mtu
|
|
126
|
+
{:mtu (get link :mtu default-mtu)})
|
|
127
|
+
(when forward
|
|
128
|
+
{:forward
|
|
129
|
+
(map #(let [[port_a port_b proto] (S/split % #"[:/]")]
|
|
130
|
+
[(js/parseInt port_a) (js/parseInt port_b) proto])
|
|
131
|
+
forward)}))]
|
|
132
|
+
(when forward
|
|
133
|
+
(let [link-id (str (or (:service link) (:container link)) ":" dev)
|
|
134
|
+
pre (str "link '" link-id "' has forward setting")]
|
|
135
|
+
(when (not= :veth type)
|
|
136
|
+
(fatal 1 (str pre " but is not of type :veth")))
|
|
137
|
+
(when (not (#{:ovs :linux} (:mode bridge)))
|
|
138
|
+
(fatal 1 (str pre " but bridge mode is not 'ovs' or 'linux'")))
|
|
139
|
+
(when (not ip)
|
|
140
|
+
(fatal 1 (str pre " but does not have IP")))
|
|
141
|
+
(when (not docker-eth0?)
|
|
142
|
+
(fatal 1 (str pre " but no docker eth0 is present")))))
|
|
120
143
|
link))
|
|
121
144
|
|
|
122
145
|
(defn enrich-bridge
|
|
123
|
-
"
|
|
146
|
+
"Check and enrich bridge config.
|
|
147
|
+
If bridge mode is :auto then return :ovs if the 'openvswitch' kernel module
|
|
124
148
|
is loaded otherwise fall back to :linux. Exit with an error if mode is :ovs
|
|
125
149
|
or :patch and the 'openvswitch' or 'act_mirred' kernel modules are not
|
|
126
150
|
loaded respectively."
|
|
@@ -223,7 +247,7 @@ General Options:
|
|
|
223
247
|
"Add offset value to ip and mac keys in a link definition to account
|
|
224
248
|
for multiple instances of that link i.e. a compose service with
|
|
225
249
|
multiple replicas (scale >= 2)."
|
|
226
|
-
[{:as link :keys [ip mac]} offset]
|
|
250
|
+
[{:as link :keys [ip mac forward]} offset]
|
|
227
251
|
;; TODO: add vlanid
|
|
228
252
|
(let [mac (when mac
|
|
229
253
|
(addrs/int->mac
|
|
@@ -232,8 +256,15 @@ General Options:
|
|
|
232
256
|
(let [[ip prefix] (S/split ip #"/")]
|
|
233
257
|
(str (addrs/int->ip
|
|
234
258
|
(+ offset (addrs/ip->int ip)))
|
|
235
|
-
"/" prefix)))
|
|
236
|
-
|
|
259
|
+
"/" prefix)))
|
|
260
|
+
forward (when forward
|
|
261
|
+
(map #(let [[port_a port_b proto] %]
|
|
262
|
+
[(+ offset port_a) port_b proto])
|
|
263
|
+
forward))]
|
|
264
|
+
(merge link
|
|
265
|
+
(when mac {:mac mac})
|
|
266
|
+
(when ip {:ip ip})
|
|
267
|
+
(when forward {:forward forward}))))
|
|
237
268
|
|
|
238
269
|
|
|
239
270
|
(defn link-instance-enrich
|
|
@@ -305,26 +336,36 @@ General Options:
|
|
|
305
336
|
(P/recur cmds)
|
|
306
337
|
res))))
|
|
307
338
|
|
|
339
|
+
(defn kmod-loaded?
|
|
340
|
+
"Return whether kernel module 'kmod' is loaded."
|
|
341
|
+
[kmod]
|
|
342
|
+
(P/let [cmd (str "grep -o '^" kmod "\\>' /proc/modules")
|
|
343
|
+
res (run cmd {:quiet true})]
|
|
344
|
+
(and (= 0 (:code res)) (= kmod (trim (:stdout res))))))
|
|
345
|
+
|
|
346
|
+
(defn intf-exists?
|
|
347
|
+
"Return whether network interface exists"
|
|
348
|
+
[intf]
|
|
349
|
+
(P/let [cmd (str "[ -d /sys/class/net/" intf " ]")
|
|
350
|
+
res (run cmd {:quiet true})]
|
|
351
|
+
(= 0 (:code res))))
|
|
352
|
+
|
|
308
353
|
(defn rename-docker-eth0
|
|
309
|
-
"
|
|
354
|
+
"Rename docker's provided eth0 to DOCKER-INTF to prevent 'RTNETLINK
|
|
310
355
|
answers: File exists' errors during creation of links that use
|
|
311
356
|
'eth0' device name. This is necessary because even if the netns is
|
|
312
357
|
specified with the same link create command, the creation and move
|
|
313
358
|
does not appear to be idempotent and results in the conflict."
|
|
314
359
|
[]
|
|
315
360
|
(P/let [{:keys [log]} @ctx
|
|
316
|
-
res (run
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
(str "ip route restore < /tmp/routesave")]
|
|
325
|
-
{:id "rename"})]
|
|
326
|
-
(when (not= 0 (:code res))
|
|
327
|
-
(fatal 1 "Could not rename docker eth0 interface"))))))
|
|
361
|
+
res (run* [(str "ip route save dev eth0 > /tmp/routesave")
|
|
362
|
+
(str "ip link set eth0 down")
|
|
363
|
+
(str "ip link set eth0 name " DOCKER-INTF)
|
|
364
|
+
(str "ip link set " DOCKER-INTF " up")
|
|
365
|
+
(str "ip route restore < /tmp/routesave")]
|
|
366
|
+
{:id "rename"})]
|
|
367
|
+
(when (not= 0 (:code res))
|
|
368
|
+
(fatal 1 "Could not rename docker eth0 interface"))))
|
|
328
369
|
|
|
329
370
|
(defn start-ovs
|
|
330
371
|
"Start and initialize the openvswitch daemons. Exit with error if it
|
|
@@ -335,13 +376,6 @@ General Options:
|
|
|
335
376
|
(fatal 1 (str "Failed starting OVS: " (:stderr res)))
|
|
336
377
|
res)))
|
|
337
378
|
|
|
338
|
-
(defn kmod-loaded?
|
|
339
|
-
"Return whether kernel module 'kmod' is loaded."
|
|
340
|
-
[kmod]
|
|
341
|
-
(P/let [cmd (str "grep -o '^" kmod "\\>' /proc/modules")
|
|
342
|
-
res (run cmd {:quiet true})]
|
|
343
|
-
(and (= 0 (:code res)) (= kmod (trim (:stdout res))))))
|
|
344
|
-
|
|
345
379
|
;;; Bridge commands
|
|
346
380
|
|
|
347
381
|
(defn check-no-bridge
|
|
@@ -377,7 +411,7 @@ General Options:
|
|
|
377
411
|
(if (not cmd)
|
|
378
412
|
(info (str "Ignoring bridge/switch " bridge " for mode " mode))
|
|
379
413
|
(P/let [_ (info "Creating bridge/switch" bridge)
|
|
380
|
-
res (run cmd)]
|
|
414
|
+
res (run* [cmd (str "ip link set " bridge " up")])]
|
|
381
415
|
(if (not= 0 (:code res))
|
|
382
416
|
(error (str "Unable to create bridge/switch " bridge))
|
|
383
417
|
(swap! ctx assoc-in [:network-state :bridges bridge :status] :created))
|
|
@@ -470,7 +504,7 @@ General Options:
|
|
|
470
504
|
line arguments from the 'link' definition and reports the results."
|
|
471
505
|
[link]
|
|
472
506
|
(P/let [{:keys [error]} @ctx
|
|
473
|
-
{:keys [type dev outer-dev pid outer-pid
|
|
507
|
+
{:keys [type dev outer-dev pid outer-pid dev-id]} link
|
|
474
508
|
cmd (str "link-add.sh"
|
|
475
509
|
" '" (name type) "' '" pid "' '" dev "'"
|
|
476
510
|
(when outer-pid (str " --pid1 " outer-pid))
|
|
@@ -499,6 +533,26 @@ General Options:
|
|
|
499
533
|
(error (str "Unable to delete " dev-id ": " (:stderr res)))))
|
|
500
534
|
res))
|
|
501
535
|
|
|
536
|
+
;;; Port forward command
|
|
537
|
+
|
|
538
|
+
(defn forward-modify
|
|
539
|
+
"Depending on 'action' create ('add') or delete ('del') port
|
|
540
|
+
forwards defined by :forward property of 'link'."
|
|
541
|
+
[link action]
|
|
542
|
+
(P/let [{:keys [error]} @ctx
|
|
543
|
+
{:keys [outer-dev dev-id bridge ip forward]} link]
|
|
544
|
+
(P/all (for [fwd forward]
|
|
545
|
+
(P/let [[port_a port_b proto] fwd
|
|
546
|
+
ip (S/replace ip #"/.*" "")
|
|
547
|
+
cmd (str "link-forward.sh " action
|
|
548
|
+
" " DOCKER-INTF " " (:bridge bridge)
|
|
549
|
+
" " port_a ":" ip ":" port_b "/" proto)
|
|
550
|
+
res (run cmd {:id dev-id})]
|
|
551
|
+
(when (not= 0 (:code res))
|
|
552
|
+
(error (str "Unable to " action " forward "
|
|
553
|
+
"'" fwd "' for " dev-id)))
|
|
554
|
+
res)))))
|
|
555
|
+
|
|
502
556
|
|
|
503
557
|
;;; docker/docker-compose utilities
|
|
504
558
|
|
|
@@ -597,7 +651,7 @@ General Options:
|
|
|
597
651
|
[link action]
|
|
598
652
|
(P/let
|
|
599
653
|
[{:keys [error log]} @ctx
|
|
600
|
-
{:keys [type outer-dev bridge dev-id]} link
|
|
654
|
+
{:keys [type outer-dev bridge dev-id forward]} link
|
|
601
655
|
status-path [:network-state :devices dev-id :status]
|
|
602
656
|
link-status (get-in @ctx status-path)]
|
|
603
657
|
(log (str (get {"start" "Creating" "die" "Deleting"} action)
|
|
@@ -613,6 +667,7 @@ General Options:
|
|
|
613
667
|
(if (= :patch (:mode bridge))
|
|
614
668
|
(patch-add-link bridge outer-dev)
|
|
615
669
|
(bridge-add-link bridge outer-dev)))
|
|
670
|
+
(when forward (forward-modify link "add"))
|
|
616
671
|
(swap! ctx assoc-in status-path :created)))
|
|
617
672
|
|
|
618
673
|
"die"
|
|
@@ -620,6 +675,7 @@ General Options:
|
|
|
620
675
|
(error (str "Link " dev-id " does not exist"))
|
|
621
676
|
(P/do
|
|
622
677
|
(swap! ctx assoc-in status-path :deleting)
|
|
678
|
+
(when forward (forward-modify link "del"))
|
|
623
679
|
(when bridge
|
|
624
680
|
(if (= :patch (:mode bridge))
|
|
625
681
|
(patch-drop-link bridge outer-dev)
|
|
@@ -777,12 +833,16 @@ General Options:
|
|
|
777
833
|
{:keys [network-file compose-file compose-project]} opts
|
|
778
834
|
env (js->clj (js/Object.assign #js {} js/process.env))
|
|
779
835
|
self-pid js/process.pid
|
|
836
|
+
self-cid (get-container-id)
|
|
780
837
|
schema (load-config (:config-schema opts))
|
|
781
838
|
kmod-ovs? (kmod-loaded? "openvswitch")
|
|
782
839
|
kmod-mirred? (kmod-loaded? "act_mirred")
|
|
840
|
+
docker-eth0? (and self-cid (intf-exists? "eth0"))
|
|
783
841
|
_ (swap! ctx merge {:default-bridge-mode (:default-bridge-mode opts)
|
|
842
|
+
:default-mtu (:default-mtu opts)
|
|
784
843
|
:kmod-ovs? kmod-ovs?
|
|
785
|
-
:kmod-mirred? kmod-mirred?
|
|
844
|
+
:kmod-mirred? kmod-mirred?
|
|
845
|
+
:docker-eth0? docker-eth0?})
|
|
786
846
|
network-config (P/-> (load-configs compose-file network-file)
|
|
787
847
|
(interpolate-walk env)
|
|
788
848
|
(check-schema schema verbose)
|
|
@@ -796,7 +856,6 @@ General Options:
|
|
|
796
856
|
_ (when (and (not docker) (not podman))
|
|
797
857
|
(fatal 1 "Failed to start either docker or podman client/listener"))
|
|
798
858
|
|
|
799
|
-
self-cid (get-container-id)
|
|
800
859
|
self-container-obj (when self-cid
|
|
801
860
|
(get-container (or docker podman) self-cid))
|
|
802
861
|
self-container (inspect-container self-container-obj)
|
|
@@ -830,7 +889,8 @@ General Options:
|
|
|
830
889
|
(info "Detected compose context:" compose-project))
|
|
831
890
|
|
|
832
891
|
(P/do
|
|
833
|
-
(when
|
|
892
|
+
(when docker-eth0?
|
|
893
|
+
(log "Renaming eth0 to" DOCKER-INTF)
|
|
834
894
|
(rename-docker-eth0))
|
|
835
895
|
|
|
836
896
|
(when (some #(= :ovs (:mode %)) (-> network-config :bridges vals))
|