conlink 2.0.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.
Files changed (59) hide show
  1. package/.dockerignore +5 -0
  2. package/Dockerfile +34 -0
  3. package/LICENSE +373 -0
  4. package/README.md +485 -0
  5. package/TODO +34 -0
  6. package/conlink +11 -0
  7. package/conlink-start.sh +172 -0
  8. package/examples/dot.js +36 -0
  9. package/examples/index.html +11 -0
  10. package/examples/net2dot.yaml +21 -0
  11. package/examples/test1-compose.yaml +60 -0
  12. package/examples/test2-compose.yaml +31 -0
  13. package/examples/test2-network.yaml +5 -0
  14. package/examples/test3-network.yaml +5 -0
  15. package/examples/test4-multiple/all-compose.yaml +5 -0
  16. package/examples/test4-multiple/base-compose.yaml +25 -0
  17. package/examples/test4-multiple/node1-compose.yaml +17 -0
  18. package/examples/test4-multiple/nodes2-compose.yaml +20 -0
  19. package/examples/test4-multiple/web-network.yaml +2 -0
  20. package/examples/test5-geneve-compose.yaml +31 -0
  21. package/examples/test6-cfn.yaml +184 -0
  22. package/examples/test7-compose.yaml +31 -0
  23. package/examples/test8-compose.yaml +35 -0
  24. package/host-build.yaml +1 -0
  25. package/inspect.json +210 -0
  26. package/link-add.sh +197 -0
  27. package/link-del.sh +60 -0
  28. package/net2dot +11 -0
  29. package/notes.txt +82 -0
  30. package/old/Dockerfile.bak +26 -0
  31. package/old/add-link.sh +82 -0
  32. package/old/conlink +12 -0
  33. package/old/conlink.cljs +131 -0
  34. package/old/dot_gitignore +1 -0
  35. package/old/examples/test2-compose.yaml +32 -0
  36. package/old/examples/test2-network.yaml +42 -0
  37. package/old/move-link.sh +108 -0
  38. package/old/net2dot.py +122 -0
  39. package/old/notes-old.txt +97 -0
  40. package/old/package.json +16 -0
  41. package/old/schema.yaml +138 -0
  42. package/old/schema.yaml.bak +76 -0
  43. package/old/test2b-compose.yaml +18 -0
  44. package/old/veth-link.sh +96 -0
  45. package/package.json +15 -0
  46. package/schema-ish.yaml +29 -0
  47. package/schema.yaml +71 -0
  48. package/shadow-cljs.edn +33 -0
  49. package/src/conlink/addrs.cljc +63 -0
  50. package/src/conlink/core.cljs +772 -0
  51. package/src/conlink/net2dot.cljs +158 -0
  52. package/src/conlink/util.cljs +140 -0
  53. package/tests/invalid-schema-1.yaml +6 -0
  54. package/tests/invalid-schema-2.yaml +6 -0
  55. package/tests/invalid-schema-3.yaml +17 -0
  56. package/tests/invalid-schema-4.yaml +14 -0
  57. package/tests/invalid-schema-5.yaml +12 -0
  58. package/tests/invalid-schema-6.yaml +12 -0
  59. package/tmp/conlink/.env +1 -0
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env nbb
2
+
3
+ (ns conlink
4
+ (:require [clojure.string :as S]
5
+ [promesa.core :as P]
6
+ [cljs-bean.core :refer [->clj]]
7
+ [conlink.util :refer [parse-opts Eprintln Epprint
8
+ fatal spawn exec read-file]]
9
+ ["yaml$default" :as yaml]
10
+ ["dockerode$default" :as Docker]))
11
+
12
+ (def usage "
13
+ conlink: advanced container linking (networking).
14
+
15
+ Usage:
16
+ conlink [options]
17
+
18
+ Options:
19
+ -v, --verbose Show verbose output (stderr)
20
+ [env: VERBOSE]
21
+ --project PROJECT Docker compose project name
22
+ [env: COMPOSE_PROJECT_NAME]
23
+ --network-file NETWORK-FILE... Network configuration file
24
+ --compose-file COMPOSE-FILE... Docker compose file
25
+ ")
26
+
27
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
28
+
29
+ (defn json-str [obj]
30
+ (js/JSON.stringify (clj->js obj)))
31
+
32
+ (defn mangle-network-config
33
+ "
34
+ - Rewrite network config to use actual container names instead of
35
+ the convenience aliases.
36
+ - Prune links, interfaces, and commands based on enabled service
37
+ profiles.
38
+ "
39
+ [net-cfg prefix]
40
+ (let [links (reduce
41
+ (fn [links {:keys [left right] :as link}]
42
+ (let [lname (str prefix (:container left))
43
+ rname (str prefix (:container right))]
44
+ (conj links
45
+ {:left (assoc left :container lname)
46
+ :right (assoc right :container rname)})))
47
+ [] (:links net-cfg))]
48
+ (assoc net-cfg :links links)))
49
+
50
+
51
+ (defn init-containers-state [net-cfg]
52
+ (let [state (into {} (for [l (:links net-cfg)
53
+ c [(:left l) (:right l)]]
54
+ [(:container c) {:state :unconnected :links []}]))]
55
+ (reduce
56
+ (fn [cfg {:keys [left right] :as link}]
57
+ (let [lfn (fn [me you] {:self (dissoc (me link) :container)
58
+ :remote (you link)})]
59
+ (-> cfg
60
+ (update-in [(:container left) :links] conj (lfn :left :right))
61
+ (update-in [(:container right) :links] conj (lfn :right :left)))))
62
+ state
63
+ (:links net-cfg)))
64
+
65
+ (defn handle-container [{:keys [client project config state] :as ctx} cid]
66
+ (P/let [container (.getContainer client cid)
67
+ details (P/-> container .inspect ->clj)
68
+ {Name :Name {Labels :Labels} :Config} details]
69
+ (Eprintln :handle-container :cid cid)
70
+ #_(Epprint details)
71
+ (Epprint (get @state Name))))
72
+
73
+ (P/let
74
+ [cfg (parse-opts usage *command-line-args*)
75
+ _ (when (empty? cfg) (js/process.exit 2))
76
+ {:keys [verbose project network-file compose-file]} cfg
77
+ _ (when verbose (Eprintln "Settings:"))
78
+ _ (when verbose (Epprint cfg))
79
+ _ (when (not (or network-file compose-file))
80
+ (fatal 2 "either --network-file or --compose-file is required"))
81
+
82
+ raw-network-config (P/-> (first network-file)
83
+ (read-file "utf8")
84
+ yaml/parse
85
+ ->clj)
86
+ docker (Docker. #js {:socketPath "/var/run/docker.sock"})
87
+
88
+ _ (Eprintln "raw-network-config:")
89
+ _ (Epprint raw-network-config)
90
+ prefix (if project (str "/" project "_") "/")
91
+ network-config (mangle-network-config raw-network-config prefix)
92
+ _ (Eprintln "network-config:")
93
+ _ (Epprint network-config)
94
+ state (init-containers-state network-config)
95
+ _ (Eprintln "state:")
96
+ _ (Epprint state)
97
+
98
+ ctx {:client docker
99
+ :project project
100
+ :config network-config
101
+ :state (atom state)}
102
+ label-filters (if project
103
+ {"label" [(str "com.docker.compose.project=" project)]}
104
+ {})
105
+ ev-filters (merge label-filters
106
+ {"event" ["start"]})
107
+ ev-stream (.getEvents docker #js {:filters (json-str ev-filters)})
108
+ _ (.on ev-stream "data" (fn [ev]
109
+ (let [event (-> ev js/JSON.parse ->clj)]
110
+ (prn :event event)
111
+ (handle-container ctx (:id event)))))
112
+
113
+ _ (Eprintln "Handling already running containers")
114
+ containers (P/-> docker
115
+ (.listContainers #js {:filters (json-str label-filters)
116
+ :sparse true})
117
+ ->clj)
118
+ #_#__ (Epprint containers)
119
+ _ (doseq [c containers] (handle-container ctx (:Id c)))
120
+
121
+ #_#_res (P/all [(spawn "sleep 1")
122
+ (spawn "ip link show")
123
+ (spawn "ip addr show")
124
+ (spawn "ip route show")])
125
+ #_#__ (prn :res res)
126
+ ]
127
+ (prn :here1)
128
+ )
129
+
130
+
131
+
@@ -0,0 +1 @@
1
+ *.swp
@@ -0,0 +1,32 @@
1
+ # A docker-compose file with an external network configuration file
2
+ # and two docker containers that are connected via a switch. In
3
+ # addition an "internet" host is setup in the network container that
4
+ # is also connected to the switch and is listening on 8.8.8.8.
5
+
6
+ version: "2.4"
7
+
8
+ services:
9
+ network:
10
+ build: {context: ../}
11
+ image: conlink.cljs
12
+ pid: host
13
+ network_mode: none
14
+ cap_add: [SYS_ADMIN, NET_ADMIN, SYS_NICE, NET_BROADCAST, IPC_LOCK]
15
+ security_opt: [ 'apparmor:unconfined' ] # needed on Ubuntu 18.04
16
+ volumes:
17
+ - /var/run/docker.sock:/var/run/docker.sock
18
+ - /var/lib/docker:/var/lib/docker
19
+ - ./:/test
20
+ command: /sbin/conlink -v --project ${COMPOSE_PROJECT_NAME:?required} --compose-file /test/test2-compose.yaml --network-file /test/test2-network.yaml
21
+
22
+ node1:
23
+ image: alpine
24
+ cap_add: [NET_ADMIN]
25
+ network_mode: none
26
+ command: sleep 864000
27
+
28
+ node2:
29
+ image: alpine
30
+ cap_add: [NET_ADMIN]
31
+ network_mode: none
32
+ command: sh -c 'while ! ip link show eth0 up; do sleep 1; done; sleep 864000'
@@ -0,0 +1,42 @@
1
+ # "Physical" network interface definitions.
2
+ # This defines container to container links.
3
+ links:
4
+ - left: {container: node1_1, intf: eth0, ip: 10.0.1.1/16}
5
+ right: {container: network_1, intf: node1-eth0}
6
+ - left: {container: node2_1, intf: eth0, ip: 10.0.1.2/16}
7
+ right: {container: network_1, intf: node2-eth0}
8
+
9
+ # Commands to run in containers after they are connected
10
+ commands:
11
+ - container: node1_1
12
+ command: ip route add default via 10.0.0.1
13
+ - container: node2_1
14
+ command: ip route add default via 10.0.0.1
15
+
16
+ # Network container mininet configuration
17
+ # This configuration is used by config_mininet.py inside the network
18
+ # container to define complex network elements (routers, switches,
19
+ # etc).
20
+ mininet-cfg:
21
+ switches:
22
+ - name: s1 # the switch between node1, node2, and internet
23
+ hosts:
24
+ - name: internet
25
+ opts: {ip: 10.0.0.1/16}
26
+ interfaces:
27
+ - name: node1-eth0
28
+ node: s1
29
+ - name: node2-eth0
30
+ node: s1
31
+ links:
32
+ - left: internet
33
+ right: s1
34
+
35
+ commands:
36
+ # In "internet" listen to 8.8.8.* addresses.
37
+ - node: internet
38
+ sync: true
39
+ command:
40
+ - ip link add ${DNS_INTF:-gdns} type dummy
41
+ - ip link set ${DNS_INTF:-gdns} up
42
+ - ip addr add 8.8.8.8/24 dev ${DNS_INTF:-gdns}
@@ -0,0 +1,108 @@
1
+ #!/bin/bash
2
+
3
+ # Copyright (c) 2023, Viasat, Inc
4
+ # Licensed under MPL 2.0
5
+
6
+ set -e
7
+
8
+ usage () {
9
+ echo >&2 "${0} [OPTIONS] TYPE INTF0 INTF1 PID0 PID1"
10
+ echo >&2 ""
11
+ echo >&2 " TYPE: one of the following values: move, vlan, macvlan,"
12
+ echo >&2 " macvtap, ipvlan, or ipvtap. If TYPE is 'move'"
13
+ echo >&2 " then INTF0 will be moved directly. For any other"
14
+ echo >&2 " TYPE a sub-interface of TYPE will be created on"
15
+ echo >&2 " INTF0 and the sub-interface virtual link will be"
16
+ echo >&2 " moved instead."
17
+ echo >&2 " INTF0: the interface in PID0 to move to PID1"
18
+ echo >&2 " INTF1: the interface in PID1 after moving"
19
+ echo >&2 " PID0: the process ID of the first namespace"
20
+ echo >&2 " PID1: the process ID of the second namespace"
21
+ echo >&2 ""
22
+ echo >&2 "OPTIONS:"
23
+ echo >&2 " --verbose - Verbose output (set -x)"
24
+ echo >&2 " --mode MODE - Mode setting for *vlan, *vtap TYPEs"
25
+ echo >&2 " --vlanid VLANID - VLAN ID for vlan TYPE"
26
+ echo >&2 " --ip IP - Add IP (CIDR) to the moved interface"
27
+ echo >&2 " --route 'ROUTE' - route to add to the moved interface"
28
+ echo >&2 " --nat TARGET - Stateless NAT traffic to/from TARGET"
29
+ exit 2
30
+ }
31
+
32
+ IPTABLES() {
33
+ local ns=${1}; shift
34
+ ip netns exec ${ns} iptables -D "${@}" 2>/dev/null || true
35
+ ip netns exec ${ns} iptables -I "${@}"
36
+ }
37
+
38
+ VERBOSE=${VERBOSE:-}
39
+ MODE= VLANID= IP= ROUTE= TARGET=
40
+
41
+ info() { echo "move-link [${PID0}/${IF0} ->> ${PID1}/${IF1}] ${*}"; }
42
+ warn() { >&2 echo "move-link [${PID0}/${IF0} ->> ${PID1}/${IF1}] ${*}"; }
43
+ die() { warn "ERROR: ${*}"; exit 1; }
44
+
45
+ # Parse arguments
46
+ positional=
47
+ while [ "${*}" ]; do
48
+ param=$1; OPTARG=$2
49
+ case ${param} in
50
+ --verbose) VERBOSE=1 ;;
51
+ --mode) MODE="${OPTARG}"; shift ;;
52
+ --vlanid) VLANID="${OPTARG}"; shift ;;
53
+ --ip) IP="${OPTARG}"; shift ;;
54
+ --route) ROUTE="${OPTARG}"; shift ;;
55
+ --nat) TARGET="${OPTARG}"; shift ;;
56
+ -h|--help) usage ;;
57
+ *) positional="${positional} $1" ;;
58
+ esac
59
+ shift
60
+ done
61
+ set -- ${positional}
62
+
63
+ TYPE=$1 IF0=$2 IF1=$3 PID0=$4 PID1=$5 NS0=ns${PID0} NS1=ns${PID1}
64
+
65
+ [ "${VERBOSE}" ] && set -x || true
66
+
67
+ # Check arguments
68
+ [ "${$#}" -lt 5 ] && usage
69
+ [ "${TARGET}" -a -z "${IP}" ] && die "--nat requires --ip"
70
+
71
+ # Sanity checks
72
+ [ ! -d /proc/$PID0 ] && die "PID0 $PID0 is no longer running!"
73
+ [ ! -d /proc/$PID1 ] && die "PID1 $PID1 is no longer running!"
74
+
75
+ export PATH=$PATH:/usr/sbin
76
+ mkdir -p /var/run/netns
77
+ ln -sf /proc/${PID0}/ns/net /var/run/netns/${NS0}
78
+ ln -sf /proc/${PID1}/ns/net /var/run/netns/${NS1}
79
+
80
+ info "Moving ${TYPE} link (${IP})"
81
+
82
+ case "${TYPE}" in
83
+ macvlan|macvtap|ipvlan|ipvtap|vlan)
84
+ ip -netns ${NS0} link add link ${IF0} name tmp$$ type ${TYPE} \
85
+ ${MODE:+mode ${MODE}} ${VLANID:+id ${VLANID}}
86
+ ip -netns ${NS0} link set tmp$$ netns ${NS1} name ${IF1}
87
+ ;;
88
+ move)
89
+ ip -netns ${NS0} link set ${IF0} netns ${NS1} name ${IF1}
90
+ ;;
91
+ esac
92
+
93
+ info "Setting IP, ROUTE, and up state"
94
+ ip -netns ${NS1} --force -b - <<EOF
95
+ ${IP:+addr add ${IP} dev ${IF1}}
96
+ link set ${IF1} up
97
+ ${ROUTE:+route add ${ROUTE} dev ${IF1}}
98
+ EOF
99
+
100
+ if [ "${TARGET}" ]; then
101
+ info "Adding NAT rule to ${TARGET}"
102
+ IPTABLES ${NS1} PREROUTING -t nat -i ${IF1} -j DNAT --to-destination ${TARGET}
103
+ IPTABLES ${NS1} POSTROUTING -t nat -o ${IF1} -j SNAT --to-source ${IP%/*}
104
+ fi
105
+
106
+ info "Moved ${TYPE} link (${IP})"
107
+
108
+ # /test/move-link.sh --verbose ipvlan enp6s0 host 1 2500144 --ip 192.168.88.32/24 --nat 10.0.1.2
package/old/net2dot.py ADDED
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env -S python3 -u
2
+
3
+ # Copyright (c) 2021, Viasat, Inc
4
+ # Licensed under MPL 2.0
5
+
6
+ import re, sys, yaml
7
+
8
+ NODE_PROPS = 'shape=box style=filled penwidth=1'
9
+ CONTAINER_PROPS = 'fontsize = 12 style = filled fillcolor = "#e1d5e7" color = "#9673a6"'
10
+ NETWORK_PROPS = '%s penwidth = 2' % CONTAINER_PROPS
11
+ INTF_PROPS = 'width=0.1 height=0.1 fontsize=10 fillcolor="#ffbb9e" color="#d7db00"'
12
+ SWITCH_PROPS = 'fontsize=12 style="rounded,filled" fillcolor="#dae8fc" color="#6c8ebf"'
13
+ HOST_PROPS = 'fontsize=12 fillcolor="#f5f5f5" color="#666666"'
14
+
15
+ def id(n):
16
+ return re.sub(r"-", "_", n)
17
+
18
+ def intfText(cluster, intf):
19
+ name = intf.get('intf', intf.get('origName', intf.get('name')))
20
+ if name.startswith('DUMMY_'):
21
+ return '%s__%s [shape=point style=invis]' % (
22
+ id(cluster), id(name))
23
+ else:
24
+ ip = intf.get('ip', intf.get('opts', {}).get('ip', ''))
25
+ if ip: ip = "\\n%s" % ip
26
+ return '%s__%s [label="%s%s" %s]' % (
27
+ id(cluster), id(name), name, ip, INTF_PROPS)
28
+
29
+ if __name__ == '__main__':
30
+
31
+ networkFile = sys.argv[1]
32
+ networkName = sys.argv[2]
33
+
34
+ netCfg = yaml.full_load(open(networkFile))
35
+ if 'services' in netCfg:
36
+ netService = re.sub(r'_1$', '', networkName)
37
+ netCfg = netCfg['services'][netService]['x-network']
38
+ mnCfg = netCfg['mininet-cfg']
39
+
40
+ links = []
41
+ containers = {}
42
+ containers[networkName] = {'nodes': []}
43
+ namespaces = {}
44
+
45
+ for link in netCfg.get('links', []):
46
+ l, r = link['left'], link['right']
47
+ clname, crname = l['container'], r['container']
48
+ ilname, irname = l['intf'], r['intf']
49
+ links.append([(clname, ilname), (crname, irname)])
50
+ for cname, intf in [(clname, l), (crname, r)]:
51
+ if cname not in containers:
52
+ containers[cname] = {'nodes': []}
53
+ containers[cname]['nodes'].append(intf)
54
+
55
+ for lk in mnCfg.get('links', []):
56
+ l, r = lk['left'], lk['right']
57
+ links.append([(networkName, l), (networkName, r)])
58
+
59
+ for s in mnCfg.get('switches', []):
60
+ namespaces[s['name']] = {'interfaces': [], 'type': 'switch', **s}
61
+ for h in mnCfg.get('hosts', []):
62
+ namespaces[h['name']] = {'interfaces': [], 'type': 'host', **h}
63
+
64
+ for i in mnCfg.get('interfaces', []):
65
+ nm, nd = i.get('origName', i['name']), i['node']
66
+ if nd in namespaces:
67
+ namespaces[nd]['interfaces'].append(i)
68
+ else:
69
+ links.append([(networkName, nm), (networkName, nd)])
70
+
71
+ # Make sure all namespaces have at least one interface so that
72
+ # they can be connected by edges (lhead, ltail)
73
+ for nsname, nsdata in namespaces.items():
74
+ if not nsdata['interfaces']:
75
+ nsdata['interfaces'].append({'name': 'DUMMY_%s' % nsname})
76
+
77
+ ###
78
+
79
+ print('digraph D {')
80
+ print(' splines = true;')
81
+ print(' compound = true;')
82
+ print(' node [%s];' % NODE_PROPS)
83
+
84
+ for cname, cdata in containers.items():
85
+ print(' subgraph cluster_%s {' % id(cname))
86
+ print(' label = "%s";' % cname)
87
+ if cname == networkName:
88
+ print(' %s;' % NETWORK_PROPS)
89
+ else:
90
+ print(' %s;' % CONTAINER_PROPS)
91
+ for node in cdata['nodes']:
92
+ print(' %s;' % intfText(node['container'], node))
93
+ # mininet/network interface is processed after container links
94
+ # so that the mininet/network definitions take precedence
95
+ if cname == networkName:
96
+ for nsname, nsdata in namespaces.items():
97
+ print(' subgraph cluster_%s__%s {' % (
98
+ id(cname), id(nsname)))
99
+ print(' label = "%s";' % nsname)
100
+ if nsdata['type'] == 'switch':
101
+ print(' %s;' % SWITCH_PROPS)
102
+ else:
103
+ print(' %s;' % HOST_PROPS)
104
+ for intf in nsdata['interfaces']:
105
+ print(' %s;' % intfText(cname, intf))
106
+ print(' }')
107
+ print(" }")
108
+
109
+ for ((lc, ln), (rc, rn)) in links:
110
+ extra=''
111
+ if lc == networkName and ln in namespaces:
112
+ iname = namespaces[ln]['interfaces'][0]['name']
113
+ extra += ' ltail=cluster_%s__%s' % (id(networkName), id(ln))
114
+ ln = id(iname)
115
+ if rc == networkName and rn in namespaces:
116
+ iname = namespaces[rn]['interfaces'][0]['name']
117
+ extra += ' lhead=cluster_%s__%s' % (id(networkName), id(rn))
118
+ rn = id(iname)
119
+ print(' %s__%s -> %s__%s [dir=none%s];' % (
120
+ id(lc), id(ln), id(rc), id(rn), extra))
121
+
122
+ print("}")
@@ -0,0 +1,97 @@
1
+ - MVP for ViaBox:
2
+ - [x] compose/x-network file loading
3
+ - [x] multiple config sources and merging
4
+ - [x] link route config
5
+ - [x] filtering on project and workdir
6
+ - [x] interface and MAC iteration
7
+ - [x] variable templating
8
+ - [ ] *vlan type interfaces
9
+
10
+ - Near term:
11
+ - [ ] dummy interfaces
12
+ - [ ] schema validation
13
+ - [ ] tc/qdisc settings
14
+ - [ ] arbitrary container commands
15
+
16
+ - Further term:
17
+ - [ ] CNI networking support
18
+ - [ ] tunnel interfaces
19
+ - [ ] multiple routes
20
+ - [ ] ovs flow config
21
+ - [ ] Multiple bridge-modes
22
+ - bridge-mode as part of the domain definition so that the
23
+ same conlink instances can support multiple bridge modes
24
+ simultaneously (with a default for links that don't
25
+ specify).
26
+ - [ ] CNI model:
27
+ - conlink runs in container listening for events on a UDS
28
+ (intead of docker events)
29
+ - an outer conlink command is the CNI client that formats
30
+ events to send over the UDS to the inner conlink
31
+
32
+ - Also see schema-ish.yaml
33
+ links:
34
+ - type: TYPE # Default: domain.
35
+ # Others: dummy, tunnel, host, *vlan, etc
36
+
37
+ domain: DOMAIN # ovs switch name
38
+
39
+ container: FOO # full container name
40
+ # OR
41
+ service: FOO # compose service name
42
+
43
+ interface: INTF # internal container interface name
44
+
45
+ # --- optional general ---
46
+
47
+ ip(s): IP # starting address, can include net slash to limit max
48
+ mac: MAC
49
+ mtu: MTU
50
+ route(s): ROUTE # `ip route add ROUTE`, maybe add "dev INTF" automatically
51
+ tc(s): TC # tc/qdisc commands/settings
52
+ flow(s): FLOW # `ovs-ofctl add-flow DOMAIN FLOW`. With var templating.
53
+ command(s): CMD # arbitrary shell cmd. After all links setup for this container
54
+
55
+ # --- optional for 'type:' links ---
56
+
57
+ host-intf: INTF # host interface to *vlan or move in
58
+ mode: MODE # bridge, etc
59
+ vlanid: VLANID # VLAN #
60
+ nat: NAT # nat target
61
+
62
+
63
+ - type maps to 'ip link' type with default of 'veth'
64
+ -
65
+ - when type is 'veth', then base default is 'conlink'
66
+ - when type is 'veth' and base is 'conlink', then bridge is required.
67
+
68
+ - conlink veth link: {type: veth [DEFAULT], base: conlink [DEFAULT], bridge: BRIDGE, dev: DEV}
69
+
70
+
71
+
72
+ Dependencies in python version:
73
+ - argparse
74
+ - shlex (parsing commands)
75
+ - compose_interpolation import TemplateWithDefaults
76
+ - cerberus import Validator
77
+ - options: joi, ajv, json-schema, and z-schema.
78
+ - docker
79
+ - psutil (pid_exists)
80
+ - json
81
+ - yaml
82
+ - mininet
83
+
84
+
85
+ - [deprecated idea] conlink sub-commands:
86
+ conlink spit
87
+ - output override docker-compose file with conlink service
88
+ - figure out volume mounts to get to other compose file(s)
89
+ conlink dc up ...
90
+ - generate override docker-compose file with conlink service
91
+ - run the compose command with override file
92
+ conlink start
93
+ - start inside compose
94
+ conlink run
95
+ - start outside compose
96
+
97
+
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "conlink",
3
+ "dependencies": {
4
+ "@exodus/schemasafe": "^1.3.0",
5
+ "ajv": "^8.12.0",
6
+ "dockerode": "^3.3.4",
7
+ "joi": "^17.10.2",
8
+ "nbb": "^1.2.178",
9
+ "neodoc": "^2.0.2",
10
+ "yaml": "^2.2.1"
11
+ },
12
+ "devDependencies": {
13
+ "shadow-cljs": "^2.25.7",
14
+ "source-map-support": "^0.5.21"
15
+ }
16
+ }
@@ -0,0 +1,138 @@
1
+ links:
2
+ type: list
3
+ schema:
4
+ type: dict
5
+ schema:
6
+ left:
7
+ type: dict
8
+ required: true
9
+ schema:
10
+ container: {type: string, required: true}
11
+ intf: {type: string, required: true}
12
+ ip: {type: string, required: false,
13
+ regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
14
+ mac: {type: string, required: false,
15
+ regex: '([0-9A-Fa-f][0-9A-Fa-f]:){5}[0-9A-Fa-f][0-9A-Fa-f]'}
16
+ right:
17
+ type: dict
18
+ required: true
19
+ schema:
20
+ container: {type: string, required: true}
21
+ intf: {type: string, required: true}
22
+ ip: {type: string, required: false,
23
+ regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
24
+ mac: {type: string, required: false,
25
+ regex: '([0-9A-Fa-f][0-9A-Fa-f]:){5}[0-9A-Fa-f][0-9A-Fa-f]'}
26
+
27
+ tunnels:
28
+ type: list
29
+ schema:
30
+ type: dict
31
+ schema:
32
+ type: {type: string, required: true}
33
+ intf: {type: string, required: true}
34
+ vni: {type: number, required: true}
35
+ remote: {type: string, required: true,
36
+ regex: '([0-9]+\.){3}[0-9]+'}
37
+ ip: {type: string, required: false,
38
+ regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
39
+ mac: {type: string, required: false,
40
+ regex: '([0-9A-Fa-f][0-9A-Fa-f]:){5}[0-9A-Fa-f][0-9A-Fa-f]'}
41
+ link_args: {type: string, required: false}
42
+
43
+ interfaces:
44
+ type: list
45
+ schema:
46
+ type: dict
47
+ required: true
48
+ schema:
49
+ type: {type: string, required: true,
50
+ allowed: [link, vlan, macvlan, macvtap, ipvlan, ipvtap]}
51
+ container: {type: string, required: true}
52
+ host-intf: {type: string, required: true}
53
+ intf: {type: string, required: true}
54
+ mode: {type: string, required: false,
55
+ dependencies: {type: [macvlan, macvtap, ipvlan, ipvtap]}}
56
+ vlanid: {type: number, required: false,
57
+ dependencies: {type: [vlan]}}
58
+ ip: {type: string, required: false,
59
+ regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
60
+ nat: {type: string, required: false,
61
+ regex: '([0-9]+\.){3}[0-9]+',
62
+ dependencies: ip}
63
+
64
+ network-settings: {type: string}
65
+
66
+ commands:
67
+ type: list
68
+ schema:
69
+ type: dict
70
+ schema:
71
+ container: {type: string, required: true}
72
+ command:
73
+ required: true
74
+ oneof: [ {type: string}, {type: list, schema: {type: string}} ]
75
+
76
+ mininet-cfg:
77
+ type: dict
78
+ schema:
79
+
80
+ switches:
81
+ type: list
82
+ required: false
83
+ schema:
84
+ type: dict
85
+ schema:
86
+ name: {type: string, required: true}
87
+ opts: {type: dict, required: false}
88
+
89
+ hosts:
90
+ type: list
91
+ required: false
92
+ schema:
93
+ type: dict
94
+ schema:
95
+ name: {type: string, required: true}
96
+ opts: {type: dict, required: false}
97
+
98
+ containers:
99
+ type: list
100
+ required: false
101
+ schema:
102
+ type: dict
103
+ schema:
104
+ name: {type: string, required: true}
105
+ opts: {type: dict, required: false}
106
+
107
+ links:
108
+ type: list
109
+ required: false
110
+ schema:
111
+ type: dict
112
+ schema:
113
+ left: {type: string, required: true}
114
+ right: {type: string, required: true}
115
+ opts: {type: dict, required: false}
116
+
117
+ interfaces:
118
+ type: list
119
+ required: false
120
+ schema:
121
+ type: dict
122
+ schema:
123
+ node: {type: string, required: true}
124
+ name: {type: string, required: true}
125
+ origName: {type: string, required: false}
126
+ opts: {type: dict, required: false}
127
+
128
+ commands:
129
+ type: list
130
+ required: false
131
+ schema:
132
+ type: dict
133
+ schema:
134
+ node: {type: string, required: true}
135
+ sync: {type: boolean, required: false}
136
+ command:
137
+ required: true
138
+ oneof: [ {type: string}, {type: list, schema: {type: string}} ]