conlink 2.0.3 → 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/.github/workflows/push.yml +9 -2
- package/Dockerfile +2 -2
- package/README.md +135 -29
- package/examples/test10-compose.yaml +38 -0
- package/examples/test4-multiple/modes/web/compose.yaml +5 -0
- package/examples/test7-compose.yaml +4 -1
- package/examples/test9-compose.yaml +32 -0
- package/link-add.sh +2 -0
- package/link-forward.sh +45 -0
- package/link-mirred.sh +114 -0
- package/mdc +1 -0
- package/package.json +1 -1
- package/run-tests.sh +219 -0
- package/schema.yaml +10 -0
- package/src/conlink/core.cljs +274 -144
package/src/conlink/core.cljs
CHANGED
|
@@ -27,9 +27,11 @@ 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
|
|
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,11 +59,14 @@ 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
|
-
(def ctx (atom {:error
|
|
62
|
-
:warn
|
|
63
|
-
:log
|
|
64
|
-
:info
|
|
64
|
+
(def ctx (atom {:error #(apply Eprintln "ERROR:" %&)
|
|
65
|
+
:warn #(apply Eprintln "WARNING:" %&)
|
|
66
|
+
:log Eprintln
|
|
67
|
+
:info #(identity nil)
|
|
68
|
+
:kmod-ovs? false
|
|
69
|
+
:kmod-mirred? false}))
|
|
65
70
|
|
|
66
71
|
;; Simple utility functions
|
|
67
72
|
(defn json-str [obj]
|
|
@@ -94,34 +99,95 @@ General Options:
|
|
|
94
99
|
net-cfg))
|
|
95
100
|
|
|
96
101
|
(defn enrich-link
|
|
97
|
-
"
|
|
102
|
+
"Check and enrich link config
|
|
103
|
+
- Resolve bridge name to full bridge map.
|
|
104
|
+
- Add default values to a link:
|
|
98
105
|
- type: veth
|
|
99
106
|
- dev: eth0
|
|
100
|
-
- mtu:
|
|
107
|
+
- mtu: --default-mtu (for non *vlan type)
|
|
101
108
|
- base: :conlink for veth type, :host for *vlan types, :local otherwise"
|
|
102
|
-
[{:as link :keys [type base bridge ip
|
|
103
|
-
(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")
|
|
104
113
|
base-default (cond (= :veth type) :conlink
|
|
105
114
|
(VLAN-TYPES type) :host
|
|
106
115
|
:else :local)
|
|
107
116
|
base (get link :base base-default)
|
|
117
|
+
bridge (get bridges bridge)
|
|
108
118
|
link (merge
|
|
109
119
|
link
|
|
110
120
|
{:type type
|
|
111
|
-
:dev
|
|
121
|
+
:dev dev
|
|
112
122
|
:base base}
|
|
123
|
+
(when bridge
|
|
124
|
+
{:bridge bridge})
|
|
113
125
|
(when (not (VLAN-TYPES type))
|
|
114
|
-
{: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")))))
|
|
115
143
|
link))
|
|
116
144
|
|
|
145
|
+
(defn enrich-bridge
|
|
146
|
+
"Check and enrich bridge config.
|
|
147
|
+
If bridge mode is :auto then return :ovs if the 'openvswitch' kernel module
|
|
148
|
+
is loaded otherwise fall back to :linux. Exit with an error if mode is :ovs
|
|
149
|
+
or :patch and the 'openvswitch' or 'act_mirred' kernel modules are not
|
|
150
|
+
loaded respectively."
|
|
151
|
+
[{:as bridge-opts :keys [bridge mode]}]
|
|
152
|
+
(let [{:keys [warn default-bridge-mode kmod-ovs? kmod-mirred?]} @ctx
|
|
153
|
+
mode (keyword (or mode default-bridge-mode))
|
|
154
|
+
_ (when (and (= :ovs mode) (not kmod-ovs?))
|
|
155
|
+
(fatal 1 (str "bridge " bridge " mode is 'ovs', "
|
|
156
|
+
"but no 'openvswitch' kernel module loaded")))
|
|
157
|
+
_ (when (and (= :patch mode) (not kmod-mirred?))
|
|
158
|
+
(warn (str "bridge " bridge " mode is 'patch', "
|
|
159
|
+
"but no 'act_mirred' kernel module loaded, "
|
|
160
|
+
" assuming it will load when needed.")))
|
|
161
|
+
_ (when (and (= :auto mode) (not kmod-ovs?))
|
|
162
|
+
(warn (str "bridge " bridge " mode is 'auto', "
|
|
163
|
+
" but no 'openvswitch' kernel module loaded, "
|
|
164
|
+
" so falling back to 'linux'")))
|
|
165
|
+
mode (if (= :auto mode)
|
|
166
|
+
(if kmod-ovs? :ovs :linux)
|
|
167
|
+
mode)]
|
|
168
|
+
(assoc bridge-opts :mode mode)))
|
|
169
|
+
|
|
117
170
|
(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
|
-
|
|
171
|
+
"Validate and update each bridge (enrich-bridge) and link (enrich-link) and
|
|
172
|
+
add :bridges, :containers, and :services maps with restructured bridge, link,
|
|
173
|
+
and command configuration to provide a more efficient structure for looking
|
|
174
|
+
up configuration later."
|
|
175
|
+
[{:as cfg :keys [links commands bridges]}]
|
|
176
|
+
(let [bridge-map (reduce (fn [acc b] (assoc acc (:bridge b) b))
|
|
177
|
+
{} bridges)
|
|
178
|
+
;; Add bridges specified in links only
|
|
179
|
+
all-bridges (reduce (fn [bs b]
|
|
180
|
+
(assoc bs b (get bs b {:bridge b})))
|
|
181
|
+
bridge-map
|
|
182
|
+
(keep :bridge links))
|
|
183
|
+
;; Enrich each bridge
|
|
184
|
+
bridges (reduce (fn [bs [k v]] (assoc bs k (enrich-bridge v)))
|
|
185
|
+
{} all-bridges)
|
|
186
|
+
links (mapv #(enrich-link % bridges) links)
|
|
187
|
+
cfg (merge cfg {:links links
|
|
188
|
+
:bridges bridges
|
|
189
|
+
:containers {}
|
|
190
|
+
:services {}})
|
|
125
191
|
rfn (fn [kind cfg {:as x :keys [container service]}]
|
|
126
192
|
(cond-> cfg
|
|
127
193
|
container (update-in [:containers container kind] conjv x)
|
|
@@ -153,15 +219,17 @@ General Options:
|
|
|
153
219
|
"\nUser config:\n" (indent-pprint-str data " "))
|
|
154
220
|
"\nValidation errors:\n" msg))))))
|
|
155
221
|
|
|
222
|
+
|
|
223
|
+
;;; Runtime state related
|
|
224
|
+
|
|
156
225
|
(defn gen-network-state
|
|
157
226
|
"Generate network state/context from network configuration. Adds
|
|
158
227
|
empty :devices map and :bridges map containing nil status for
|
|
159
228
|
each bridge mentioned in the network config :links and :tunnels."
|
|
160
|
-
[{:keys [links tunnels]}]
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
(keep :bridge (concat links tunnels))))
|
|
229
|
+
[{:keys [links tunnels bridges]}]
|
|
230
|
+
{:devices {}
|
|
231
|
+
:bridges (into {} (for [[k v] bridges]
|
|
232
|
+
[k (merge v {:status nil :links #{}})]))})
|
|
165
233
|
|
|
166
234
|
(defn link-outer-dev
|
|
167
235
|
"outer-dev format:
|
|
@@ -179,7 +247,7 @@ General Options:
|
|
|
179
247
|
"Add offset value to ip and mac keys in a link definition to account
|
|
180
248
|
for multiple instances of that link i.e. a compose service with
|
|
181
249
|
multiple replicas (scale >= 2)."
|
|
182
|
-
[{:as link :keys [ip mac]} offset]
|
|
250
|
+
[{:as link :keys [ip mac forward]} offset]
|
|
183
251
|
;; TODO: add vlanid
|
|
184
252
|
(let [mac (when mac
|
|
185
253
|
(addrs/int->mac
|
|
@@ -188,8 +256,15 @@ General Options:
|
|
|
188
256
|
(let [[ip prefix] (S/split ip #"/")]
|
|
189
257
|
(str (addrs/int->ip
|
|
190
258
|
(+ offset (addrs/ip->int ip)))
|
|
191
|
-
"/" prefix)))
|
|
192
|
-
|
|
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}))))
|
|
193
268
|
|
|
194
269
|
|
|
195
270
|
(defn link-instance-enrich
|
|
@@ -261,26 +336,36 @@ General Options:
|
|
|
261
336
|
(P/recur cmds)
|
|
262
337
|
res))))
|
|
263
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
|
+
|
|
264
353
|
(defn rename-docker-eth0
|
|
265
|
-
"
|
|
354
|
+
"Rename docker's provided eth0 to DOCKER-INTF to prevent 'RTNETLINK
|
|
266
355
|
answers: File exists' errors during creation of links that use
|
|
267
356
|
'eth0' device name. This is necessary because even if the netns is
|
|
268
357
|
specified with the same link create command, the creation and move
|
|
269
358
|
does not appear to be idempotent and results in the conflict."
|
|
270
359
|
[]
|
|
271
360
|
(P/let [{:keys [log]} @ctx
|
|
272
|
-
res (run
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
(str "ip route restore < /tmp/routesave")]
|
|
281
|
-
{:id "rename"})]
|
|
282
|
-
(when (not= 0 (:code res))
|
|
283
|
-
(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"))))
|
|
284
369
|
|
|
285
370
|
(defn start-ovs
|
|
286
371
|
"Start and initialize the openvswitch daemons. Exit with error if it
|
|
@@ -291,88 +376,127 @@ General Options:
|
|
|
291
376
|
(fatal 1 (str "Failed starting OVS: " (:stderr res)))
|
|
292
377
|
res)))
|
|
293
378
|
|
|
294
|
-
|
|
295
|
-
"Return whether kernel module 'kmod' is loaded."
|
|
296
|
-
[kmod]
|
|
297
|
-
(P/let [cmd (str "grep -o '^" kmod "\\>' /proc/modules")
|
|
298
|
-
res (run cmd {:quiet true})]
|
|
299
|
-
(and (= 0 (:code res)) (= kmod (trim (:stdout res))))))
|
|
300
|
-
|
|
301
|
-
;;; Link and bridge commands
|
|
379
|
+
;;; Bridge commands
|
|
302
380
|
|
|
303
381
|
(defn check-no-bridge
|
|
304
382
|
"Check that no bridge named 'bridge' is currently configured.
|
|
305
|
-
Bridge type is dependent on
|
|
383
|
+
Bridge type is dependent on mode (:ovs or :linux). Exit with
|
|
306
384
|
error if the bridge already exists."
|
|
307
|
-
[bridge]
|
|
308
|
-
(P/let [{:keys [info
|
|
385
|
+
[{:keys [bridge mode]}]
|
|
386
|
+
(P/let [{:keys [info]} @ctx
|
|
309
387
|
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
|
-
|
|
388
|
+
:linux (str "ip link show type bridge " bridge)
|
|
389
|
+
:patch nil}
|
|
390
|
+
mode)]
|
|
391
|
+
(if (not cmd)
|
|
392
|
+
true
|
|
393
|
+
(P/let [res (run cmd {:quiet true})]
|
|
394
|
+
(if (= 0 (:code res))
|
|
395
|
+
;; TODO: maybe mark as :exists and use without cleanup
|
|
396
|
+
(fatal 1 (str "Bridge " bridge " already exists"))
|
|
397
|
+
(if (re-seq #"(does not exist|no bridge named)" (:stderr res))
|
|
398
|
+
true
|
|
399
|
+
(fatal 1 (str "Unable to run '" cmd "': " (:stderr res)))))))))
|
|
319
400
|
|
|
320
401
|
|
|
321
402
|
(defn bridge-create
|
|
322
403
|
"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)
|
|
404
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
405
|
+
[{:keys [bridge mode]}]
|
|
406
|
+
(P/let [{:keys [info error]} @ctx
|
|
327
407
|
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
|
-
|
|
408
|
+
:linux (str "ip link add " bridge " up type bridge")
|
|
409
|
+
:patch nil}
|
|
410
|
+
mode)]
|
|
411
|
+
(if (not cmd)
|
|
412
|
+
(info (str "Ignoring bridge/switch " bridge " for mode " mode))
|
|
413
|
+
(P/let [_ (info "Creating bridge/switch" bridge)
|
|
414
|
+
res (run* [cmd (str "ip link set " bridge " up")])]
|
|
415
|
+
(if (not= 0 (:code res))
|
|
416
|
+
(error (str "Unable to create bridge/switch " bridge))
|
|
417
|
+
(swap! ctx assoc-in [:network-state :bridges bridge :status] :created))
|
|
418
|
+
true))))
|
|
335
419
|
|
|
336
420
|
(defn bridge-del
|
|
337
421
|
"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)
|
|
422
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
423
|
+
[{:keys [bridge mode]}]
|
|
424
|
+
(P/let [{:keys [info error]} @ctx
|
|
342
425
|
cmd (get {:ovs (str "ovs-vsctl del-br " bridge)
|
|
343
|
-
:linux (str "ip link del " bridge)
|
|
344
|
-
|
|
345
|
-
(if (not
|
|
346
|
-
(
|
|
347
|
-
(
|
|
348
|
-
|
|
426
|
+
:linux (str "ip link del " bridge)
|
|
427
|
+
:patch nil} mode)]
|
|
428
|
+
(if (not cmd)
|
|
429
|
+
(info (str "Ignoring bridge/switch " bridge " for mode " mode))
|
|
430
|
+
(P/let [_ (info "Deleting bridge/switch" bridge)
|
|
431
|
+
res (run cmd)]
|
|
432
|
+
(if (not= 0 (:code res))
|
|
433
|
+
(error (str "Unable to delete bridge " bridge))
|
|
434
|
+
(swap! ctx assoc-in [:network-state :bridges bridge :status] nil))
|
|
435
|
+
true))))
|
|
349
436
|
|
|
350
437
|
(defn bridge-add-link
|
|
351
438
|
"Add the link/interface 'dev' to the bridge 'bridge'.
|
|
352
|
-
Bridge type is dependent on
|
|
353
|
-
[bridge dev]
|
|
354
|
-
(P/let [{:keys [error
|
|
439
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
440
|
+
[{:keys [bridge mode]} dev]
|
|
441
|
+
(P/let [{:keys [error]} @ctx
|
|
355
442
|
cmd (get {:ovs (str "ovs-vsctl add-port " bridge " " dev)
|
|
356
443
|
:linux (str "ip link set dev " dev " master " bridge)}
|
|
357
|
-
|
|
444
|
+
mode)
|
|
358
445
|
res (run cmd)]
|
|
359
|
-
(
|
|
360
|
-
(
|
|
361
|
-
|
|
446
|
+
(if (= 0 (:code res))
|
|
447
|
+
(swap! ctx update-in [:network-state :bridges bridge :links] conj dev)
|
|
448
|
+
(error (str "Unable to add link " dev " into " bridge)))))
|
|
362
449
|
|
|
363
450
|
(defn bridge-drop-link
|
|
364
451
|
"Remove the link/interface 'dev' from the bridge 'bridge'.
|
|
365
|
-
Bridge type is dependent on
|
|
366
|
-
[bridge dev]
|
|
367
|
-
(P/let [{:keys [error
|
|
452
|
+
Bridge type is dependent on mode (:ovs or :linux)."
|
|
453
|
+
[{:keys [bridge mode]} dev]
|
|
454
|
+
(P/let [{:keys [error]} @ctx
|
|
368
455
|
cmd (get {:ovs (str "ovs-vsctl del-port " bridge " " dev)
|
|
369
456
|
:linux (str "ip link set dev " dev " nomaster")}
|
|
370
|
-
|
|
457
|
+
mode)
|
|
371
458
|
res (run cmd)]
|
|
372
|
-
(
|
|
373
|
-
(
|
|
374
|
-
|
|
459
|
+
(if (= 0 (:code res))
|
|
460
|
+
(swap! ctx update-in [:network-state :bridges bridge :links] disj dev)
|
|
461
|
+
(error (str "Unable to drop link " dev " from " bridge)))))
|
|
462
|
+
|
|
463
|
+
(defn patch-add-link
|
|
464
|
+
"Setup patch between 'dev' and its peer link using tc qdisc mirred
|
|
465
|
+
filter action. Peer links are tracked in pseudo-bridge 'bridge'."
|
|
466
|
+
[{:keys [bridge mode]} dev]
|
|
467
|
+
(let [{:keys [info error]} @ctx
|
|
468
|
+
links-path [:network-state :bridges bridge :links]
|
|
469
|
+
links (get-in @ctx links-path)
|
|
470
|
+
peers (disj links dev)]
|
|
471
|
+
(condp = (count peers)
|
|
472
|
+
0
|
|
473
|
+
(P/do
|
|
474
|
+
(info (str "Registering first peer link "
|
|
475
|
+
dev " in :patch 'bridge' " bridge))
|
|
476
|
+
(swap! ctx update-in links-path conj dev))
|
|
477
|
+
|
|
478
|
+
1
|
|
479
|
+
(P/let [cmd (str "link-mirred.sh " dev " " (first peers))
|
|
480
|
+
res (run cmd)]
|
|
481
|
+
(if (= 0 (:code res))
|
|
482
|
+
(swap! ctx update-in links-path conj dev)
|
|
483
|
+
(error (str "Failed to setup tc filter action for "
|
|
484
|
+
dev " in :patch 'bridge' " bridge))))
|
|
485
|
+
|
|
486
|
+
(error "Cannot add third peer link "
|
|
487
|
+
dev " to :patch 'bridge' " bridge))))
|
|
375
488
|
|
|
489
|
+
(defn patch-drop-link
|
|
490
|
+
"Remove tracking of 'dev' from pseudo-bridge 'bridge'."
|
|
491
|
+
[{:keys [bridge mode]} dev]
|
|
492
|
+
(let [{:keys [info error]} @ctx
|
|
493
|
+
links-path [:network-state :bridges bridge :links]]
|
|
494
|
+
(info (str "Removing peer link "
|
|
495
|
+
dev " from :patch 'bridge' " bridge))
|
|
496
|
+
;; State is in the links, no extra cleanup
|
|
497
|
+
(swap! ctx update-in links-path conj dev)))
|
|
498
|
+
|
|
499
|
+
;;; Link commands
|
|
376
500
|
|
|
377
501
|
(defn link-add
|
|
378
502
|
"Create a link/interface defined by 'link' in a container by calling
|
|
@@ -380,7 +504,7 @@ General Options:
|
|
|
380
504
|
line arguments from the 'link' definition and reports the results."
|
|
381
505
|
[link]
|
|
382
506
|
(P/let [{:keys [error]} @ctx
|
|
383
|
-
{:keys [type dev outer-dev pid outer-pid
|
|
507
|
+
{:keys [type dev outer-dev pid outer-pid dev-id]} link
|
|
384
508
|
cmd (str "link-add.sh"
|
|
385
509
|
" '" (name type) "' '" pid "' '" dev "'"
|
|
386
510
|
(when outer-pid (str " --pid1 " outer-pid))
|
|
@@ -409,6 +533,26 @@ General Options:
|
|
|
409
533
|
(error (str "Unable to delete " dev-id ": " (:stderr res)))))
|
|
410
534
|
res))
|
|
411
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
|
+
|
|
412
556
|
|
|
413
557
|
;;; docker/docker-compose utilities
|
|
414
558
|
|
|
@@ -507,7 +651,7 @@ General Options:
|
|
|
507
651
|
[link action]
|
|
508
652
|
(P/let
|
|
509
653
|
[{:keys [error log]} @ctx
|
|
510
|
-
{:keys [type outer-dev bridge dev-id]} link
|
|
654
|
+
{:keys [type outer-dev bridge dev-id forward]} link
|
|
511
655
|
status-path [:network-state :devices dev-id :status]
|
|
512
656
|
link-status (get-in @ctx status-path)]
|
|
513
657
|
(log (str (get {"start" "Creating" "die" "Deleting"} action)
|
|
@@ -519,7 +663,11 @@ General Options:
|
|
|
519
663
|
(P/do
|
|
520
664
|
(swap! ctx assoc-in status-path :creating)
|
|
521
665
|
(link-add link)
|
|
522
|
-
(when bridge
|
|
666
|
+
(when bridge
|
|
667
|
+
(if (= :patch (:mode bridge))
|
|
668
|
+
(patch-add-link bridge outer-dev)
|
|
669
|
+
(bridge-add-link bridge outer-dev)))
|
|
670
|
+
(when forward (forward-modify link "add"))
|
|
523
671
|
(swap! ctx assoc-in status-path :created)))
|
|
524
672
|
|
|
525
673
|
"die"
|
|
@@ -527,7 +675,11 @@ General Options:
|
|
|
527
675
|
(error (str "Link " dev-id " does not exist"))
|
|
528
676
|
(P/do
|
|
529
677
|
(swap! ctx assoc-in status-path :deleting)
|
|
530
|
-
(when
|
|
678
|
+
(when forward (forward-modify link "del"))
|
|
679
|
+
(when bridge
|
|
680
|
+
(if (= :patch (:mode bridge))
|
|
681
|
+
(patch-drop-link bridge outer-dev)
|
|
682
|
+
(bridge-drop-link bridge outer-dev)))
|
|
531
683
|
(link-del link)
|
|
532
684
|
(swap! ctx assoc-in status-path nil))))))
|
|
533
685
|
|
|
@@ -635,7 +787,7 @@ General Options:
|
|
|
635
787
|
(when (seq bridges)
|
|
636
788
|
(P/do
|
|
637
789
|
(log (str "Removing bridges: " (S/join ", " (keys bridges))))
|
|
638
|
-
(P/all (map bridge-del (
|
|
790
|
+
(P/all (map bridge-del (vals bridges)))))
|
|
639
791
|
(js/process.exit 127))))
|
|
640
792
|
|
|
641
793
|
|
|
@@ -650,40 +802,6 @@ General Options:
|
|
|
650
802
|
(when (empty? config-schema)
|
|
651
803
|
(fatal 2 "Could not find config-schema" orig-config-schema)))
|
|
652
804
|
|
|
653
|
-
(defn startup-checks
|
|
654
|
-
"Check startup state and return map of :bridge-mode, :docker, and
|
|
655
|
-
:podman. If bridge-mode is :auto then return :ovs if the
|
|
656
|
-
'openvswitch' kernel module is loaded otherwise fall back to :linux.
|
|
657
|
-
Exit with an error if bridge-mode is :ovs and the 'openvswitch'
|
|
658
|
-
kernel module is not loaded or if neither a docker or podman
|
|
659
|
-
connection could be established."
|
|
660
|
-
[{:keys [bridge-mode docker-socket podman-socket]}]
|
|
661
|
-
(P/let
|
|
662
|
-
[{:keys [info warn]} @ctx
|
|
663
|
-
ovs? (kmod-loaded? "openvswitch")
|
|
664
|
-
bridge-mode (condp = [bridge-mode ovs?]
|
|
665
|
-
[:auto true]
|
|
666
|
-
:ovs
|
|
667
|
-
|
|
668
|
-
[:auto false]
|
|
669
|
-
(do
|
|
670
|
-
(warn (str "bridge-mode is 'auto' but no 'openvswitch' "
|
|
671
|
-
"kernel module loaded, so using 'linux'"))
|
|
672
|
-
:linux)
|
|
673
|
-
|
|
674
|
-
[:ovs false]
|
|
675
|
-
(fatal 1 (str "bridge-mode is 'ovs', but no 'openvswitch' "
|
|
676
|
-
"kernel module loaded"))
|
|
677
|
-
|
|
678
|
-
bridge-mode)
|
|
679
|
-
docker (docker-client docker-socket)
|
|
680
|
-
podman (docker-client podman-socket)]
|
|
681
|
-
(when (and (not docker) (not podman))
|
|
682
|
-
(fatal 1 "Failed to start either docker or podman client/listener"))
|
|
683
|
-
{:bridge-mode bridge-mode
|
|
684
|
-
:docker docker
|
|
685
|
-
:podman podman}))
|
|
686
|
-
|
|
687
805
|
(defn server
|
|
688
806
|
"Process:
|
|
689
807
|
- parse/validate command line options
|
|
@@ -692,7 +810,7 @@ General Options:
|
|
|
692
810
|
- determine our own container ID and compose properties (if any)
|
|
693
811
|
- generate runtime network state and other process context/state
|
|
694
812
|
- install exit/cleanup handlers
|
|
695
|
-
- start/init openvswitch daemons/config (if :ovs
|
|
813
|
+
- start/init openvswitch daemons/config (if any bridges use :ovs mode)
|
|
696
814
|
- check that any defined bridges do not already exist
|
|
697
815
|
- create any bridges defined in network config links
|
|
698
816
|
- start listening/handling docker/podman container events
|
|
@@ -704,7 +822,7 @@ General Options:
|
|
|
704
822
|
{:keys [log info]} (swap! ctx merge (when verbose {:info Eprintln}))
|
|
705
823
|
opts (merge
|
|
706
824
|
opts
|
|
707
|
-
{:bridge-mode (keyword (:bridge-mode opts))
|
|
825
|
+
{:default-bridge-mode (keyword (:default-bridge-mode opts))
|
|
708
826
|
:orig-config-schema (:config-schema opts)
|
|
709
827
|
:config-schema (resolve-path (:config-schema opts) SCHEMA-PATHS)
|
|
710
828
|
:network-file (mapcat #(S/split % #":") (:network-file opts))
|
|
@@ -715,7 +833,16 @@ General Options:
|
|
|
715
833
|
{:keys [network-file compose-file compose-project]} opts
|
|
716
834
|
env (js->clj (js/Object.assign #js {} js/process.env))
|
|
717
835
|
self-pid js/process.pid
|
|
836
|
+
self-cid (get-container-id)
|
|
718
837
|
schema (load-config (:config-schema opts))
|
|
838
|
+
kmod-ovs? (kmod-loaded? "openvswitch")
|
|
839
|
+
kmod-mirred? (kmod-loaded? "act_mirred")
|
|
840
|
+
docker-eth0? (and self-cid (intf-exists? "eth0"))
|
|
841
|
+
_ (swap! ctx merge {:default-bridge-mode (:default-bridge-mode opts)
|
|
842
|
+
:default-mtu (:default-mtu opts)
|
|
843
|
+
:kmod-ovs? kmod-ovs?
|
|
844
|
+
:kmod-mirred? kmod-mirred?
|
|
845
|
+
:docker-eth0? docker-eth0?})
|
|
719
846
|
network-config (P/-> (load-configs compose-file network-file)
|
|
720
847
|
(interpolate-walk env)
|
|
721
848
|
(check-schema schema verbose)
|
|
@@ -724,8 +851,11 @@ General Options:
|
|
|
724
851
|
(println (js/JSON.stringify (->js network-config)))
|
|
725
852
|
(js/process.exit 0))
|
|
726
853
|
|
|
727
|
-
|
|
728
|
-
|
|
854
|
+
docker (docker-client (:docker-socket opts))
|
|
855
|
+
podman (docker-client (:podman-socket opts))
|
|
856
|
+
_ (when (and (not docker) (not podman))
|
|
857
|
+
(fatal 1 "Failed to start either docker or podman client/listener"))
|
|
858
|
+
|
|
729
859
|
self-container-obj (when self-cid
|
|
730
860
|
(get-container (or docker podman) self-cid))
|
|
731
861
|
self-container (inspect-container self-container-obj)
|
|
@@ -733,8 +863,7 @@ General Options:
|
|
|
733
863
|
{:project compose-project}
|
|
734
864
|
(get-compose-labels self-container))
|
|
735
865
|
network-state (gen-network-state network-config)
|
|
736
|
-
ctx-data {:
|
|
737
|
-
:network-config network-config
|
|
866
|
+
ctx-data {:network-config network-config
|
|
738
867
|
:network-state network-state
|
|
739
868
|
:compose-opts compose-opts
|
|
740
869
|
:docker docker
|
|
@@ -749,7 +878,6 @@ General Options:
|
|
|
749
878
|
(js/process.on "SIGTERM" #(exit-handler % "signal"))
|
|
750
879
|
(js/process.on "uncaughtException" #(exit-handler %1 %2))
|
|
751
880
|
|
|
752
|
-
(log "Bridge mode:" (name bridge-mode))
|
|
753
881
|
(log (str "Using schema at '" (:config-schema opts) "'"))
|
|
754
882
|
(info (str "Starting network config\n"
|
|
755
883
|
(indent-pprint-str network-config " ")))
|
|
@@ -761,18 +889,20 @@ General Options:
|
|
|
761
889
|
(info "Detected compose context:" compose-project))
|
|
762
890
|
|
|
763
891
|
(P/do
|
|
764
|
-
(when
|
|
892
|
+
(when docker-eth0?
|
|
893
|
+
(log "Renaming eth0 to" DOCKER-INTF)
|
|
765
894
|
(rename-docker-eth0))
|
|
766
895
|
|
|
767
|
-
(when (= :ovs
|
|
896
|
+
(when (some #(= :ovs (:mode %)) (-> network-config :bridges vals))
|
|
768
897
|
(start-ovs))
|
|
769
898
|
|
|
770
899
|
;; Check that bridges/switches do not already exist
|
|
771
|
-
(P/all (for [bridge (
|
|
900
|
+
(P/all (for [bridge (vals (:bridges network-state))]
|
|
772
901
|
(check-no-bridge bridge)))
|
|
902
|
+
|
|
773
903
|
;; Create bridges/switch configs
|
|
774
904
|
;; TODO: should be done on-demand
|
|
775
|
-
(P/all (for [bridge (
|
|
905
|
+
(P/all (for [bridge (vals (:bridges network-state))]
|
|
776
906
|
(bridge-create bridge)))
|
|
777
907
|
|
|
778
908
|
;; Create tunnels configs
|