-
Notifications
You must be signed in to change notification settings - Fork 679
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
Add getifaddrs/ifaddrs wrapper #764
Conversation
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.
This looks like a really nice addition to nix, especially since getifaddrs is so difficult to use on its own. The main thing that bothers me is that 'static lifetime where it doesn't belong. Why did you need to do that?
src/net/ifaddrs/if_flags.rs
Outdated
use libc; | ||
use libc::c_uint; | ||
|
||
bitflags! { |
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.
You can simplify this definition by using the libc_bitflags
macro. See for example SFlag
in src/sys/stat.rs.
src/net/ifaddrs/if_flags.rs
Outdated
/// Driver signals L1 up (since Linux 2.6.17) | ||
const IFF_LOWER_UP = 1<<16; | ||
/// Driver signals dormant (since Linux 2.6.17) | ||
const IFF_DORMANT = 1<<17; |
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.
Linux-specific flags like this one must be guarded with #[cfg()]
directives.
src/net/ifaddrs/mod.rs
Outdated
/// interface configurations. | ||
pub struct InterfaceAddrs { | ||
inner: *mut libc::ifaddrs, | ||
current: Option<&'static libc::ifaddrs>, |
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.
Why 'static
? That's normally for global variables, but libc::getifaddrs
dynamically allocates its storage.
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.
It works just as well using either *mut or &'static; I was told on users.rustlang that it's more "rusty" to do it this way, but I'm happy to revert it if that's better style for the nix crate.
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'm very skeptical. The only reason why your patch compiles now is because you cast that lifetime within an unsafe
block. Could you please show me the post where somebody recommended using a static lifetime?
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.
Here's the post: https://users.rust-lang.org/t/code-review-for-my-first-libc-wrapper/12743/9?u=silverwingedseraph
As I said, I'm happy to switch it back.
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 think you should.
src/net/ifaddrs/mod.rs
Outdated
pub fn query_system() -> Option<Self> { | ||
let mut p = null_mut(); | ||
|
||
// UNSAFETY: Calling libc FFI function, which allocates memory and |
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.
These comments are unnecessary, at least in Nix. The whole crate is riddled with unsafe { libc::...}
calls. Anybody who doesn't know why it's unsafe to call a libc
function will soon figure out.
src/net/ifaddrs/mod.rs
Outdated
pub name: String, | ||
|
||
/// The address assigned to the interface for this protocol. | ||
/// A value of `None` means the libc reported a type of IP address that |
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.
s/a type of IP address/a type of address/
The address might not be IP. It might be a MAC, for example.
src/net/ifaddrs/sockaddr.rs
Outdated
let s_addr_v4: [u8; 4]; | ||
unsafe { s_addr_v4 = transmute(data_v4.sin_addr.s_addr); } | ||
Some(IpAddr::V4( | ||
Ipv4Addr::from(s_addr_v4) |
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.
Actually, std::net::Ipv4Addr
implements From
for big endian u32
, so you don't need to do the array conversion.
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 have tried this and it results in having backwards addresses (lo
with the address 1.0.0.127
, et cetera).
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.
This sounds like a bug in the standard library, then. If you can reproduce it in a standalone test program, please report it upstream.
src/net/ifaddrs/sockaddr.rs
Outdated
/// The caller is responsible for guaranteeing that the provided reference | ||
/// refers to valid memory. | ||
// Allowing unsused_unsafe because I like to annotate unsafety | ||
#[allow(unused_unsafe)] |
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.
You should not use allow(unused_unsafe)
. Either the entire function should be unsafe
, or it should contain some unsafe
blocks. Not both.
src/net/ifaddrs/mod.rs
Outdated
#[cfg(test)] | ||
mod tests { | ||
#[test] | ||
fn tests_get_if_addrs() { |
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.
s/get_if_addrs/getifaddrs/
src/net/ifaddrs/mod.rs
Outdated
fn tests_get_if_addrs() { | ||
let ifs = super::InterfaceAddrs::query_system().unwrap(); | ||
for i in ifs { | ||
println!("{}:", i.name); |
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.
This isn't much of a test, because it doesn't verify anything. It's really an example, and should go in the examples directory. Making a proper test would be hard. You'd probably have to compare against the output of ifconfig
.
src/net/ifaddrs/mod.rs
Outdated
impl InterfaceAddrs { | ||
/// Produce an `InterfaceAddrs` from the system's information. Returns `None` | ||
/// if there are no interfaces to be inspected. | ||
pub fn query_system() -> Option<Self> { |
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.
May as well name this function getifaddrs
. That's the nix convention. We usually name functions the same as the underlying C function, even though the API is rustier.
src/net/ifaddrs/iff_flags.rs
Outdated
// These flags are available on modern Linuxes | ||
#[cfg(any(target_os = "linux", target_os = "android"))] | ||
/// Driver signals L1 up (since Linux 2.6.17) | ||
const IFF_LOWER_UP = 1<<16; |
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.
All constants should be defined in libc if appropriate. Please upstream these constants and then switch to the libc_bitflags
macro that's defined in nix (see src/macros).
First off, thanks for submitting this PR. Most of our functionality is contributed by others rather than us maintainers. Looking briefly through the code, I think it'd be appropriate to include in nix, and @asomers hasn't commented otherwise. We'll just need to do some minor cleanups to the code to bring it inline with nix's conventions. One thing that this PR is missing is a changelog entry describing the addition of this API. |
Thanks to both of you for commenting so extensively! Many of @asomers concerns are addressed in a pending commit (which will be pushed after this comment). Remaining:
I'll do these over the weekend as much as possible, once my homework is done (darn college getting in the way of open source) |
src/net/ifaddrs/sockaddr.rs
Outdated
/// It returns `None` if the libc reports a type of address that `std::net` | ||
/// doesn't understand, or if the given `sockaddr_input` was null. | ||
/// It returns `None` if the libc reports a type of address other than | ||
/// IPv4, IPv6, or EUI-84 MAC, or if the given `sockaddr_input` was null. |
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.
s/84/48/
src/net/ifaddrs/sockaddr.rs
Outdated
Some(IpAddr::V6(Ipv6Addr::from(data_v6.sin6_addr.s6_addr)).into()) | ||
} | ||
libc::AF_PACKET => { | ||
let data_mac: &libc::sockaddr_ll = transmute(sa); |
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.
On FreeBSD, you should use sockaddr_dl
src/net/ifaddrs/sockaddr.rs
Outdated
let data_v6: &libc::sockaddr_in6 = transmute(sa); | ||
Some(IpAddr::V6(Ipv6Addr::from(data_v6.sin6_addr.s6_addr)).into()) | ||
} | ||
libc::AF_PACKET => { |
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.
Is AF_PACKET
a Linux thing? In the BSD world, it's AF_LINK
.
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.
Yes, this is a Linux thing. Unfortunately I don't have a BSD box to test on, but I'll try to write conditionally compiled code for this.
src/net/ifaddrs/sockaddr.rs
Outdated
} | ||
libc::AF_PACKET => { | ||
let data_mac: &libc::sockaddr_ll = transmute(sa); | ||
if data_mac.sll_halen != 6 { |
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.
FYI, Infiniband addresses are longer than 6 bytes. I think it's fine for Nix to ignore them, at least for now.
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.
Yes, this is intended to be implemented later.
src/net/ifaddrs/sockaddr.rs
Outdated
// 6, it's not EUI48 and we can't handle it. | ||
} else { | ||
let a = data_mac.sll_addr; | ||
let eui: Eui48 = [a[0], a[1], a[2], a[3], a[4], a[5]]; |
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.
Can this be expressed as an array operation?
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.
Not as far as I can tell, unfortunately; that is, there's no way to go from data_mac.sll_addr[0..6]
(&[u8]
) to Eui48
(which is [u8; 6]
) except explicitly, because there's no guarantee that &[u8]
is 6 bytes long.
We still need some tests here. Fortunately, your code that calls |
|
||
/// Converts a `libc::sockaddr` into an `Option<IfAddrValue>`. | ||
/// | ||
/// It returns `None` if the libc reports a type of address other than |
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've been chewing on this, and I've concluded that we're doing it wrong. There are two problems:
- Nix consumers can't extend this function to handle weird stuff like
AF_IPX
. - Handling link addresses is too platform specific. That functionality probably doesn't belong in Nix at all.
Both of these problems share a common solution. For unknown address families, sockaddr_to_ifaddrvalue
should return Other(&libc::sockaddr)
instead of None
. That way Nix consumers can do whatever they need to handle AF_LINK
or anything else.
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 like that solution a lot, actually. I'll go ahead and implement that.
Note that a consequence of the change to returning |
Add additional interface flags (IFF_) Adds the three missing IFF_ constants (IFF_LOWER_UP, IFF_DORMANT, and IFF_ECHO) per nix-rust/nix#764
OK, my checklist is done. I'd love to have a code review of the current state of this at your leisure @asomers |
src/net/ifaddrs/mod.rs
Outdated
/// `Err(())` will be returned if `p` was void. | ||
pub unsafe fn from_raw(p: *mut libc::ifaddrs) -> ::std::result::Result<Self, ()> { | ||
match p.as_ref() { | ||
Some(r) => Ok(Self { |
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.
This fails to compile. You should change Self
to InterfaceAddrs
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.
It compiles for me? Something must be screwy with my Rustup, I'll change it. Thanks.
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.
You fixed the wrong line. Just check the Travis build log to see the error I'm talking about. If it works for you, that's probably because you're using a newer version of Rust than Travis. We're using 1.13.0 in Travis.
assert_eq!(hm.len(), 2, "Expected 2 interfaces, found {}.", hm.len()); | ||
|
||
assert!(hm.contains_key("test_lo")); | ||
assert!(hm.contains_key("test_ext")); |
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.
How about assertions for the addresses in hm
? How about some IPv6 addresses and AF_LINK addresses in the mix?
src/net/ifaddrs/mod.rs
Outdated
/// | ||
/// # Errors | ||
/// `Err(())` will be returned if `p` was void. | ||
pub unsafe fn from_raw(p: *mut libc::ifaddrs) -> ::std::result::Result<Self, ()> { |
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.
Since this function is only used by test code, it should not be pub
src/net/ifaddrs/mod.rs
Outdated
inner: *mut libc::ifaddrs, | ||
current: Option<&'static libc::ifaddrs>, | ||
current: Option<&'a libc::ifaddrs>, | ||
do_free: bool, |
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.
It looks like do_free
only exists to support your test code. You can eliminate it by using mem::forget
in the test code to prevent drop
from being run.
src/net/ifaddrs/mod.rs
Outdated
@@ -101,16 +127,17 @@ impl InterfaceAddrs { | |||
Some(r) => Ok(Self { |
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.
Once you eliminate do_free
, this section can turn into a call to from_raw
.
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.
This all looks like relatively clean, but I'm not certain about the need for from_raw
, but I don't fully understand the interface and it looks like @asomers has some suggestions for revising the interface that may overlap with my comments there.
//! # Examples | ||
//! | ||
//! You can access the basic information of the system in a few lines. | ||
//! The following program prints all the known addresses. |
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.
Let's get a colon at the end of this line.
//! println!("\t{:?}", addr); | ||
//! } | ||
//! } | ||
//! |
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.
Remove this blank line.
//! } | ||
//! | ||
//! ``` | ||
//! |
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.
And this one.
IFF_AUTOMEDIA as c_uint; | ||
/// The addresses are lost when the interface goes down. | ||
IFF_DYNAMIC as c_uint; | ||
|
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.
Please remove this blank line.
IFF_DYNAMIC as c_uint; | ||
|
||
// These flags are available on modern Linuxes | ||
#[cfg(any(target_os = "linux", target_os = "android"))] |
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.
The OSes here should be listed alphabetically.
// UNSAFETY: Calling libc FFI function which frees previously allocated | ||
// memory. | ||
unsafe { | ||
// Ask the libc to drop free the memory it allocated when |
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.
"to free the memory" I think is what this should read.
pub struct InterfaceAddr<'a> { | ||
/// The name of the interface | ||
pub name: String, | ||
|
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.
Please remove these blank lines from between the elements in this struct.
|
||
|
||
/// Represents the configuration and state of a network interface. | ||
/// Interfaces are uniquely identified by name, and each interface is likely |
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.
Needs a blank line above for the generated docs to look pretty (run rustdoc to see what I'm talking about).
} | ||
} | ||
|
||
|
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.
Remove this blank line. Should only be 1 between things in a file.
match sa.sa_family as i32 { | ||
libc::AF_INET => { | ||
let data_v4: &libc::sockaddr_in = transmute(sa); | ||
// Transmuting a u32 into a [u8; 4] because |
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.
This should be wrapped to the same number of columns as the code. We should be wrapping everything to 99 characters (which is the Rust standard).
This code is from my
getaddrs
crate, which I think fits quite well into this crate. However, I am aware that there are a number of issues with integrating this code withnix
, and would really appreciate some help dealing with those issues.nix
-provided wrappersMany thanks for assistance.