-
Notifications
You must be signed in to change notification settings - Fork 521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
netdog: refactor to prepare for upcoming additions #2330
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
use super::{error, print_json, Result}; | ||
use crate::CURRENT_IP; | ||
use argh::FromArgs; | ||
use dns_lookup::lookup_addr; | ||
use snafu::ResultExt; | ||
use std::fs; | ||
use std::net::IpAddr; | ||
use std::str::FromStr; | ||
|
||
#[derive(FromArgs, PartialEq, Debug)] | ||
#[argh(subcommand, name = "generate-hostname")] | ||
/// Generate hostname from DNS reverse lookup or use current IP | ||
pub(crate) struct GenerateHostnameArgs {} | ||
|
||
/// Attempt to resolve assigned IP address, if unsuccessful use the IP as the hostname. | ||
/// | ||
/// The result is returned as JSON. (intended for use as a settings generator) | ||
pub(crate) fn run() -> Result<()> { | ||
let ip_string = fs::read_to_string(CURRENT_IP) | ||
.context(error::CurrentIpReadFailedSnafu { path: CURRENT_IP })?; | ||
let ip = IpAddr::from_str(&ip_string).context(error::IpFromStringSnafu { ip: &ip_string })?; | ||
let hostname = match lookup_addr(&ip) { | ||
Ok(hostname) => hostname, | ||
Err(e) => { | ||
eprintln!("Reverse DNS lookup failed: {}", e); | ||
ip_string | ||
} | ||
}; | ||
|
||
// sundog expects JSON-serialized output | ||
print_json(hostname) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
use super::{error, Result}; | ||
use crate::{net_config, DEFAULT_NET_CONFIG_FILE, KERNEL_CMDLINE, PRIMARY_INTERFACE}; | ||
use argh::FromArgs; | ||
use snafu::{OptionExt, ResultExt}; | ||
use std::{fs, path::Path}; | ||
|
||
#[derive(FromArgs, PartialEq, Debug)] | ||
#[argh(subcommand, name = "generate-net-config")] | ||
/// Generate wicked network configuration | ||
pub(crate) struct GenerateNetConfigArgs {} | ||
|
||
/// Generate configuration for network interfaces. | ||
pub(crate) fn run() -> Result<()> { | ||
let maybe_net_config = if Path::exists(Path::new(DEFAULT_NET_CONFIG_FILE)) { | ||
net_config::from_path(DEFAULT_NET_CONFIG_FILE).context(error::NetConfigParseSnafu { | ||
path: DEFAULT_NET_CONFIG_FILE, | ||
})? | ||
} else { | ||
net_config::from_command_line(KERNEL_CMDLINE).context(error::NetConfigParseSnafu { | ||
path: KERNEL_CMDLINE, | ||
})? | ||
}; | ||
|
||
// `maybe_net_config` could be `None` if no interfaces were defined | ||
let net_config = match maybe_net_config { | ||
Some(net_config) => net_config, | ||
None => { | ||
eprintln!("No network interfaces were configured"); | ||
return Ok(()); | ||
} | ||
}; | ||
|
||
let primary_interface = net_config | ||
.primary_interface() | ||
.context(error::GetPrimaryInterfaceSnafu)?; | ||
write_primary_interface(primary_interface)?; | ||
|
||
let wicked_interfaces = net_config.as_wicked_interfaces(); | ||
for interface in wicked_interfaces { | ||
interface | ||
.write_config_file() | ||
.context(error::InterfaceConfigWriteSnafu)?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Persist the primary interface name to file | ||
fn write_primary_interface<S>(interface: S) -> Result<()> | ||
where | ||
S: AsRef<str>, | ||
{ | ||
let interface = interface.as_ref(); | ||
fs::write(PRIMARY_INTERFACE, interface).context(error::PrimaryInterfaceWriteSnafu { | ||
path: PRIMARY_INTERFACE, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
use super::{error, InterfaceFamily, InterfaceType, Result}; | ||
use crate::lease::LeaseInfo; | ||
use crate::{CURRENT_IP, PRIMARY_INTERFACE, RESOLV_CONF}; | ||
use argh::FromArgs; | ||
use rand::prelude::SliceRandom; | ||
use rand::thread_rng; | ||
use snafu::ResultExt; | ||
use std::fmt::Write; | ||
use std::fs; | ||
use std::net::IpAddr; | ||
use std::path::PathBuf; | ||
|
||
#[derive(FromArgs, PartialEq, Debug)] | ||
#[argh(subcommand, name = "install")] | ||
/// Write resolv.conf and current IP to disk | ||
pub(crate) struct InstallArgs { | ||
#[argh(option, short = 'i')] | ||
/// name of the network interface | ||
interface_name: String, | ||
|
||
#[argh(option, short = 't')] | ||
/// network interface type | ||
interface_type: InterfaceType, | ||
|
||
#[argh(option, short = 'f')] | ||
/// network interface family (ipv4/6) | ||
interface_family: InterfaceFamily, | ||
|
||
#[argh(positional)] | ||
/// lease info data file | ||
data_file: PathBuf, | ||
|
||
#[argh(positional)] | ||
// wicked adds `info` to the call to this program. We don't do anything with it but must | ||
// be able to parse the option to avoid failing | ||
/// ignored | ||
info: Option<String>, | ||
} | ||
|
||
pub(crate) fn run(args: InstallArgs) -> Result<()> { | ||
// Wicked doesn't mangle interface names, but let's be defensive. | ||
let install_interface = args.interface_name.trim().to_lowercase(); | ||
let primary_interface = fs::read_to_string(PRIMARY_INTERFACE) | ||
.context(error::PrimaryInterfaceReadSnafu { | ||
path: PRIMARY_INTERFACE, | ||
})? | ||
.trim() | ||
.to_lowercase(); | ||
|
||
if install_interface != primary_interface { | ||
return Ok(()); | ||
} | ||
|
||
match (&args.interface_type, &args.interface_family) { | ||
(InterfaceType::Dhcp, InterfaceFamily::Ipv4) => { | ||
let info = | ||
LeaseInfo::from_lease(&args.data_file).context(error::LeaseParseFailedSnafu { | ||
path: &args.data_file, | ||
})?; | ||
// Randomize name server order, for libc implementations like musl that send | ||
// queries to the first N servers. | ||
let mut dns_servers: Vec<_> = info.dns_servers.iter().collect(); | ||
dns_servers.shuffle(&mut thread_rng()); | ||
write_resolv_conf(&dns_servers, &info.dns_search)?; | ||
write_current_ip(&info.ip_address.addr())?; | ||
} | ||
_ => eprintln!("Unhandled 'install' command: {:?}", &args), | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Write resolver configuration for libc. | ||
fn write_resolv_conf(dns_servers: &[&IpAddr], dns_search: &Option<Vec<String>>) -> Result<()> { | ||
let mut output = String::new(); | ||
|
||
if let Some(s) = dns_search { | ||
writeln!(output, "search {}", s.join(" ")).context(error::ResolvConfBuildFailedSnafu)?; | ||
} | ||
|
||
for n in dns_servers { | ||
writeln!(output, "nameserver {}", n).context(error::ResolvConfBuildFailedSnafu)?; | ||
} | ||
|
||
fs::write(RESOLV_CONF, output) | ||
.context(error::ResolvConfWriteFailedSnafu { path: RESOLV_CONF })?; | ||
Ok(()) | ||
} | ||
|
||
/// Persist the current IP address to file | ||
fn write_current_ip(ip: &IpAddr) -> Result<()> { | ||
fs::write(CURRENT_IP, ip.to_string()) | ||
.context(error::CurrentIpWriteFailedSnafu { path: CURRENT_IP }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
pub(crate) mod generate_hostname; | ||
pub(crate) mod generate_net_config; | ||
pub(crate) mod install; | ||
pub(crate) mod node_ip; | ||
pub(crate) mod prepare_primary_interface; | ||
pub(crate) mod remove; | ||
pub(crate) mod set_hostname; | ||
|
||
pub(crate) use generate_hostname::GenerateHostnameArgs; | ||
pub(crate) use generate_net_config::GenerateNetConfigArgs; | ||
pub(crate) use install::InstallArgs; | ||
pub(crate) use node_ip::NodeIpArgs; | ||
pub(crate) use prepare_primary_interface::PreparePrimaryInterfaceArgs; | ||
pub(crate) use remove::RemoveArgs; | ||
use serde::{Deserialize, Serialize}; | ||
pub(crate) use set_hostname::SetHostnameArgs; | ||
use snafu::ResultExt; | ||
|
||
#[derive(Debug, PartialEq, Deserialize)] | ||
#[serde(rename_all = "kebab-case")] | ||
enum InterfaceType { | ||
Dhcp, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Deserialize)] | ||
#[serde(rename_all = "kebab-case")] | ||
enum InterfaceFamily { | ||
Ipv4, | ||
Ipv6, | ||
} | ||
|
||
// Implement `from_str()` so argh can attempt to deserialize args into their proper types | ||
derive_fromstr_from_deserialize!(InterfaceType); | ||
derive_fromstr_from_deserialize!(InterfaceFamily); | ||
|
||
/// Helper function that serializes the input to JSON and prints it | ||
fn print_json<S>(val: S) -> Result<()> | ||
where | ||
S: AsRef<str> + Serialize, | ||
{ | ||
let val = val.as_ref(); | ||
let output = serde_json::to_string(val).context(error::JsonSerializeSnafu { output: val })?; | ||
println!("{}", output); | ||
Ok(()) | ||
} | ||
|
||
/// Potential errors during netdog execution | ||
mod error { | ||
use crate::{lease, net_config, wicked}; | ||
use snafu::Snafu; | ||
use std::io; | ||
use std::path::PathBuf; | ||
|
||
#[derive(Debug, Snafu)] | ||
#[snafu(visibility(pub(crate)))] | ||
#[allow(clippy::enum_variant_names)] | ||
pub(crate) enum Error { | ||
#[snafu(display("Failed to write current IP to '{}': {}", path.display(), source))] | ||
CurrentIpWriteFailed { path: PathBuf, source: io::Error }, | ||
|
||
#[snafu(display("Failed to read current IP data in '{}': {}", path.display(), source))] | ||
CurrentIpReadFailed { path: PathBuf, source: io::Error }, | ||
|
||
#[snafu(display("'systemd-sysctl' failed: {}", stderr))] | ||
FailedSystemdSysctl { stderr: String }, | ||
|
||
#[snafu(display("Failed to discern primary interface"))] | ||
GetPrimaryInterface, | ||
|
||
#[snafu(display("Failed to write hostname to '{}': {}", path.display(), source))] | ||
HostnameWriteFailed { path: PathBuf, source: io::Error }, | ||
|
||
#[snafu(display("Failed to write network interface configuration: {}", source))] | ||
InterfaceConfigWrite { source: wicked::Error }, | ||
|
||
#[snafu(display("Invalid IP address '{}': {}", ip, source))] | ||
IpFromString { | ||
ip: String, | ||
source: std::net::AddrParseError, | ||
}, | ||
|
||
#[snafu(display("Error serializing to JSON: '{}': {}", output, source))] | ||
JsonSerialize { | ||
output: String, | ||
source: serde_json::error::Error, | ||
}, | ||
|
||
#[snafu(display("Failed to read/parse lease data in '{}': {}", path.display(), source))] | ||
LeaseParseFailed { path: PathBuf, source: lease::Error }, | ||
|
||
#[snafu(display("Unable to read/parse network config from '{}': {}", path.display(), source))] | ||
NetConfigParse { | ||
path: PathBuf, | ||
source: net_config::Error, | ||
}, | ||
|
||
#[snafu(display("Failed to write primary interface to '{}': {}", path.display(), source))] | ||
PrimaryInterfaceWrite { path: PathBuf, source: io::Error }, | ||
|
||
#[snafu(display("Failed to read primary interface from '{}': {}", path.display(), source))] | ||
PrimaryInterfaceRead { path: PathBuf, source: io::Error }, | ||
|
||
#[snafu(display("Failed to build resolver configuration: {}", source))] | ||
ResolvConfBuildFailed { source: std::fmt::Error }, | ||
|
||
#[snafu(display("Failed to write resolver configuration to '{}': {}", path.display(), source))] | ||
ResolvConfWriteFailed { path: PathBuf, source: io::Error }, | ||
|
||
#[snafu(display("Failed to build sysctl config: {}", source))] | ||
SysctlConfBuild { source: std::fmt::Error }, | ||
|
||
#[snafu(display("Failed to write sysctl config to '{}': {}", path.display(), source))] | ||
SysctlConfWrite { path: PathBuf, source: io::Error }, | ||
|
||
#[snafu(display("Failed to run 'systemd-sysctl': {}", source))] | ||
SystemdSysctlExecution { source: io::Error }, | ||
} | ||
} | ||
|
||
pub(crate) type Result<T> = std::result::Result<T, error::Error>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use super::{error, print_json, Result}; | ||
use crate::CURRENT_IP; | ||
use argh::FromArgs; | ||
use snafu::ResultExt; | ||
use std::fs; | ||
use std::net::IpAddr; | ||
use std::str::FromStr; | ||
|
||
#[derive(FromArgs, PartialEq, Debug)] | ||
#[argh(subcommand, name = "node-ip")] | ||
/// Return the current IP address | ||
pub(crate) struct NodeIpArgs {} | ||
|
||
/// Return the current IP address as JSON (intended for use as a settings generator) | ||
pub(crate) fn run() -> Result<()> { | ||
let ip_string = fs::read_to_string(CURRENT_IP) | ||
.context(error::CurrentIpReadFailedSnafu { path: CURRENT_IP })?; | ||
// Validate that we read a proper IP address | ||
let _ = IpAddr::from_str(&ip_string).context(error::IpFromStringSnafu { ip: &ip_string })?; | ||
|
||
// sundog expects JSON-serialized output | ||
print_json(ip_string) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about moving the constants into some other module, like the parent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent more time than I'd like to admit playing and thinking about that... I ended up keeping them where they were since they get used in the CLI and other modules so I didn't really like them in
cli/mod.rs
and I also didn't really like the idea of making a top-levellib.rs
just for constants.Totally willing to entertain this though - perhaps there's an elegant way I just couldn't come up with! 😄