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 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 | 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 (index offset) |
104
- | mac | 3 | MAC | | MAC addr (index offset) |
105
- | mtu | * | number 4 | 9000 | 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 |
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 MAC addresses
453
- are `00:0a:0b:0c:0d:0*` and the MTUs are set to `4111`.
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
@@ -45,6 +45,8 @@ usage () {
45
45
  echo >&2 ""
46
46
  echo >&2 " --netem NETEM - tc qdisc netem OPTIONS (man 8 netem)"
47
47
  echo >&2 " --nat TARGET - Stateless NAT traffic to/from TARGET"
48
+ echo >&2 " (in primary/PID0 netns)"
49
+ echo >&2 ""
48
50
  exit 2
49
51
  }
50
52
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conlink",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "conlink - Declarative Low-Level Networking for Containers",
5
5
  "repository": "https://github.com/LonoCloud/conlink",
6
6
  "license": "SEE LICENSE IN LICENSE",
package/run-tests.sh CHANGED
@@ -29,37 +29,47 @@ dc_init() {
29
29
  done
30
30
  }
31
31
 
32
- dc_wait() {
33
- local tries="${1}" cont="${2}" try=1 svc= idx= result=
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; 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 [ "${VERBOSE}" ]; then
44
- vecho "Running: dc exec -T --index ${idx} ${svc} sh -c ${*}"
45
- dc exec -T --index ${idx} ${svc} sh -c "${*}" || result=$?
57
+ if [ "${WITH_DC}" ]; then
58
+ dc_run "${@}" || result=$?
46
59
  else
47
- dc exec -T --index ${idx} ${svc} sh -c "${*}" > /dev/null || result=$?
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
- 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}"]=$?
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 -c2 10.0.1.2 | tail -n1 | grep "max = 80\."'
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
@@ -45,6 +45,9 @@ properties:
45
45
  netem: {type: string}
46
46
  mode: {type: string}
47
47
  vlanid: {type: number}
48
+ forward:
49
+ type: array
50
+ items: {type: string, pattern: "^[0-9]{1,5}:[0-9]{1,5}/(tcp|udp)$"}
48
51
 
49
52
  bridges:
50
53
  type: array
@@ -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
- "Resolve bridge name to full bridge map.
100
- Add default values to a link:
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: 9000 (for non *vlan type)
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 vlanid]} bridges]
106
- (let [type (keyword (or type "veth"))
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 (get link :dev "eth0")
121
+ :dev dev
115
122
  :base base}
116
123
  (when bridge
117
- {:bridge (get bridges bridge)})
124
+ {:bridge bridge})
118
125
  (when (not (VLAN-TYPES type))
119
- {:mtu (get link :mtu 9000)}))]
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
- "If bridge mode is :auto then return :ovs if the 'openvswitch' kernel module
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
- (merge link (when mac {:mac mac}) (when ip {:ip ip}))))
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
- "If eth0 exists, then rename it to DOCKER-ETH0 to prevent 'RTNETLINK
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 "[ -d /sys/class/net/eth0 ]" {:quiet true})]
317
- (if (not= 0 (:code res))
318
- (log "No eth0 docker network interface detected")
319
- (P/let [_ (log "Renaming eth0 to DOCKER-ETH0")
320
- res (run* [(str "ip route save dev eth0 > /tmp/routesave")
321
- (str "ip link set eth0 down")
322
- (str "ip link set eth0 name DOCKER-ETH0")
323
- (str "ip link set DOCKER-ETH0 up")
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 container dev-id]} link
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 self-cid
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))