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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Network testing utility functions for Zebra.

use std::env;

/// The name of the env var that skips Zebra tests which need reliable,
/// fast network connectivity.
///
/// We use a constant so that the compiler detects typos.
const ZEBRA_SKIP_NETWORK_TESTS: &str = "ZEBRA_SKIP_NETWORK_TESTS";

/// The name of the env var that skips Zebra's IPv6 tests.
///
/// We use a constant so that the compiler detects typos.
const ZEBRA_SKIP_IPV6_TESTS: &str = "ZEBRA_SKIP_IPV6_TESTS";

/// Should we skip Zebra tests which need reliable, fast network connectivity?
//
// TODO: separate "good and reliable" from "any network"?
#[allow(clippy::print_stderr)]
pub fn zebra_skip_network_tests() -> bool {
    if env::var_os(ZEBRA_SKIP_NETWORK_TESTS).is_some() {
        // This message is captured by the test runner, use
        // `cargo test -- --nocapture` to see it.
        eprintln!("Skipping network test because '$ZEBRA_SKIP_NETWORK_TESTS' is set.");
        return true;
    }

    false
}

/// Should we skip Zebra tests which need a local IPv6 network stack and
/// IPv6 interface addresses?
///
/// Since `zebra_skip_network_tests` only disables tests which need reliable network connectivity,
/// we allow IPv6 tests even when `ZEBRA_SKIP_NETWORK_TESTS` is set.
#[allow(clippy::print_stderr)]
pub fn zebra_skip_ipv6_tests() -> bool {
    if env::var_os(ZEBRA_SKIP_IPV6_TESTS).is_some() {
        eprintln!("Skipping IPv6 network test because '$ZEBRA_SKIP_IPV6_TESTS' is set.");
        return true;
    }

    // TODO: if we separate "good and reliable" from "any network",
    //       also skip IPv6 tests when we're skipping all network tests.
    false
}

#[cfg(windows)]
/// Returns a random port number from the ephemeral port range.
///
/// Does not check if the port is already in use. It's impossible to do this
/// check in a reliable, cross-platform way.
///
/// ## Usage
///
/// If you want a once-off random unallocated port, use
/// `random_unallocated_port`. Don't use this function if you don't need
/// to - it has a small risk of port conflicts.
///
/// Use this function when you need to use the same random port multiple
/// times. For example: setting up both ends of a connection, or re-using
/// the same port multiple times.
pub fn random_known_port() -> u16 {
    use rand::Rng;
    // Use the intersection of the IANA/Windows/macOS ephemeral port range,
    // and the Linux ephemeral port range:
    //   - https://en.wikipedia.org/wiki/Ephemeral_port#Range
    // excluding ports less than 53500, to avoid:
    //   - typical Hyper-V reservations up to 52000:
    //      - https://github.com/googlevr/gvr-unity-sdk/issues/1002
    //      - https://github.com/docker/for-win/issues/3171
    //   - the MOM-Clear port 51515
    //      - https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/service-overview-and-network-port-requirements
    //   - the LDAP Kerberos byte-swapped reservation 53249
    //      - https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/ldap-kerberos-server-reset-tcp-sessions
    //   - macOS and Windows sequential ephemeral port allocations,
    //     starting from 49152:
    //      - https://dataplane.org/ephemeralports.html

    rand::thread_rng().gen_range(53500..60999)
}

#[cfg(not(windows))]
/// Uses the "magic" port number that tells the operating system to
/// choose a random unallocated port.
///
/// The OS chooses a different port each time it opens a connection or
/// listener with this magic port number.
///
/// Creates a TcpListener to find a random unallocated port, then drops the TcpListener to close the socket.
///
/// Returns the unallocated port number.
///
/// ## Usage
///
/// If you want a once-off random unallocated port, use
/// `random_unallocated_port`. Don't use this function if you don't need
/// to - it has a small risk of port conflicts when there is a delay
/// between this fn call and binding the tcp listener.
///
/// Use this function when you need to use the same random port multiple
/// times. For example: setting up both ends of a connection, or re-using
/// the same port multiple times.
///
/// ## Panics
///
/// If the OS finds no available ports
///
/// If there is an OS error when getting the socket address
pub fn random_known_port() -> u16 {
    use std::net::{Ipv4Addr, SocketAddrV4, TcpListener};

    let host_ip = Ipv4Addr::new(127, 0, 0, 1);
    let socket = TcpListener::bind(SocketAddrV4::new(host_ip, random_unallocated_port()))
        .expect("needs an available port")
        .local_addr()
        .expect("OS error: could not get socket addr");
    socket.port()
}

/// Returns the "magic" port number that tells the operating system to
/// choose a random unallocated port.
///
/// The OS chooses a different port each time it opens a connection or
/// listener with this magic port number.
///
/// ## Usage
///
/// See the usage note for `random_known_port`.
pub fn random_unallocated_port() -> u16 {
    0
}