claim-ip 1.0.0

Respond to ARP replies for a some address on a network interface
use nix::ifaddrs::getifaddrs;
use nix::sys::socket::{
    recvfrom, sendto, socket, AddressFamily, LinkAddr, MsgFlags, SockAddr, SockFlag, SockType,
};
use std::convert::TryFrom;

pub mod arp;
pub mod types;

fn lookup_link_addr(iface: &str) -> Result<LinkAddr, Box<dyn std::error::Error>> {
    for ifaddr in getifaddrs()? {
        if ifaddr.interface_name == iface {
            if let Some(SockAddr::Link(link_addr)) = ifaddr.address {
                return Ok(link_addr);
            }
        }
    }
    Err("interface not found")?
}

fn main() {
    if std::env::args().len() != 3 {
        eprintln!(
            "Usage: {} <iface> <ip>",
            std::env::args()
                .nth(0)
                .as_ref()
                .map(|v| v.as_str())
                .unwrap_or(file!())
        );
        std::process::exit(0);
    }

    let iface = std::env::args().nth(1).expect("no interface supplied");
    let ip: std::net::Ipv4Addr = std::env::args()
        .nth(2)
        .expect("no ip supplied")
        .parse()
        .expect("invalid ip");
    let ifaddr = lookup_link_addr(&iface).expect("failed to lookup link address");
    let ifindex = ifaddr.ifindex();
    let mac = ifaddr.addr();
    println!(
        "Claiming IP {} on {}[{}] for {:x?}",
        ip, iface, ifindex, mac
    );

    let socket = socket(
        AddressFamily::Packet,
        SockType::Datagram,
        SockFlag::empty(),
        None,
    )
    .expect("failed to create packet socket");
    {
        let mut bind_addr = ifaddr.clone();
        bind_addr.0.sll_protocol = (nix::libc::ETH_P_ARP as u16).to_be();
        nix::sys::socket::bind(socket, &SockAddr::Link(bind_addr))
            .expect("failed to bind to interface for arp data");
    }
    let mut rbuf = [0u8; 500];
    let mut wbuf = [0u8; 500];
    loop {
        let (size, from) = match recvfrom(socket, &mut rbuf) {
            Ok(r) => r,
            Err(err) => {
                eprintln!("failed to receive packet: {}", err);
                std::process::exit(1);
            }
        };

        let pkt = &rbuf[0..size];
        let from = match from {
            Some(SockAddr::Link(from)) => from,
            _ => {
                eprintln!("received packet without link address sender: {:?}", from);
                continue;
            }
        };
        eprintln!("received packet from {:?}: {:x?}", from, pkt);

        match arp::Arp::try_from(pkt) {
            Ok(arp::Arp::Request(req)) => {
                eprintln!("received arp request: {:x?}", req);
                if from.addr() != req.sha {
                    eprintln!(
                        "received arp with sender mac {:x?} from mac {:x?}",
                        from.addr(),
                        req.sha
                    );
                }

                if req.tpa == ip {
                    eprintln!("sending arp reply");
                    if let Err(err) = sendto(
                        socket,
                        req.reply(mac)
                            .fill(&mut wbuf)
                            .expect("failed to construct reply packet"),
                        &SockAddr::Link(from),
                        MsgFlags::MSG_DONTWAIT,
                    ) {
                        eprintln!("failed to send arp reply: {}", err);
                    }
                }
            }
            Ok(_) => {}
            Err(_) => {
                eprintln!("failed to decode arp packet");
            }
        }
    }
}