Project

General

Profile

create_docker_lan.sh

Roger Borrello, 05/06/2026 01:04 PM

Download (10.2 KB)

 
1
#!/bin/bash
2
set -euo pipefail
3

    
4
update_bridge_forward_rules()
5
{
6
   local lan_if="$1"
7
   local bridge_if="$2"
8
   local subnet="$3"
9

    
10
   sudo iptables -C FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null || \
11
      sudo iptables -A FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT
12

    
13
   sudo iptables -C FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null || \
14
      sudo iptables -A FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT
15
}
16

    
17
remove_bridge_forward_rules()
18
{
19
   local lan_if="$1"
20
   local bridge_if="$2"
21
   local subnet="$3"
22

    
23
   while sudo iptables -C FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null
24
   do
25
      sudo iptables -D FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT
26
   done
27

    
28
   while sudo iptables -C FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null
29
   do
30
      sudo iptables -D FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT
31
   done
32
}
33

    
34
update_forward_rules()
35
{
36
   local lan_if="$1"
37
   local host_if="$2"
38
   local subnet="$3"
39

    
40
   sudo iptables -C FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT 2>/dev/null || \
41
      sudo iptables -A FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT
42

    
43
   sudo iptables -C FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null || \
44
      sudo iptables -A FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT
45
}
46

    
47
remove_forward_rules()
48
{
49
   local lan_if="$1"
50
   local host_if="$2"
51
   local subnet="$3"
52

    
53
   while sudo iptables -C FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT 2>/dev/null
54
   do
55
      sudo iptables -D FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT
56
   done
57

    
58
   while sudo iptables -C FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null
59
   do
60
      sudo iptables -D FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT
61
   done
62
}
63

    
64
update_bridge_host_rules()
65
{
66
   local subnet="$1"
67
   local bridge_if="$2"
68

    
69
   sudo iptables -C OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null || \
70
      sudo iptables -A OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT
71

    
72
   sudo iptables -C INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT 2>/dev/null || \
73
      sudo iptables -A INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT
74
}
75

    
76
remove_bridge_host_rules()
77
{
78
   local subnet="$1"
79
   local bridge_if="$2"
80

    
81
   while sudo iptables -C OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null
82
   do
83
      sudo iptables -D OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT
84
   done
85

    
86
   while sudo iptables -C INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT 2>/dev/null
87
   do
88
      sudo iptables -D INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT
89
   done
90
}
91

    
92
invalid_cidr()
93
{
94
   local cidr="$1"
95

    
96
   if [[ ! $cidr =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]{1,2})$ ]]; then
97
      return 0
98
   fi
99

    
100
   local ip="${cidr%/*}"
101
   local prefix="${cidr#*/}"
102

    
103
   if (( prefix < 0 || prefix > 32 )); then
104
      return 0
105
   fi
106

    
107
   IFS='.' read -r o1 o2 o3 o4 <<< "$ip"
108

    
109
   for octet in "$o1" "$o2" "$o3" "$o4"
110
   do
111
      if (( octet < 0 || octet > 255 )); then
112
         return 0
113
      fi
114
   done
115

    
116
   return 1
117
}
118

    
119
show_usage()
120
{
121
   cat <<EOF
122
Create or remove a Docker network and optionally configure host access.
123

    
124
Usage:
125
   $0 [-t <ntype>] [-s <subnet>] [-g <gateway>] [-n <nname>] [-i] [--link_ip=<link ip>] [--bridge-name=<bridge ifname>]
126
   $0 -r -n <nname>
127

    
128
Where:
129
   -t <ntype>          Network type: bridge, macvlan, ipvlan (def=${ntype})
130
   -s <subnet>         Subnet in CIDR format (def=${sn})
131
   -g <gateway>        Gateway IP address (def=${gw})
132
   -n <nname>          Docker network name (def=${nname})
133
   -i                  Configure host/routed access
134
   -r                  Remove/cleanup the given network
135
   --link_ip=<cidr>    Host-side helper IP for macvlan/ipvlan (def=${lip})
136
   --bridge-name=<if>  Linux bridge interface name for bridge networks (optional, otherwise br-nnnnnnnnnnnn)
137

    
138
Notes:
139
   * For macvlan/ipvlan, -g should normally be the real upstream router.
140
   * For bridge, -g is the Docker bridge gateway.
141
   * For bridge, -i adds INPUT/OUTPUT rules for host access and FORWARD rules for routed LAN access.
142
   * In remove mode, the network name is required.
143
EOF
144
}
145

    
146
derive_bridge_ifname()
147
{
148
   local network_name="$1"
149
   local explicit_bridge_name="$2"
150
   local bridge_if
151

    
152
   if [ -n "$explicit_bridge_name" ]; then
153
      echo "$explicit_bridge_name"
154
      return 0
155
   fi
156

    
157
   bridge_if=$(docker network inspect "$network_name" -f '{{index .Options "com.docker.network.bridge.name"}}' 2>/dev/null || true)
158
   if [ -n "$bridge_if" ] && [ "$bridge_if" != "<no value>" ]; then
159
      echo "$bridge_if"
160
      return 0
161
   fi
162

    
163
   local netid
164
   netid=$(docker network inspect "$network_name" -f '{{.Id}}')
165
   echo "br-${netid:0:12}"
166
}
167

    
168
network_has_endpoints()
169
{
170
   local network_name="$1"
171
   local count
172

    
173
   count=$(docker network inspect "$network_name" -f '{{len .Containers}}' 2>/dev/null || echo "0")
174
   [ "$count" != "0" ]
175
}
176

    
177
remove_network()
178
{
179
   local network_name="$1"
180
   local host_if_name="${network_name}_host"
181

    
182
   if ! docker network inspect "$network_name" >/dev/null 2>&1; then
183
      echo "Docker network $network_name does not exist"
184
      exit 1
185
   fi
186

    
187
   local driver
188
   local subnet
189
   local bridge_if
190

    
191
   driver=$(docker network inspect "$network_name" -f '{{.Driver}}')
192
   subnet=$(docker network inspect "$network_name" -f '{{(index .IPAM.Config 0).Subnet}}')
193

    
194
   echo "Removing Docker network $network_name"
195
   echo "  Driver: $driver"
196
   echo "  Subnet: $subnet"
197

    
198
   case "$driver" in
199
      bridge)
200
         bridge_if=$(derive_bridge_ifname "$network_name" "$bridge_name")
201

    
202
         if ip link show "$bridge_if" >/dev/null 2>&1; then
203
            echo "Removing bridge INPUT/OUTPUT rules for $bridge_if"
204
            remove_bridge_host_rules "$subnet" "$bridge_if"
205

    
206
            echo "Removing bridge FORWARD rules for $bridge_if"
207
            remove_bridge_forward_rules "$interf" "$bridge_if" "$subnet"
208
         fi
209
         ;;
210

    
211
      macvlan|ipvlan)
212
         if ip link show "$host_if_name" >/dev/null 2>&1; then
213
            echo "Removing FORWARD rules for helper interface $host_if_name"
214
            remove_forward_rules "$interf" "$host_if_name" "$subnet"
215

    
216
            echo "Deleting helper interface $host_if_name"
217
            sudo ip link delete "$host_if_name"
218
         fi
219
         ;;
220
   esac
221

    
222
   if network_has_endpoints "$network_name"; then
223
      echo "ERROR: Network $network_name still has attached containers/endpoints"
224
      echo "Detach or remove them first, then rerun."
225
      exit 1
226
   fi
227

    
228
   docker network rm "$network_name"
229
   echo "Removed Docker network $network_name"
230
}
231

    
232
# Defaults
233
ntype="macvlan"
234
sn="192.168.2.0/24"
235
gw="192.168.2.1"
236
interf=$(ip route get 8.8.8.8 | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}')
237
nname="corp_lan"
238
configure_host_access=false
239
remove_mode=false
240
lname="${nname}_host"
241
lip="192.168.2.10/24"
242
bridge_name=""
243

    
244
# Process options
245
while getopts "h?t:s:g:n:ir-:" opt
246
do
247
   case $opt in
248
      t )
249
         ntype=$OPTARG
250
         ;;
251
      s )
252
         sn=$OPTARG
253
         ;;
254
      g )
255
         gw=$OPTARG
256
         ;;
257
      n )
258
         nname=$OPTARG
259
         lname="${nname}_host"
260
         ;;
261
      i )
262
         configure_host_access=true
263
         ;;
264
      r )
265
         remove_mode=true
266
         ;;
267
      - )
268
         case ${OPTARG} in
269
            link_ip=* )
270
               lip="${OPTARG#*=}"
271
               ;;
272
            bridge-name=* )
273
               bridge_name="${OPTARG#*=}"
274
               ;;
275
            * )
276
               echo "Unknown option: --${OPTARG}"
277
               show_usage
278
               exit 1
279
               ;;
280
         esac
281
         ;;
282
      h | \? )
283
         show_usage
284
         exit 1
285
         ;;
286
   esac
287
done
288
shift $((OPTIND - 1))
289

    
290
if [ "$remove_mode" = true ]; then
291
   if [ -z "$nname" ]; then
292
      echo "ERROR: Remove mode requires -n <network_name>"
293
      exit 1
294
   fi
295

    
296
   remove_network "$nname"
297
   exit 0
298
fi
299

    
300
# Validate inputs
301
if invalid_cidr "$sn"; then
302
   echo "ERROR: Invalid CIDR IPv4 format: $sn"
303
   exit 1
304
fi
305

    
306
if [ "$configure_host_access" = true ] && [ "$ntype" != "bridge" ] && invalid_cidr "$lip"; then
307
   echo "ERROR: Invalid CIDR IPv4 format: $lip"
308
   exit 1
309
fi
310

    
311
case "$ntype" in
312
   bridge|macvlan|ipvlan)
313
      ;;
314
   *)
315
      echo "ERROR: Unsupported network type: $ntype"
316
      exit 1
317
      ;;
318
esac
319

    
320
# Ensure Docker network
321
if docker network inspect "$nname" >/dev/null 2>&1; then
322
   echo "Docker network $nname already exists"
323
else
324
   echo "Creating Docker network $nname"
325

    
326
   if [ "$ntype" = "bridge" ]; then
327
      create_cmd=(docker network create -d bridge --subnet="$sn" --gateway="$gw")
328

    
329
      if [ -n "$bridge_name" ]; then
330
         create_cmd+=(-o "com.docker.network.bridge.name=$bridge_name")
331
      fi
332

    
333
      if [ "$configure_host_access" = true ]; then
334
         create_cmd+=(-o "com.docker.network.bridge.trusted_host_interfaces=$interf")
335
      fi
336

    
337
      create_cmd+=("$nname")
338
      "${create_cmd[@]}"
339
   else
340
      docker network create \
341
         -d "$ntype" \
342
         --subnet="$sn" \
343
         --gateway="$gw" \
344
         -o parent="$interf" \
345
         "$nname"
346
   fi
347
fi
348

    
349
# Host-access configuration
350
if [ "$configure_host_access" = true ]; then
351
   if [ "$ntype" = "bridge" ]; then
352
      bridge_if=$(derive_bridge_ifname "$nname" "$bridge_name")
353

    
354
      if ! ip link show "$bridge_if" >/dev/null 2>&1; then
355
         echo "ERROR: Derived bridge interface $bridge_if does not exist"
356
         exit 1
357
      fi
358

    
359
      echo "Configuring host firewall rules for bridge interface $bridge_if"
360
      update_bridge_host_rules "$sn" "$bridge_if"
361
      update_bridge_forward_rules "$interf" "$bridge_if" "$sn"
362
   else
363
      if ip link show "$lname" >/dev/null 2>&1; then
364
         echo "Link $lname already exists... removing"
365
         sudo ip link delete "$lname"
366
      fi
367

    
368
      echo "Creating host ${ntype} link $lname"
369
      sudo ip link add "$lname" link "$interf" type "$ntype" mode bridge
370

    
371
      if ip addr show "$lname" | grep -q "$lip"; then
372
         echo "IP $lip already assigned to $lname"
373
      else
374
         echo "Assigning IP $lip to $lname"
375
         sudo ip addr add "$lip" dev "$lname"
376
      fi
377

    
378
      if ip link show "$lname" | grep -q "state UP"; then
379
         echo "$lname already UP"
380
      else
381
         echo "Bringing $lname up"
382
         sudo ip link set "$lname" up
383
      fi
384

    
385
      update_forward_rules "$interf" "$lname" "$sn"
386
   fi
387
fi