diff --git a/Cargo.lock b/Cargo.lock index b471680..0b390f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,12 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -520,6 +526,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "libc" version = "0.2.155" @@ -603,6 +618,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "object" version = "0.36.3" @@ -683,6 +704,97 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pnet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.72", +] + +[[package]] +name = "pnet_macros_support" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + [[package]] name = "polyval" version = "0.6.2" @@ -816,6 +928,7 @@ dependencies = [ "generic-array", "log", "packet", + "pnet", "rand", "serde", "serde_derive", diff --git a/Cargo.toml b/Cargo.toml index a5f364b..48aa078 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ tun2 = "2.0.5" packet = "0.1.4" ctrlc2 = "3.5" crossbeam-channel = "0.5.13" +pnet = "0.35.0" \ No newline at end of file diff --git a/src/eth_util.rs b/src/eth_util.rs new file mode 100644 index 0000000..953b8a3 --- /dev/null +++ b/src/eth_util.rs @@ -0,0 +1,250 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::time::{Duration, Instant}; +use std::{env, process}; + +use pnet::datalink::{Channel, DataLinkReceiver, DataLinkSender, NetworkInterface}; +use pnet::packet::arp::{ArpHardwareTypes, ArpOperations, ArpPacket, MutableArpPacket}; +use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket}; +use pnet::packet::icmpv6::ndp::{ + MutableNdpOptionPacket, MutableNeighborSolicitPacket, NdpOptionPacket, NdpOptionTypes, + NeighborAdvertPacket, NeighborSolicitPacket, +}; +use pnet::packet::icmpv6::{self, Icmpv6Types, MutableIcmpv6Packet}; +use pnet::packet::ip::IpNextHeaderProtocols; +use pnet::packet::ipv6::{Ipv6Packet, MutableIpv6Packet}; +use pnet::util::MacAddr; + +const TIMEOUT: Duration = Duration::from_secs(10); + +// Constants used to help locate our nested packets +const PKT_ETH_SIZE: usize = EthernetPacket::minimum_packet_size(); +const PKT_ARP_SIZE: usize = ArpPacket::minimum_packet_size(); +const PKT_IP6_SIZE: usize = Ipv6Packet::minimum_packet_size(); +const PKT_NDP_SOL_SIZE: usize = NeighborSolicitPacket::minimum_packet_size(); +const PKT_NDP_ADV_SIZE: usize = NeighborAdvertPacket::minimum_packet_size(); +const PKT_OPT_SIZE: usize = NdpOptionPacket::minimum_packet_size(); +const PKT_MAC_SIZE: usize = 6; + +const PKT_ARP_OFFSET: usize = PKT_ETH_SIZE; +const PKT_IP6_OFFSET: usize = PKT_ETH_SIZE; +const PKT_NDP_OFFSET: usize = PKT_IP6_OFFSET + PKT_IP6_SIZE; + +const PKT_MIN_ARP_RESP_SIZE: usize = PKT_ETH_SIZE + PKT_ARP_SIZE; +const PKT_MIN_NDP_RESP_SIZE: usize = PKT_ETH_SIZE + PKT_IP6_SIZE + PKT_NDP_ADV_SIZE; + +/// Given an IPv4 or IPv6 address and an interface name +pub fn get_mac(ifname: &str, ip: IpAddr) -> Result { + let interfaces = pnet::datalink::interfaces(); + + let interface = interfaces + .into_iter() + .find(|iface| iface.name == ifname) + .ok_or_else(|| Error::Interface(ifname.into()))?; + + println!("Source MAC address: {}", interface.mac.unwrap()); + + match ip { + IpAddr::V4(ipv4) => get_mac_via_arp(&interface, ipv4), + IpAddr::V6(ipv6) => get_mac_via_ndp(&interface, ipv6), + } +} + +/// Use ARP to locate the MAC of an IPv4 address +fn get_mac_via_arp(interface: &NetworkInterface, target_ipv4: Ipv4Addr) -> Result { + let source_ipv4 = interface + .ips + .iter() + .find_map(|ip| match ip.ip() { + IpAddr::V4(addr) => Some(addr), + IpAddr::V6(_) => None, + }) + .unwrap(); + + let source_mac = interface.mac.unwrap(); + let mut pkt_buf = [0u8; PKT_ETH_SIZE + PKT_ARP_SIZE]; + + // Use scope blocks so we can reborrow our buffer + { + // Build our base ethernet frame + let mut pkt_eth = MutableEthernetPacket::new(&mut pkt_buf).unwrap(); + + pkt_eth.set_destination(MacAddr::broadcast()); + pkt_eth.set_source(interface.mac.unwrap()); + pkt_eth.set_ethertype(EtherTypes::Arp); + } + + { + // Build the ARP frame on top of the ethernet frame + let mut pkt_arp = MutableArpPacket::new(&mut pkt_buf[PKT_ARP_OFFSET..]).unwrap(); + + pkt_arp.set_hardware_type(ArpHardwareTypes::Ethernet); + pkt_arp.set_protocol_type(EtherTypes::Ipv4); + pkt_arp.set_hw_addr_len(6); + pkt_arp.set_proto_addr_len(4); + pkt_arp.set_operation(ArpOperations::Request); + pkt_arp.set_sender_hw_addr(interface.mac.unwrap()); + pkt_arp.set_sender_proto_addr(source_ipv4); + pkt_arp.set_target_hw_addr(MacAddr::zero()); + pkt_arp.set_target_proto_addr(target_ipv4); + } + + let (mut sender, mut receiver) = build_eth_channel(interface); + let start = Instant::now(); + + // Send to the broadcast address + sender.send_to(&pkt_buf, None).unwrap().unwrap(); + eprintln!("Sent ARP request"); + + // Zero buffer for sanity check + pkt_buf.fill(0); + + loop { + let buf = receiver.next().unwrap(); + + if buf.len() < PKT_MIN_ARP_RESP_SIZE { + timeout_check(start)?; + continue; + } + + let pkt_arp = ArpPacket::new(&buf[PKT_ARP_OFFSET..]).unwrap(); + + if pkt_arp.get_sender_proto_addr() == target_ipv4 + && pkt_arp.get_target_hw_addr() == source_mac + { + return Ok(pkt_arp.get_sender_hw_addr()); + } + + timeout_check(start)?; + } +} + +/// Use NDP to locate the MAC of an IPv6 address +fn get_mac_via_ndp(interface: &NetworkInterface, target_ipv6: Ipv6Addr) -> Result { + let source_ipv6 = interface + .ips + .iter() + .find_map(|ip| match ip.ip() { + IpAddr::V4(_) => None, + IpAddr::V6(addr) => Some(addr), + }) + .unwrap(); + + let source_mac = interface.mac.unwrap(); + let mut pkt_buf = + [0u8; PKT_ETH_SIZE + PKT_IP6_SIZE + PKT_NDP_SOL_SIZE + PKT_OPT_SIZE + PKT_MAC_SIZE]; + + // Use scope blocks so we can reborrow our buffer + { + // Build our base ethernet frame + let mut pkt_eth = MutableEthernetPacket::new(&mut pkt_buf).unwrap(); + + pkt_eth.set_destination(MacAddr::broadcast()); + pkt_eth.set_source(interface.mac.unwrap()); + pkt_eth.set_ethertype(EtherTypes::Ipv6); + } + + { + // Build the ipv6 packet + let mut pkt_ipv6 = MutableIpv6Packet::new(&mut pkt_buf[PKT_IP6_OFFSET..]).unwrap(); + + pkt_ipv6.set_version(0x06); + pkt_ipv6.set_payload_length( + (PKT_NDP_SOL_SIZE + PKT_OPT_SIZE + PKT_MAC_SIZE) + .try_into() + .unwrap(), + ); + pkt_ipv6.set_next_header(IpNextHeaderProtocols::Icmpv6); + pkt_ipv6.set_hop_limit(u8::MAX); + pkt_ipv6.set_source(source_ipv6); + pkt_ipv6.set_destination(target_ipv6); + } + + { + // Build the NDP packet + let mut pkt_ndp = + MutableNeighborSolicitPacket::new(&mut pkt_buf[PKT_NDP_OFFSET..]).unwrap(); + pkt_ndp.set_target_addr(target_ipv6); + pkt_ndp.set_icmpv6_type(Icmpv6Types::NeighborSolicit); + pkt_ndp.set_checksum(0x3131); + + let mut pkt_opt = MutableNdpOptionPacket::new(pkt_ndp.get_options_raw_mut()).unwrap(); + pkt_opt.set_option_type(NdpOptionTypes::SourceLLAddr); + pkt_opt.set_length(octets_len(PKT_MAC_SIZE)); + pkt_opt.set_data(&source_mac.octets()); + } + + { + // Set the checksum (part of the NDP packet) + let mut pkt_icmpv6 = MutableIcmpv6Packet::new(&mut pkt_buf[PKT_NDP_OFFSET..]).unwrap(); + pkt_icmpv6.set_checksum(icmpv6::checksum( + &pkt_icmpv6.to_immutable(), + &source_ipv6, + &target_ipv6, + )); + } + + let (mut sender, mut receiver) = build_eth_channel(interface); + let start = Instant::now(); + + // Send to the broadcast address + sender.send_to(&pkt_buf, None).unwrap().unwrap(); + eprintln!("Sent NDP request"); + + // Zero buffer for sanity check + pkt_buf.fill(0); + + loop { + let buf = receiver.next().unwrap(); + + if buf.len() < PKT_MIN_NDP_RESP_SIZE { + timeout_check(start)?; + continue; + } + + let pkt_eth = EthernetPacket::new(buf).unwrap(); + let pkt_ipv6 = Ipv6Packet::new(&buf[PKT_IP6_OFFSET..]).unwrap(); + let _pkt_ndp = NeighborAdvertPacket::new(&buf[PKT_NDP_OFFSET..]).unwrap(); + + if pkt_ipv6.get_source() == target_ipv6 && pkt_eth.get_destination() == source_mac { + return Ok(pkt_eth.get_source()); + } + + timeout_check(start)?; + } +} + +/// Construct a sender/receiver channel from an interface +fn build_eth_channel( + interface: &NetworkInterface, +) -> (Box, Box) { + let cfg = pnet::datalink::Config::default(); + match pnet::datalink::channel(interface, cfg) { + Ok(Channel::Ethernet(tx, rx)) => (tx, rx), + Ok(_) => panic!("Unknown channel type"), + Err(e) => panic!("Channel error: {e}"), + } +} + +/// Length in octets (8bytes) +fn octets_len(len: usize) -> u8 { + // 3 = log2(8) + (len.next_power_of_two() >> 3).try_into().unwrap() +} + +/// Bail if we exceed TIMEOUT +fn timeout_check(start: Instant) -> Result<(), Error> { + if Instant::now().duration_since(start) > TIMEOUT { + Err(Error::Timeout(TIMEOUT)) + } else { + Ok(()) + } +} + +/// Simple error types for this demo +#[derive(Debug)] +pub enum Error { + /// Something didn't happen on time + Timeout(Duration), + /// Interface of this name did not exist + Interface(String), +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3576f45..345c391 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use serde_derive::Deserialize; //mod server; mod tcp_client; mod tcp_server; +mod eth_util; const HEADER: [u8;3] = [0x56, 0x66, 0x76]; const TAIL: [u8;3] = [0x76, 0x66, 0x56]; diff --git a/src/tcp_server.rs b/src/tcp_server.rs index 74e5189..9b3bbff 100644 --- a/src/tcp_server.rs +++ b/src/tcp_server.rs @@ -1,7 +1,7 @@ use crossbeam_channel::{unbounded, Receiver}; use tokio::{io::AsyncWriteExt, net::{TcpListener, TcpSocket, TcpStream}, sync::{mpsc, Mutex}}; use tokio::task::JoinSet; -use packet::{builder::Builder, icmp, ip, Packet}; +use packet::{builder::Builder, icmp, ip, AsPacket, Packet}; use std::io::{Read, Write}; use tun2::BoxError; use log::{error, info, LevelFilter}; @@ -17,6 +17,7 @@ pub async fn server_mode(bind_addr: String) { let mut config = tun2::Configuration::default(); config.address("10.8.0.1"); + config.netmask("255.255.255.0"); config.tun_name("tun0"); config.up(); @@ -32,7 +33,12 @@ pub async fn server_mode(bind_addr: String) { let (dx, mx) = unbounded::>(); tokio::spawn(async move { - while let Ok(bytes) = rx.recv() { + while let Ok(mut bytes) = rx.recv() { + info!("Source ip: {:?}", &bytes[12..=15]); + //bytes[12] = 192; + //bytes[13] = 168; + //bytes[14] = 0; + //bytes[15] = 5; dev_writer.write_all(&bytes).unwrap(); } });