Fixed server option code point conflict
[dhcp.git] / client / scripts / linux
1 #!/bin/bash
2 # dhclient-script for Linux. Dan Halbert, March, 1997.
3 # Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
4 # No guarantees about this. I'm a novice at the details of Linux
5 # networking.
6
7 # Notes:
8
9 # 0. This script is based on the netbsd script supplied with dhcp-970306.
10
11 # 1. ifconfig down apparently deletes all relevant routes and flushes
12 # the arp cache, so this doesn't need to be done explicitly.
13
14 # 2. The alias address handling here has not been tested AT ALL.
15 # I'm just going by the doc of modern Linux ip aliasing, which uses
16 # notations like eth0:0, eth0:1, for each alias.
17
18 # 3. I have to calculate the network address, and calculate the broadcast
19 # address if it is not supplied. This might be much more easily done
20 # by the dhclient C code, and passed on.
21
22 # 4. TIMEOUT not tested. ping has a flag I don't know, and I'm suspicious
23 # of the $1 in its args.
24
25 # 5. Script refresh in 2017. The aliasing code was too convoluted and needs
26 # to go away. Migrated DHCPv4 script to ip command from iproute2 suite.
27 # This is based on Debian script with some tweaks. ifconfig is no longer
28 # used. Everything is done using ip tool from ip-route2.
29
30 # 'ip' just looks too weird. Also, we now have unit-tests! Those unit-tests
31 # overwirte this line to use a fake ip-echo tool. It's also convenient
32 # if your system holds ip tool in a non-standard location.
33 ip=/sbin/ip
34
35 # update /etc/resolv.conf based on received values
36 # This updated version mostly follows Debian script by Andrew Pollock et al.
37 make_resolv_conf() {
38     local new_resolv_conf
39
40     # DHCPv4
41     if [ -n "$new_domain_search" ] || [ -n "$new_domain_name" ] ||
42        [ -n "$new_domain_name_servers" ]; then
43         new_resolv_conf=/etc/resolv.conf.dhclient-new
44         rm -f $new_resolv_conf
45
46         if [ -n "$new_domain_name" ]; then
47             echo domain ${new_domain_name%% *} >>$new_resolv_conf
48         fi
49
50         if [ -n "$new_domain_search" ]; then
51             if [ -n "$new_domain_name" ]; then
52                 domain_in_search_list=""
53                 for domain in $new_domain_search; do
54                     if [ "$domain" = "${new_domain_name}" ] ||
55                        [ "$domain" = "${new_domain_name}." ]; then
56                         domain_in_search_list="Yes"
57                     fi
58                 done
59                 if [ -z "$domain_in_search_list" ]; then
60                     new_domain_search="$new_domain_name $new_domain_search"
61                 fi
62             fi
63             echo "search ${new_domain_search}" >> $new_resolv_conf
64         elif [ -n "$new_domain_name" ]; then
65             echo "search ${new_domain_name}" >> $new_resolv_conf
66         fi
67
68         if [ -n "$new_domain_name_servers" ]; then
69             for nameserver in $new_domain_name_servers; do
70                 echo nameserver $nameserver >>$new_resolv_conf
71             done
72         else # keep 'old' nameservers
73             sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p /etc/resolv.conf >>$new_resolv_conf
74         fi
75
76         if [ -f /etc/resolv.conf ]; then
77             chown --reference=/etc/resolv.conf $new_resolv_conf
78             chmod --reference=/etc/resolv.conf $new_resolv_conf
79         fi
80         mv -f $new_resolv_conf /etc/resolv.conf
81     # DHCPv6
82     elif [ -n "$new_dhcp6_domain_search" ] || [ -n "$new_dhcp6_name_servers" ]; then
83         new_resolv_conf=/etc/resolv.conf.dhclient-new
84         rm -f $new_resolv_conf
85
86         if [ -n "$new_dhcp6_domain_search" ]; then
87             echo "search ${new_dhcp6_domain_search}" >> $new_resolv_conf
88         fi
89
90         if [ -n "$new_dhcp6_name_servers" ]; then
91             for nameserver in $new_dhcp6_name_servers; do
92                 # append %interface to link-local-address nameservers
93                 if [ "${nameserver##fe80::}" != "$nameserver" ] ||
94                    [ "${nameserver##FE80::}" != "$nameserver" ]; then
95                     nameserver="${nameserver}%${interface}"
96                 fi
97                 echo nameserver $nameserver >>$new_resolv_conf
98             done
99         else # keep 'old' nameservers
100             sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p /etc/resolv.conf >>$new_resolv_conf
101         fi
102
103         if [ -f /etc/resolv.conf ]; then
104             chown --reference=/etc/resolv.conf $new_resolv_conf
105             chmod --reference=/etc/resolv.conf $new_resolv_conf
106         fi
107         mv -f $new_resolv_conf /etc/resolv.conf
108     fi
109 }
110
111 # set host name
112 set_hostname() {
113     local current_hostname
114
115     if [ -n "$new_host_name" ]; then
116         current_hostname=$(hostname)
117
118         # current host name is empty, '(none)' or 'localhost' or differs from new one from DHCP
119         if [ -z "$current_hostname" ] ||
120            [ "$current_hostname" = '(none)' ] ||
121            [ "$current_hostname" = 'localhost' ] ||
122            [ "$current_hostname" = "$old_host_name" ]; then
123            if [ "$new_host_name" != "$old_host_name" ]; then
124                hostname "$new_host_name"
125            fi
126         fi
127     fi
128 }
129
130 # run given script
131 run_hook() {
132     local script
133     local exit_status
134     script="$1"
135
136     if [ -f $script ]; then
137         . $script
138     fi
139
140     if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ]; then
141         logger -p daemon.err "$script returned non-zero exit status $exit_status"
142     fi
143
144     return $exit_status
145 }
146
147 # run scripts in given directory
148 run_hookdir() {
149     local dir
150     local exit_status
151     dir="$1"
152
153     if [ -d "$dir" ]; then
154         for script in $(run-parts --list $dir); do
155             run_hook $script || true
156             exit_status=$?
157         done
158     fi
159
160     return $exit_status
161 }
162
163 # Must be used on exit.   Invokes the local dhcp client exit hooks, if any.
164 exit_with_hooks() {
165     exit_status=$1
166
167     # Source the documented exit-hook script, if it exists
168     if ! run_hook /etc/dhclient-exit-hooks; then
169         exit_status=$?
170     fi
171
172     # Now run scripts in the Debian-specific directory.
173     if ! run_hookdir /etc/dhclient-exit-hooks.d; then
174         exit_status=$?
175     fi
176
177     exit $exit_status
178 }
179
180
181 # Invoke the local dhcp client enter hooks, if they exist.
182 run_hook /etc/dhclient-enter-hooks
183 run_hookdir /etc/dhclient-enter-hooks.d
184
185 # Execute the operation
186 case "$reason" in
187
188     ### DHCPv4 Handlers
189
190     MEDIUM|ARPCHECK|ARPSEND)
191         # Do nothing
192         ;;
193     PREINIT)
194         # The DHCP client is requesting that an interface be
195         # configured as required in order to send packets prior to
196         # receiving an actual address. - dhclient-script(8)
197
198         # ensure interface is up
199         ${ip} link set dev ${interface} up
200
201         if [ -n "$alias_ip_address" ]; then
202             # flush alias IP from interface
203             ${ip} -4 addr flush dev ${interface} label ${interface}:0
204         fi
205
206         ;;
207
208     BOUND|RENEW|REBIND|REBOOT)
209         set_hostname
210
211         if [ -n "$old_ip_address" ] && [ -n "$alias_ip_address" ] &&
212            [ "$alias_ip_address" != "$old_ip_address" ]; then
213             # alias IP may have changed => flush it
214             ${ip} -4 addr flush dev ${interface} label ${interface}:0
215         fi
216
217         if [ -n "$old_ip_address" ] &&
218            [ "$old_ip_address" != "$new_ip_address" ]; then
219             # leased IP has changed => flush it
220             ${ip} -4 addr flush dev ${interface} label ${interface}
221         fi
222
223         if [ -z "$old_ip_address" ] ||
224            [ "$old_ip_address" != "$new_ip_address" ] ||
225            [ "$reason" = "BOUND" ] || [ "$reason" = "REBOOT" ]; then
226             # new IP has been leased or leased IP changed => set it
227             ${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
228                 ${new_broadcast_address:+broadcast $new_broadcast_address} \
229                 dev ${interface} label ${interface}
230
231             if [ -n "$new_interface_mtu" ]; then
232                 # set MTU
233                 ${ip} link set dev ${interface} mtu ${new_interface_mtu}
234             fi
235
236             # if we have $new_rfc3442_classless_static_routes then we have to
237             # ignore $new_routers entirely
238             if [ ! "$new_rfc3442_classless_static_routes" ]; then
239                     # set if_metric if IF_METRIC is set or there's more than one router
240                     if_metric="$IF_METRIC"
241                     if [ "${new_routers%% *}" != "${new_routers}" ]; then
242                         if_metric=${if_metric:-1}
243                     fi
244
245                     for router in $new_routers; do
246                         if [ "$new_subnet_mask" = "255.255.255.255" ]; then
247                             # point-to-point connection => set explicit route
248                             ${ip} -4 route add ${router} dev $interface >/dev/null 2>&1
249                         fi
250
251                         # set default route
252                         ${ip} -4 route add default via ${router} dev ${interface} \
253                             ${if_metric:+metric $if_metric} >/dev/null 2>&1
254
255                         if [ -n "$if_metric" ]; then
256                             if_metric=$((if_metric+1))
257                         fi
258                     done
259             fi
260         fi
261
262         if [ -n "$alias_ip_address" ] &&
263            [ "$new_ip_address" != "$alias_ip_address" ]; then
264             # separate alias IP given, which may have changed
265             # => flush it, set it & add host route to it
266             ${ip} -4 addr flush dev ${interface} label ${interface}:0
267             ${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
268                 dev ${interface} label ${interface}:0
269             ${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
270         fi
271
272         # update /etc/resolv.conf
273         make_resolv_conf
274
275         ;;
276
277     EXPIRE|FAIL|RELEASE|STOP)
278         if [ -n "$alias_ip_address" ]; then
279             # flush alias IP
280             ${ip} -4 addr flush dev ${interface} label ${interface}:0
281         fi
282
283         if [ -n "$old_ip_address" ]; then
284             # flush leased IP
285             ${ip} -4 addr flush dev ${interface} label ${interface}
286         fi
287
288         if [ -n "$alias_ip_address" ]; then
289             # alias IP given => set it & add host route to it
290             ${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
291                 dev ${interface} label ${interface}:0
292             ${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
293         fi
294
295         ;;
296
297     TIMEOUT)
298         if [ -n "$alias_ip_address" ]; then
299             # flush alias IP
300             ${ip} -4 addr flush dev ${interface} label ${interface}:0
301         fi
302
303         # set IP from recorded lease
304         ${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
305             ${new_broadcast_address:+broadcast $new_broadcast_address} \
306             dev ${interface} label ${interface}
307
308         if [ -n "$new_interface_mtu" ]; then
309             # set MTU
310             ${ip} link set dev ${interface} mtu ${new_interface_mtu}
311         fi
312
313         # if there is no router recorded in the lease or the 1st router answers pings
314         if [ -z "$new_routers" ] || ping -q -c 1 "${new_routers%% *}"; then
315             # if we have $new_rfc3442_classless_static_routes then we have to
316             # ignore $new_routers entirely
317             if [ ! "$new_rfc3442_classless_static_routes" ]; then
318                     if [ -n "$alias_ip_address" ] &&
319                        [ "$new_ip_address" != "$alias_ip_address" ]; then
320                         # separate alias IP given => set up the alias IP & add host route to it
321                         ${ip} -4 addr add \
322                               ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
323                               dev ${interface} label ${interface}:0
324                         ${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
325                     fi
326
327                     # set if_metric if IF_METRIC is set or there's more than one router
328                     if_metric="$IF_METRIC"
329                     if [ "${new_routers%% *}" != "${new_routers}" ]; then
330                         if_metric=${if_metric:-1}
331                     fi
332
333                     # set default route
334                     for router in $new_routers; do
335                         ${ip} -4 route add default via ${router} dev ${interface} \
336                             ${if_metric:+metric $if_metric} >/dev/null 2>&1
337
338                         if [ -n "$if_metric" ]; then
339                             if_metric=$((if_metric+1))
340                         fi
341                     done
342             fi
343
344             # update /etc/resolv.conf
345             make_resolv_conf
346         else
347             # flush all IPs from interface
348             ip -4 addr flush dev ${interface}
349             exit_with_hooks 2
350         fi
351
352         ;;
353
354     ### DHCPv6 Handlers
355     # TODO handle prefix change: ?based on ${old_ip6_prefix} and ${new_ip6_prefix}?
356
357     PREINIT6)
358         # ensure interface is up
359         ${ip} link set ${interface} up
360
361         # We need to give the kernel some time to active interface
362         interface_up_wait_time=5
363         for i in $(seq 0 ${interface_up_wait_time})
364         do
365             ifconfig ${interface} | grep RUNNING >/dev/null 2>&1
366             if [ $? -eq 0 ]; then
367                 break;
368             fi
369             sleep 1
370         done
371
372         # flush any stale global permanent IPs from interface
373         ${ip} -6 addr flush dev ${interface} scope global permanent
374
375         # Wait for duplicate address detection for this interface if the
376         # --dad-wait-time parameter has been specified and is greater than
377         # zero.
378         if [ ${dad_wait_time} -gt 0 ]; then
379             # Check if any IPv6 address on this interface is marked as
380             # tentative.
381             ${ip} addr show ${interface} | grep inet6 | grep tentative \
382                 &> /dev/null
383             if [ $? -eq 0 ]; then
384                 # Wait for duplicate address detection to complete or for
385                 # the timeout specified as --dad-wait-time.
386                 for i in $(seq 0 $dad_wait_time)
387                 do
388                     # We're going to poll for the tentative flag every second.
389                     sleep 1
390                     ${ip} addr show ${interface} | grep inet6 | grep tentative \
391                         &> /dev/null
392                     if [ $? -ne 0 ]; then
393                         break;
394                     fi
395                 done
396             fi
397         fi
398
399         ;;
400
401     BOUND6|RENEW6|REBIND6)
402         if [ "${new_ip6_address}" ] && [ "${new_ip6_prefixlen}" ]; then
403             # set leased IP
404             ${ip} -6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
405                 dev ${interface} scope global
406         fi
407
408         # update /etc/resolv.conf
409         if [ "${reason}" = BOUND6 ] ||
410            [ "${new_dhcp6_name_servers}" != "${old_dhcp6_name_servers}" ] ||
411            [ "${new_dhcp6_domain_search}" != "${old_dhcp6_domain_search}" ]; then
412             make_resolv_conf
413         fi
414
415         ;;
416
417     DEPREF6)
418         if [ -z "${cur_ip6_prefixlen}" ]; then
419             exit_with_hooks 2
420         fi
421
422         # set preferred lifetime of leased IP to 0
423         ${ip} -6 addr change ${cur_ip6_address}/${cur_ip6_prefixlen} \
424             dev ${interface} scope global preferred_lft 0
425
426         ;;
427
428     EXPIRE6|RELEASE6|STOP6)
429         if [ -z "${old_ip6_address}" ] || [ -z "${old_ip6_prefixlen}" ]; then
430             exit_with_hooks 2
431         fi
432
433         # delete leased IP
434         ${ip} -6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
435             dev ${interface}
436
437         ;;
438 esac
439
440 exit_with_hooks 0