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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! Helper to receive the IP of the local machine.
//
//  Copyright (C) 2021 Zhang Maiyun <myzhang1029@hotmail.com>
//
//  This file is part of DNS updater.
//
//  DNS updater is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  DNS updater is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with DNS updater.  If not, see <https://www.gnu.org/licenses/>.
//

use crate::libc_getips::get_iface_addrs;
use crate::{Error, IpType, Result};
use log::{debug, info};
use std::net::IpAddr;
use std::process::{Output, Stdio};
use std::str::FromStr;
use tokio::process::Command;

/// Run a chain of ip/ifconfig commands and returns the output of first succeeded one
async fn chain_ip_cmd_until_succeed(nic: &str) -> Result<Output> {
    let commands = [
        // First try to use `ip`
        (
            "ip",
            vec![
                "address", "show", "dev", nic,
                // This scope filters out unique-local addresses
                "scope", "global",
            ],
        ),
        // If that failed, try (BSD) ifconfig
        ("ifconfig", vec!["-L", nic, "inet6"]),
        // Linux ifconfig cannot distinguish between RFC 3041 temporary/permanent addresses
        ("ifconfig", vec![nic]),
    ];
    // Record only the last failure
    let mut last_error: Option<Error> = None;
    for (cmd, args) in commands {
        let mut command = Command::new(cmd);
        command.stdout(Stdio::piped());
        debug!("Running command {:?} with arguments {:?}", cmd, args);
        let output = command.args(&args).output().await;
        match output {
            Ok(output) => {
                if output.status.success() {
                    return Ok(output);
                }
                // Since a chain of commands are executed, these are not really errors
                debug!(
                    "Command {:?} failed with status: {}",
                    command, output.status
                );
                last_error = Some(Error::NonZeroExit(output.status));
            }
            Err(exec_error) => {
                debug!(
                    "Command {:?} failed to be executed: {}",
                    command, exec_error
                );
                last_error = Some(Error::IoError(exec_error));
            }
        }
    }
    info!("None of the commands to extract the IPv6 address succeeded.");
    // Not failable
    Err(last_error.unwrap())
}

/// Try to get a IPv6 address from a interface with `ifconfig` or `ip` if possible.
/// Two Results are used to indicate a "determining error":
/// if the outer Result is an Err variant, it indicates an error with the current IP backend.
/// But if the inner Result is an Err variant, no other backends will be tried.
async fn get_ipv6_ifconfig_ip(nic: &str, permanent: bool) -> Result<Result<IpAddr>> {
    let out = chain_ip_cmd_until_succeed(nic).await?;
    // Extract output
    let out_br = out.stdout.split(|c| *c == b'\n');
    // Some systems use temporary addresses and one permanent address.
    // The second fields indicates whether the "secured"/permanent flag is present.
    let mut addrs: Vec<(IpAddr, bool)> = Vec::with_capacity(4);
    // Extract addresses line by line
    for line in out_br {
        let line = String::from_utf8(line.to_vec())?;
        let fields: Vec<String> = line
            .split_whitespace()
            .map(std::string::ToString::to_string)
            .collect();
        // A shorter one is certainly not an entry
        // Check if the label is "inet6"
        if fields.len() > 1 && fields[0] == "inet6" {
            let address_stripped = match fields[1].split_once("/") {
                // `ip` includes the prefix length in the address
                Some((addr, _prefixlen)) => addr,
                // but `ifconfig` doesn't
                None => &fields[1],
            };
            if let Ok(addr6) = IpAddr::from_str(address_stripped) {
                // If "secured" (for RFC 3041, ifconfig) or "mngtmpaddr" (RFC 3041, ip) is in the flags, it is permanent
                let is_perm = fields.iter().any(|f| f == "secured" || f == "mngtmpaddr")
                // But any "temporary" tells us it is not.
                    && fields.iter().all(|f| f != "temporary");
                // Treating non-RFC-3041 interface's addresses all the same
                if !addr6.is_loopback() {
                    addrs.push((addr6, is_perm));
                }
            }
        }
    }
    if addrs.is_empty() {
        debug!("Short-circuting NoAddress because an ip command succeeded without addresses");
        Ok(Err(crate::Error::NoAddress))
    } else {
        Ok(Ok(addrs
            .iter()
            // If is_perm == permanent, it is the one we are looking for
            .filter(|(_, is_perm)| *is_perm == permanent)
            .map(|(addr, _)| *addr)
            .next()
            .unwrap_or(addrs[0].0)))
    }
}

/// Force-cast $id: IpAddr to Ipv4Addr
macro_rules! cast_ipv4 {
    ($id: expr) => {
        if let IpAddr::V4(ip) = $id {
            ip
        } else {
            unreachable!()
        }
    };
}

/// Force-cast $id: IpAddr to Ipv6Addr
macro_rules! cast_ipv6 {
    ($id: expr) => {
        if let IpAddr::V6(ip) = $id {
            ip
        } else {
            unreachable!()
        }
    };
}

/// Get a local IPv4 address on the specified interface.
///
/// # Errors
///
/// This function propagates the error from `libc_getips::get_iface_addrs`.
pub fn get_local_ipv4(nic: Option<&str>) -> Result<IpAddr> {
    let ipv4_addrs = get_iface_addrs(Some(IpType::Ipv4), nic)?;
    let ipv4_addrs: Vec<&IpAddr> = ipv4_addrs
        .iter()
        // Remove loopback, link local, and unspecified
        .filter(|addr| {
            let remove =
                !addr.is_loopback() && !cast_ipv4!(addr).is_link_local() && !addr.is_unspecified();
            if remove {
                debug!(
                    "Removing address {:?} because it is loopback/link local/unspecified",
                    addr
                );
            }
            remove
        })
        .collect();
    // Prefer the ones that are likely global
    // XXX: `IpAddr::is_global` is probably a better choice but it's currently unstable.
    let first_non_local_addr: Option<&&IpAddr> = ipv4_addrs
        .iter()
        .find(|addr| !cast_ipv4!(addr).is_private());
    first_non_local_addr.map_or_else(|| Ok(*ipv4_addrs[0]), |addr| Ok(**addr))
}

/// Get a local IPv6 address on the specified interface.
///
/// # Errors
///
/// This function returns `NoAddress` if the IP commands, or in case that none
/// of those commands are available, it propagates the error from `libc_getips::get_iface_addrs`.
pub async fn get_local_ipv6(nic: Option<&str>) -> Result<IpAddr> {
    if let Some(nic) = nic {
        if let Ok(command_result) = get_ipv6_ifconfig_ip(nic, true).await {
            // TODO: ipconfig.exe IPv6 backend
            return command_result;
        }
    };
    let ipv6_addrs = get_iface_addrs(Some(IpType::Ipv6), nic)?;
    let ipv6_addrs: Vec<&IpAddr> = ipv6_addrs
        .iter()
        // Remove loopback, link local, and unspecified
        .filter(|addr| {
            !addr.is_loopback()
                && !addr.is_unspecified()
                && !(cast_ipv6!(addr).segments()[0] & 0xffc0) == 0xfe80
        })
        .collect();
    // TODO: Prefer the ones that are likely global
    // `IpAddr::is_global` is currently unstable.
    Ok(*ipv6_addrs[0])
}