Skip to content
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

Closed
wants to merge 10 commits into from

Conversation

NoraCodes
Copy link

@NoraCodes NoraCodes commented Sep 9, 2017

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 with nix, and would really appreciate some help dealing with those issues.

  • Error handling is basically nonexistent
  • I may not be doing memory management right? This is my first time writing any significant unsafe Rust.
  • This may be a more heavyweight wrapper than other nix-provided wrappers
  • Not properly feature gated, causing build failures (I think? OTOH all the other PRs are failing)

Many thanks for assistance.

Copy link
Member

@asomers asomers left a 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?

use libc;
use libc::c_uint;

bitflags! {
Copy link
Member

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.

/// 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;
Copy link
Member

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.

/// interface configurations.
pub struct InterfaceAddrs {
inner: *mut libc::ifaddrs,
current: Option<&'static libc::ifaddrs>,
Copy link
Member

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.

Copy link
Author

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.

Copy link
Member

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?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should.

pub fn query_system() -> Option<Self> {
let mut p = null_mut();

// UNSAFETY: Calling libc FFI function, which allocates memory and
Copy link
Member

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.

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
Copy link
Member

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.

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)
Copy link
Member

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.

Copy link
Author

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).

Copy link
Member

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.

/// 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)]
Copy link
Member

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.

#[cfg(test)]
mod tests {
#[test]
fn tests_get_if_addrs() {
Copy link
Member

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/

fn tests_get_if_addrs() {
let ifs = super::InterfaceAddrs::query_system().unwrap();
for i in ifs {
println!("{}:", i.name);
Copy link
Member

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.

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> {
Copy link
Member

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.

// 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;
Copy link
Contributor

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).

@Susurrus
Copy link
Contributor

Susurrus commented Sep 9, 2017

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.

@NoraCodes
Copy link
Author

NoraCodes commented Sep 9, 2017

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:

  • Error handling
  • Testing
  • Upstream IFF_LOWER_UP, IFF_DORMANT, and IFF_ECHO
  • Convert ifaddrs::iff_flags to use libc_bitflags! macro
  • Implement Other variant for non-IP addresses.
  • Switch away from 'static in InterfaceAddrs
  • Make a changelog entry

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)

/// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/84/48/

Some(IpAddr::V6(Ipv6Addr::from(data_v6.sin6_addr.s6_addr)).into())
}
libc::AF_PACKET => {
let data_mac: &libc::sockaddr_ll = transmute(sa);
Copy link
Member

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

let data_v6: &libc::sockaddr_in6 = transmute(sa);
Some(IpAddr::V6(Ipv6Addr::from(data_v6.sin6_addr.s6_addr)).into())
}
libc::AF_PACKET => {
Copy link
Member

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.

Copy link
Author

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.

}
libc::AF_PACKET => {
let data_mac: &libc::sockaddr_ll = transmute(sa);
if data_mac.sll_halen != 6 {
Copy link
Member

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.

Copy link
Author

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.

// 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]];
Copy link
Member

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?

Copy link
Author

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.

@asomers
Copy link
Member

asomers commented Sep 11, 2017

We still need some tests here. Fortunately, your code that calls libc::getifaddrs is well isolated from the code that interprets its output. Could you write a test that takes canned libc::getifaddrs output and converts it to a HashMap?


/// Converts a `libc::sockaddr` into an `Option<IfAddrValue>`.
///
/// It returns `None` if the libc reports a type of address other than
Copy link
Member

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:

  1. Nix consumers can't extend this function to handle weird stuff like AF_IPX.
  2. 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.

Copy link
Author

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.

@NoraCodes
Copy link
Author

Note that a consequence of the change to returning Other(&sockaddr) is that the reference inside InterfaceAddrs now has a sane lifetime.

bors added a commit to rust-lang/libc that referenced this pull request Sep 12, 2017
Add additional interface flags (IFF_)

Adds the three missing IFF_ constants (IFF_LOWER_UP, IFF_DORMANT, and IFF_ECHO) per nix-rust/nix#764
@NoraCodes
Copy link
Author

OK, my checklist is done. I'd love to have a code review of the current state of this at your leisure @asomers

/// `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 {
Copy link
Member

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

Copy link
Author

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.

Copy link
Member

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"));
Copy link
Member

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?

///
/// # Errors
/// `Err(())` will be returned if `p` was void.
pub unsafe fn from_raw(p: *mut libc::ifaddrs) -> ::std::result::Result<Self, ()> {
Copy link
Member

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

inner: *mut libc::ifaddrs,
current: Option<&'static libc::ifaddrs>,
current: Option<&'a libc::ifaddrs>,
do_free: bool,
Copy link
Member

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.

@@ -101,16 +127,17 @@ impl InterfaceAddrs {
Some(r) => Ok(Self {
Copy link
Member

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.

Copy link
Contributor

@Susurrus Susurrus left a 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.
Copy link
Contributor

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);
//! }
//! }
//!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this blank line.

//! }
//!
//! ```
//!
Copy link
Contributor

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;

Copy link
Contributor

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"))]
Copy link
Contributor

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
Copy link
Contributor

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,

Copy link
Contributor

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
Copy link
Contributor

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).

}
}


Copy link
Contributor

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
Copy link
Contributor

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).

bors bot added a commit that referenced this pull request Dec 4, 2017
667: Add support for getifaddrs. r=asomers a=mwanner

Here's a first attempt at a rustian interface for `getifaddrs`, please review.

Changes for the changelog:

- Added `nix::ifaddrs::{getifaddrs, InterfaceAddressIterator,
  InterfaceAddress and InterfaceFlags}`

Closes #650.
Closes #764.
@bors bors bot closed this in #667 Dec 4, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants