Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scripts/* linguist-vendored
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,16 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/

# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# macOS
*.DS_Store
34 changes: 15 additions & 19 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
[workspace]
resolver = "2"
members = [
"nex",
"nex-core",
"nex-datalink",
"nex-macro",
"nex-macro-helper",
"nex-packet",
"nex-socket",
"nex-sys",
"nex-packet-builder"
"nex",
"nex-core",
"nex-datalink",
"nex-packet",
"nex-socket",
"nex-sys"
]

[workspace.package]
version = "0.19.1"
version = "0.20.0"
edition = "2021"
authors = ["shellrow <shellrow@fortnium.com>"]

[workspace.dependencies]
nex-core = { version = "0.19.1", path = "nex-core" }
nex-datalink = { version = "0.19.1", path = "nex-datalink" }
nex-macro = { version = "0.19.1", path = "nex-macro" }
nex-macro-helper = { version = "0.19.1", path = "nex-macro-helper" }
nex-packet = { version = "0.19.1", path = "nex-packet" }
nex-packet-builder = { version = "0.19.1", path = "nex-packet-builder" }
nex-socket = { version = "0.19.1", path = "nex-socket" }
nex-sys = { version = "0.19.1", path = "nex-sys" }
nex-core = { version = "0.20.0", path = "nex-core" }
nex-datalink = { version = "0.20.0", path = "nex-datalink" }
nex-packet = { version = "0.20.0", path = "nex-packet" }
nex-sys = { version = "0.20.0", path = "nex-sys" }
nex-socket = { version = "0.20.0", path = "nex-socket" }
serde = { version = "1" }
libc = "0.2"
netdev = { version = "0.36" }
bytes = "1"
tokio = { version = "1" }
rand = "0.8"
netdev = { version = "0.34" }
9 changes: 0 additions & 9 deletions Cross.toml

This file was deleted.

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 shellrow
Copyright (c) 2023-2025 shellrow

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
16 changes: 2 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ Cross-platform low-level networking library in Rust
It includes a set of modules, each with a specific focus:

- `datalink`: Datalink layer networking.
- `packet`: Low-level packet parsing and building.
- `packet-builder`: High-level packet building.
- `packet`: Low-level packet parsing and building.
- `socket`: Socket-related functionality.

## Upcoming Features
Expand All @@ -27,14 +26,13 @@ To use `nex`, add it as a dependency in your `Cargo.toml`:

```toml
[dependencies]
nex = "0.19"
nex = "0.20"
```

## Using Specific Sub-crates
You can also directly use specific sub-crates by importing them individually.
- `nex-datalink`
- `nex-packet`
- `nex-packet-builder`
- `nex-socket`

If you want to focus on network interfaces, you can use the [netdev](https://github.com/shellrow/netdev).
Expand All @@ -55,13 +53,3 @@ Please note that in order to send and receive raw packets using `nex-datalink` o
On macOS, managing access to the Berkeley Packet Filter (BPF) devices is necessary for send and receive raw packets using `nex-datalink`.
You can use [chmod-bpf](https://github.com/shellrow/chmod-bpf) to automatically manage permissions for BPF devices.
Alternatively, of course, you can also use `sudo` to temporarily grant the necessary permissions.

## Build time requirements for optional feature
The cryptography provider for `nex-socket`'s optional `tls-aws-lc` feature use `aws-lc-rs`. Note that this has some implications on [build-time tool requirements](https://aws.github.io/aws-lc-rs/requirements/index.html), such as requiring cmake on all platforms and nasm on Windows.
**You can use `ring` as the cryptography provider (without additional dependencies) by specifying the `tls` feature.**

## Acknowledgment
This library was heavily inspired by `pnet`, which catalyzed my journey into Rust development.
I am grateful to everyone involved in `pnet` for their pioneering efforts and significant contributions to networking in Rust.

Additionally, thank you to all contributors and maintainers of the projects `nex` depends on for your invaluable work and support.
165 changes: 69 additions & 96 deletions examples/arp.rs
Original file line number Diff line number Diff line change
@@ -1,132 +1,105 @@
//! This example sends ARP request packet to the target and waits for ARP reply packets.
//! Sends ARP request to the target and waits for ARP reply.
//!
//! e.g.
//! Usage:
//! arp <TARGET IPv4 Addr> <INTERFACE NAME>
//!
//! arp 192.168.1.1 eth0
//! Example:
//! arp 192.168.1.1 eth0

use nex::datalink;
use nex::datalink::Channel::Ethernet;
use nex::net::interface::Interface;
use nex::net::interface::{get_interfaces, Interface};
use nex::net::mac::MacAddr;
use nex::packet::ethernet::EtherType;
use nex::packet::frame::Frame;
use nex::packet::frame::ParseOption;
use nex::util::packet_builder::builder::PacketBuilder;
use nex::util::packet_builder::ethernet::EthernetPacketBuilder;
use nex::packet::frame::{Frame, ParseOption};
use nex::packet::builder::ethernet::EthernetPacketBuilder;
use nex_packet::arp::ArpOperation;
use nex_packet_builder::arp::ArpPacketBuilder;
use nex_packet::builder::arp::ArpPacketBuilder;
use nex_packet::packet::Packet;
use std::env;
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::{IpAddr, Ipv4Addr};
use std::process;

const USAGE: &str = "USAGE: arp <TARGET IPv4 Addr> <NETWORK INTERFACE>";

fn main() {
let interface: Interface = match env::args().nth(2) {
Some(n) => {
// Use interface specified by the user
let interfaces: Vec<Interface> = nex::net::interface::get_interfaces();
let interface: Interface = interfaces
.into_iter()
.find(|interface| interface.name == n)
.expect("Failed to get interface information");
interface
}
None => {
// Use the default interface
match Interface::default() {
Ok(interface) => interface,
Err(e) => {
println!("Failed to get default interface: {}", e);
process::exit(1);
}
}
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: arp <TARGET IPv4 Addr> <INTERFACE NAME>");
process::exit(1);
}

let target_ip: Ipv4Addr = match args[1].parse() {
Ok(IpAddr::V4(ipv4)) => ipv4,
Ok(_) => {
eprintln!("IPv6 is not supported. Use ndp instead.");
process::exit(1);
}
};
let dst_ip: Ipv4Addr = match env::args().nth(1) {
Some(target_ip) => match target_ip.parse::<IpAddr>() {
Ok(ip) => match ip {
IpAddr::V4(ipv4) => ipv4,
IpAddr::V6(_ipv6) => {
println!("IPv6 is not supported. Use ndp instead.");
eprintln!("{USAGE}");
process::exit(1);
}
},
Err(e) => {
println!("Failed to parse target ip: {}", e);
eprintln!("{USAGE}");
process::exit(1);
}
},
None => {
println!("Failed to get target ip");
eprintln!("{USAGE}");
Err(e) => {
eprintln!("Failed to parse target IP: {}", e);
process::exit(1);
}
};

let src_ip: Ipv4Addr = interface.ipv4[0].addr();
let interface = match env::args().nth(2) {
Some(name) => get_interfaces()
.into_iter()
.find(|i| i.name == name)
.expect("Failed to get interface"),
None => Interface::default().expect("Failed to get default interface"),
};

let src_mac = interface.mac_addr.clone().expect("No MAC address on interface");
let src_ip = interface.ipv4.get(0).expect("No IPv4 address").addr();

// Create a channel to send/receive packet
let (mut tx, mut rx) = match datalink::channel(&interface, Default::default()) {
Ok(Ethernet(tx, rx)) => (tx, rx),
Ok(_) => panic!("parse_frame: unhandled channel type"),
Err(e) => panic!("parse_frame: unable to create channel: {}", e),
Ok(_) => panic!("Unhandled channel type"),
Err(e) => panic!("Failed to create channel: {}", e),
};

// Packet builder for ARP Request
let mut packet_builder = PacketBuilder::new();
let ethernet_packet_builder = EthernetPacketBuilder {
src_mac: interface.mac_addr.clone().unwrap(),
dst_mac: MacAddr::broadcast(),
ether_type: EtherType::Arp,
};
packet_builder.set_ethernet(ethernet_packet_builder);
let eth_builder = EthernetPacketBuilder::new()
.source(src_mac)
.destination(MacAddr::broadcast())
.ethertype(EtherType::Arp);

let arp_packet = ArpPacketBuilder {
src_mac: interface.mac_addr.clone().unwrap(),
dst_mac: MacAddr::broadcast(),
src_ip: src_ip,
dst_ip: dst_ip,
};
packet_builder.set_arp(arp_packet);
let arp_builder = ArpPacketBuilder::new(src_mac, src_ip, target_ip);

let packet = eth_builder
.payload(arp_builder.build().to_bytes())
.build();

// Send ARP Request packet
match tx.send(&packet_builder.packet()) {
Some(_) => println!("ARP Packet sent"),
None => println!("Failed to send packet"),
match tx.send(&packet.to_bytes()) {
Some(_) => println!("ARP Request sent to {}", target_ip),
None => {
eprintln!("Failed to send ARP packet");
process::exit(1);
}
}

// Receive ARP Reply packet
println!("Waiting for ARP Reply packet...");
println!("Waiting for ARP Reply...");
loop {
match rx.next() {
Ok(packet) => {
let parse_option: ParseOption = ParseOption::default();
let frame: Frame = Frame::from_bytes(&packet, parse_option);
if let Some(datalik_layer) = &frame.datalink {
if let Some(arp_packet) = &datalik_layer.arp {
if arp_packet.operation == ArpOperation::Reply {
println!("ARP Reply packet received");
println!(
"Received ARP Reply packet from {}",
arp_packet.sender_proto_addr
);
println!("MAC address: {}", arp_packet.sender_hw_addr);
println!(
"---- Interface: {}, Total Length: {} bytes ----",
interface.name,
packet.len()
);
println!("Packet Frame: {:?}", frame);
break;
let frame = Frame::from_buf(&packet, ParseOption::default()).unwrap();
match &frame.datalink {
Some(dlink) => {
if let Some(arp) = &dlink.arp {
if arp.operation == ArpOperation::Reply && arp.sender_proto_addr == target_ip {
println!("Received ARP Reply from {}", arp.sender_proto_addr);
println!("MAC address: {}", arp.sender_hw_addr);
println!(
"---- Interface: {}, Total Length: {} bytes ----",
interface.name,
packet.len()
);
println!("Frame: {:?}", frame);
break;
}
}
}
None => continue, // No datalink layer
}
}
Err(e) => println!("Failed to receive packet: {}", e),
Err(e) => eprintln!("Receive failed: {}", e),
}
}
}
Loading