aboutsummaryrefslogtreecommitdiff
blob: 41274fa975c4cb1731b6d514c27a7516c0d715d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# Copyright (c) 2007-2008 Roy Marples <roy@marples.name>
# Released under the 2-clause BSD license.

apipa_depend()
{
	program /sbin/arping /bin/arping
}

_random_bytes_as_int()
{
	local hex num_bytes="$1"

	# While POSIX does not require that /dev/urandom exist, it is a
	# de-facto standard. Therefore, the following approach should be
	# highly portable in practice. In the case of Linux, and unlike BSD
	# this interface does not block in the event that the CSRNG has not
	# yet been seeded. Still, this is acceptable because we do not
	# require a guarantee that the entropy be cryptographically secure.
	# It's also worth noting that Linux >=5.4 is faster at seeding in
	# the absence of RDRAND/RDSEED than previous versions were.
	test -e /dev/urandom &&
	hex=$(
		LC_ALL=C tr -dc '[:xdigit:]' < /dev/urandom |
		dd bs="$(( num_bytes * 2 ))" count=1 2>/dev/null) &&
	test "${#hex}" = "$(( num_bytes * 2 ))" &&
	printf '%d\n' "0x${hex}"
}

_random_apipa_octets()
{
	local seed

	# Obtain a highly random 16-bit seed for use by awk's RNG. In the
	# unlikely event that the seed ends up being empty, awk will seed
	# based on the time of day, with a granularity of one second.
	seed=$(_random_bytes_as_int 2)

	# For APIPA (RFC 3927), the 169.254.0.0/16 address block is
	# reserved. This provides 65024 addresses, having accounted for the
	# fact that the first and last /24 are reserved for future use.
	awk "BEGIN {
		srand($seed)
		for (i=256; i<65280; i++) print rand() \" \" i
	}" |
	sort -k 1,1 -n |
	POSIXLY_CORRECT=1 awk '{
		hex = sprintf("%04x",$2)
		printf("%d %d\n", "0x" substr(hex,1,2), "0x" substr(hex,3,2))
	}'
}

apipa_start()
{
	local addr rc

	_exists || return

	einfo "Searching for free addresses in 169.254.0.0/16"
	eindent

	exec 3>&1
	addr=$(
		_random_apipa_octets |
		{
			while read -r i1 i2; do
				addr="169.254.${i1}.${i2}"
				vebegin "${addr}/16" >&3
				if ! arping_address "${addr}" >&3; then
					printf '%s\n' "${addr}"
					exit 0
				fi
			done
			exit 1
		}
	)
	rc=$?
	exec 3>&-

	if [ "$rc" = 0 ]; then
		eval "config_${config_index}=\"\${addr}/16 broadcast 169.254.255.255\""
		: $(( config_index -= 1 ))
		veend 0
	else
		eerror "No free address found!"
	fi

	eoutdent
	return "$rc"
}