1 |
commit: 31a05f1b3fb90a3b4e9c0e587bdd5a39e8236f6b |
2 |
Author: Kerin Millar <kfm <AT> plushkava <DOT> net> |
3 |
AuthorDate: Mon Jan 25 02:40:29 2021 +0000 |
4 |
Commit: Lars Wendler <polynomial-c <AT> gentoo <DOT> org> |
5 |
CommitDate: Wed Jan 27 14:05:54 2021 +0000 |
6 |
URL: https://gitweb.gentoo.org/proj/netifrc.git/commit/?id=31a05f1b |
7 |
|
8 |
net/apipa.sh: fix broken implementation by way of a rewrite |
9 |
|
10 |
Sadly, the present implementation has never functioned correctly. The |
11 |
original author employed incorrect syntax for what was intended to be a |
12 |
command substitution. As a result, the _random() function is never called. |
13 |
What actually happens is that arping is needlessly executed exactly 64516 |
14 |
times, with no address ever being considered as a valid candidate. |
15 |
|
16 |
Furthermore, this module has other bugs and is poorly designed. Here are the |
17 |
reasons as to why:- |
18 |
|
19 |
• the 169.254.0.0/16 block offers 65534 addresses, not 64516 |
20 |
• the main loop is horrendously slow at enumerating the address block |
21 |
• it counts to 64516 but doesn't ensure that each address is unique! |
22 |
• it prefers bash for generating entropy (fine, but non-standard) |
23 |
• it falls back to a non-standard utility for generating entropy |
24 |
|
25 |
Therefore, I decided to re-write most of it. The fundamental difference is |
26 |
that all 65534 octet pairs are generated up front before being processed by |
27 |
the main loop. At most, every possible address will now be tested exactly |
28 |
once. |
29 |
|
30 |
In fact, this approach turns out to be faster by an order of magnitude. The |
31 |
following synthetic tests - which calculate the time taken to enumerate the |
32 |
entire address space - demonstrate the tremendous difference between the |
33 |
existing code and mine. Of course, to ensure that the comparison was |
34 |
meaningful, I rectified the command substitution bug in the existing code. |
35 |
|
36 |
# time bash apipa-old-test.sh |
37 |
real 2m34.367s |
38 |
user 1m9.959s |
39 |
sys 1m37.502s |
40 |
|
41 |
# time bash apipa-new-test.sh |
42 |
real 0m1.119s |
43 |
user 0m0.965s |
44 |
sys 0m0.182s |
45 |
|
46 |
Note that the new _random_apipa_octets() function is responsible for |
47 |
generating all 65534 combinations of octet pairs in a random order. It |
48 |
mainly relies on awk(1) and sort(1). Where possible, a seed is obtained from |
49 |
/dev/urandom for the benefit of awk's RNG, but this is not required. |
50 |
|
51 |
I have isolated and tested the new functions on GNU/Linux, macOS, FreeBSD, |
52 |
NetBSD, OpenBSD and MirBSD. I have individually tested gawk, mawk, nawk, |
53 |
busybox awk and the awk implementations provided by the previously mentioned |
54 |
operating systems in the case that they are distinct. The only |
55 |
incompatiblity that I was personally able to find was with the awk |
56 |
implementation of MirBSD, which affects the final invocation of awk in the |
57 |
_random_apipa_octets function. However, MirBSD was forked from an old |
58 |
version of OpenBSD and seems sufficiently obscure so as not to be worth |
59 |
worrying about. If someone should try to integrate netifrc into MirBSD one |
60 |
day then the matter can be dealt with then. |
61 |
|
62 |
Finally, I want to thank Steve Arnold for bringing the original bug to my |
63 |
attention. Congratulations, Steve. You may be the only known user of |
64 |
net/apipa.sh on the planet. |
65 |
|
66 |
Signed-off-by: Kerin Millar <kfm <AT> plushkava.net> |
67 |
Reported-by: Steve Arnold <nerdboy <AT> gentoo.org> |
68 |
Closes: https://bugs.gentoo.org/766890 |
69 |
Signed-off-by: Lars Wendler <polynomial-c <AT> gentoo.org> |
70 |
|
71 |
net/apipa.sh | 94 +++++++++++++++++++++++++++++++++++++++++++----------------- |
72 |
1 file changed, 67 insertions(+), 27 deletions(-) |
73 |
|
74 |
diff --git a/net/apipa.sh b/net/apipa.sh |
75 |
index 849728b..f3ec534 100644 |
76 |
--- a/net/apipa.sh |
77 |
+++ b/net/apipa.sh |
78 |
@@ -1,49 +1,89 @@ |
79 |
# Copyright (c) 2007-2008 Roy Marples <roy@×××××××.name> |
80 |
# Released under the 2-clause BSD license. |
81 |
-# shellcheck shell=sh disable=SC1008 |
82 |
|
83 |
apipa_depend() |
84 |
{ |
85 |
program /sbin/arping /bin/arping |
86 |
} |
87 |
|
88 |
-_random() |
89 |
+_random_bytes_as_int() |
90 |
{ |
91 |
- local r=${RANDOM} # checkbashisms: false positive, we handle it AFTERWARDS |
92 |
- if [ -n "${r}" ]; then |
93 |
- echo "${r}" |
94 |
- else |
95 |
- uuidgen | sed -n -e 's/[^[:digit:]]//g' -e 's/\(^.\{1,7\}\).*/\1/p' |
96 |
- fi |
97 |
+ local hex num_bytes="$1" |
98 |
+ |
99 |
+ # While POSIX does not require that /dev/urandom exist, it is a |
100 |
+ # de-facto standard. Therefore, the following approach should be |
101 |
+ # highly portable in practice. In the case of Linux, and unlike BSD |
102 |
+ # this interface does not block in the event that the CSRNG has not |
103 |
+ # yet been seeded. Still, this is acceptable because we do not |
104 |
+ # require a guarantee that the entropy be cryptographically secure. |
105 |
+ # It's also worth noting that Linux >=5.4 is faster at seeding in |
106 |
+ # the absence of RDRAND/RDSEED than previous versions were. |
107 |
+ test -e /dev/urandom && |
108 |
+ hex=$( |
109 |
+ LC_ALL=C tr -dc '[:xdigit:]' < /dev/urandom | |
110 |
+ dd bs="$(( num_bytes * 2 ))" count=1 2>/dev/null) && |
111 |
+ test "${#hex}" = "$(( num_bytes * 2 ))" && |
112 |
+ printf '%d\n' "0x${hex}" |
113 |
+} |
114 |
+ |
115 |
+_random_apipa_octets() |
116 |
+{ |
117 |
+ local seed |
118 |
+ |
119 |
+ # Obtain a highly random 16-bit seed for use by awk's RNG. In the |
120 |
+ # unlikely event that the seed ends up being empty, awk will seed |
121 |
+ # based on the time of day, with a granularity of one second. |
122 |
+ seed=$(_random_bytes_as_int 2) |
123 |
+ |
124 |
+ # For APIPA (RFC 3927), the 169.254.0.0/16 address block is |
125 |
+ # reserved. This provides 65534 addresses, having accounted for the |
126 |
+ # network and broadcast address. Note that we must count from 1. |
127 |
+ awk "BEGIN { |
128 |
+ srand($seed) |
129 |
+ for (i=1; i<65535; i++) print rand() \" \" i |
130 |
+ }" | |
131 |
+ sort -k 1,1 -n | |
132 |
+ POSIXLY_CORRECT=1 awk '{ |
133 |
+ hex = sprintf("%04x",$2) |
134 |
+ printf("%d %d\n", "0x" substr(hex,1,2), "0x" substr(hex,3,2)) |
135 |
+ }' |
136 |
} |
137 |
|
138 |
apipa_start() |
139 |
{ |
140 |
- local iface="$1" i1= i2= addr= i=0 |
141 |
+ local addr rc |
142 |
|
143 |
- _exists true || return 1 |
144 |
+ _exists || return |
145 |
|
146 |
einfo "Searching for free addresses in 169.254.0.0/16" |
147 |
eindent |
148 |
|
149 |
- while [ ${i} -lt 64516 ]; do |
150 |
- : $(( i1 = (_random % 255) + 1 )) |
151 |
- : $(( i2 = (_random % 255) + 1 )) |
152 |
- |
153 |
- addr="169.254.${i1}.${i2}" |
154 |
- vebegin "${addr}/16" |
155 |
- if ! arping_address "${addr}"; then |
156 |
- eval config_${config_index}="\"${addr}/16 broadcast 169.254.255.255\"" |
157 |
- : $(( config_index -= 1 )) |
158 |
- veend 0 |
159 |
- eoutdent |
160 |
- return 0 |
161 |
- fi |
162 |
+ exec 3>&1 |
163 |
+ addr=$( |
164 |
+ _random_apipa_octets | |
165 |
+ { |
166 |
+ while read -r i1 i2; do |
167 |
+ addr="169.254.${i1}.${i2}" |
168 |
+ vebegin "${addr}/16" >&3 |
169 |
+ if ! arping_address "${addr}" >&3; then |
170 |
+ printf '%s\n' "${addr}" |
171 |
+ exit 0 |
172 |
+ fi |
173 |
+ done |
174 |
+ exit 1 |
175 |
+ } |
176 |
+ ) |
177 |
+ rc=$? |
178 |
+ exec 3>&- |
179 |
|
180 |
- : $(( i += 1 )) |
181 |
- done |
182 |
+ if [ "$rc" = 0 ]; then |
183 |
+ eval "config_${config_index}=\"\${addr}/16 broadcast 169.254.255.255\"" |
184 |
+ : $(( config_index -= 1 )) |
185 |
+ veend 0 |
186 |
+ else |
187 |
+ eerror "No free address found!" |
188 |
+ fi |
189 |
|
190 |
- eerror "No free address found!" |
191 |
eoutdent |
192 |
- return 1 |
193 |
+ return "$rc" |
194 |
} |