diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index eb8be6b..4a9ac47 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -26,3 +26,20 @@ jobs: run: cargo build --verbose --examples --tests --all-features - name: Test run: cargo test --all-features --examples + + semver: + name: Check semver + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] # macos-latest, windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Check semver + if: ${{ !cancelled() }} + uses: obi1kenobi/cargo-semver-checks-action@v2 + - name: Abort on error + if: ${{ failure() }} + run: echo "Semver check failed" && false \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dfe8e3b..edbd11c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ['Narrowlink '] description = 'Asynchronous lightweight userspace implementation of TCP/IP stack for Tun device' name = "ipstack" -version = "0.1.1" +version = "0.2.0" edition = "2021" license = "Apache-2.0" repository = 'https://github.com/narrowlink/ipstack' @@ -11,23 +11,20 @@ readme = "README.md" [dependencies] ahash = "0.8" -tokio = { version = "1.41", features = [ +tokio = { version = "1.43", features = [ "sync", "rt", "time", "io-util", "macros", ], default-features = false } -etherparse = { version = "0.16", default-features = false, features = ["std"] } +etherparse = { version = "0.17", default-features = false, features = ["std"] } thiserror = { version = "2.0", default-features = false } log = { version = "0.4", default-features = false } -rand = { version = "0.8.5", default-features = false, features = [ - "std", - "std_rng", -] } +rand = { version = "0.9", default-features = false, features = ["thread_rng"] } [dev-dependencies] -tokio = { version = "1.41", features = [ +tokio = { version = "1.43", features = [ "rt-multi-thread", ], default-features = false } clap = { version = "4.5", features = ["derive"] } @@ -38,7 +35,7 @@ udp-stream = { version = "0.0", default-features = false } criterion = { version = "0.5" } [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dev-dependencies] -tun = { version = "0.7.5", features = ["async"], default-features = false } +tun = { version = "0.7.13", features = ["async"], default-features = false } [target.'cfg(target_os = "windows")'.dev-dependencies] wintun = { version = "0.5", default-features = false } @@ -60,7 +57,6 @@ strip = true # Automatically strip symbols from the binary. opt-level = 3 lto = false codegen-units = 16 -debug = true # Enable debug info for `perf(1)` +debug = true # Enable debug info for `perf(1)` incremental = true strip = "none" - diff --git a/README.md b/README.md index 108b2f1..9517ed6 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Unstable, under development. ### Usage -```rust -use etherparse::{IcmpEchoHeader, Icmpv4Header, IpNumber}; -use ipstack::stream::IpStackStream; +```rust, no_run +use etherparse::{IcmpEchoHeader, Icmpv4Header}; +use ipstack::{stream::IpStackStream, IpNumber}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use tokio::net::TcpStream; use udp_stream::UdpStream; @@ -24,7 +24,7 @@ async fn main() { const MTU: u16 = 1500; let ipv4 = Ipv4Addr::new(10, 0, 0, 1); let netmask = Ipv4Addr::new(255, 255, 255, 0); - let mut config = tun2::Configuration::default(); + let mut config = tun::Configuration::default(); config.address(ipv4).netmask(netmask).mtu(MTU).up(); #[cfg(target_os = "linux")] @@ -40,7 +40,7 @@ async fn main() { let mut ipstack_config = ipstack::IpStackConfig::default(); ipstack_config.mtu(MTU); let mut ip_stack = - ipstack::IpStack::new(ipstack_config, tun2::create_as_async(&config).unwrap()); + ipstack::IpStack::new(ipstack_config, tun::create_as_async(&config).unwrap()); while let Ok(stream) = ip_stack.accept().await { match stream { @@ -58,7 +58,7 @@ async fn main() { }); } IpStackStream::UnknownTransport(u) => { - if u.src_addr().is_ipv4() && IpNumber::from(u.ip_protocol()) == IpNumber::ICMP { + if u.src_addr().is_ipv4() && u.ip_protocol() == IpNumber::ICMP { let (icmp_header, req_payload) = Icmpv4Header::from_slice(u.payload()).unwrap(); if let etherparse::Icmpv4Type::EchoRequest(req) = icmp_header.icmp_type { println!("ICMPv4 echo"); @@ -76,7 +76,7 @@ async fn main() { } continue; } - println!("unknown transport - Ip Protocol {}", u.ip_protocol().0); + println!("unknown transport - Ip Protocol {:?}", u.ip_protocol()); } IpStackStream::UnknownNetwork(pkt) => { println!("unknown transport - {} bytes", pkt.len()); diff --git a/examples/tun2.rs b/examples/tun2.rs index e406203..3c17db3 100644 --- a/examples/tun2.rs +++ b/examples/tun2.rs @@ -5,7 +5,7 @@ //! //! This example must be run as root or administrator privileges. //! ``` -//! sudo target/debug/examples/tun --server-addr 127.0.0.1:8080 # Linux or macOS +//! sudo target/debug/examples/tun2 --server-addr 127.0.0.1:8080 # Linux or macOS //! ``` //! Then please run the `echo` example server, which listens on TCP & UDP ports 127.0.0.1:8080. //! ``` @@ -28,8 +28,8 @@ //! use clap::Parser; -use etherparse::{IcmpEchoHeader, Icmpv4Header, IpNumber}; -use ipstack::stream::IpStackStream; +use etherparse::{IcmpEchoHeader, Icmpv4Header}; +use ipstack::{stream::IpStackStream, IpNumber}; use std::net::{Ipv4Addr, SocketAddr}; use tokio::net::TcpStream; use udp_stream::UdpStream; @@ -152,7 +152,7 @@ async fn main() -> Result<(), Box> { } IpStackStream::UnknownTransport(u) => { let n = number; - if u.src_addr().is_ipv4() && IpNumber::from(u.ip_protocol()) == IpNumber::ICMP { + if u.src_addr().is_ipv4() && u.ip_protocol() == IpNumber::ICMP { let (icmp_header, req_payload) = Icmpv4Header::from_slice(u.payload())?; if let etherparse::Icmpv4Type::EchoRequest(req) = icmp_header.icmp_type { log::info!("#{n} ICMPv4 echo"); diff --git a/examples/tun_wintun.rs b/examples/tun_wintun.rs index 1af173c..e55c8d2 100644 --- a/examples/tun_wintun.rs +++ b/examples/tun_wintun.rs @@ -1,8 +1,8 @@ use std::net::{Ipv4Addr, SocketAddr}; use clap::Parser; -use etherparse::{IcmpEchoHeader, Icmpv4Header, IpNumber}; -use ipstack::stream::IpStackStream; +use etherparse::{IcmpEchoHeader, Icmpv4Header}; +use ipstack::{stream::IpStackStream, IpNumber}; use tokio::net::TcpStream; use udp_stream::UdpStream; @@ -84,7 +84,7 @@ async fn main() -> Result<(), Box> { }); } IpStackStream::UnknownTransport(u) => { - if u.src_addr().is_ipv4() && IpNumber::from(u.ip_protocol()) == IpNumber::ICMP { + if u.src_addr().is_ipv4() && u.ip_protocol() == IpNumber::ICMP { let (icmp_header, req_payload) = Icmpv4Header::from_slice(u.payload())?; if let etherparse::Icmpv4Type::EchoRequest(req) = icmp_header.icmp_type { println!("ICMPv4 echo"); diff --git a/src/lib.rs b/src/lib.rs index 0787e15..93ab830 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + use crate::{ packet::IpStackPacketProtocol, stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream, IpStackUnknownTransport}, @@ -25,6 +27,7 @@ mod packet; pub mod stream; pub use self::error::{IpStackError, Result}; +pub use etherparse::IpNumber; const DROP_TTL: u8 = 0; diff --git a/src/packet.rs b/src/packet.rs index 3a25340..540d4ea 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -61,6 +61,7 @@ impl NetworkPacket { IpHeader::Ipv6(ip.header().to_header()), ip.payload().payload, ), + NetSlice::Arp(_) => return Err(IpStackError::UnsupportedTransportProtocol), }; let (transport, payload) = match p.transport { Some(etherparse::TransportSlice::Tcp(h)) => { diff --git a/src/stream/tcb.rs b/src/stream/tcb.rs index 2829465..ec0a671 100644 --- a/src/stream/tcb.rs +++ b/src/stream/tcb.rs @@ -5,7 +5,7 @@ use tokio::time::Sleep; const MAX_UNACK: u32 = 1024 * 16; // 16KB const READ_BUFFER_SIZE: usize = 1024 * 16; // 16KB -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum TcpState { SynReceived(bool), // bool means if syn/ack is sent Established, @@ -14,7 +14,7 @@ pub enum TcpState { Closed, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub(super) enum PacketStatus { WindowUpdate, Invalid, @@ -106,8 +106,8 @@ impl Tcb { pub(super) fn change_state(&mut self, state: TcpState) { self.state = state; } - pub(super) fn get_state(&self) -> &TcpState { - &self.state + pub(super) fn get_state(&self) -> TcpState { + self.state } pub(super) fn change_send_window(&mut self, window: u16) { let avg_send_window = ((self.avg_send_window.0 * self.avg_send_window.1) + window as u64) diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 448769c..77e2547 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -178,17 +178,24 @@ impl AsyncRead for IpStackTcpStream { buf: &mut tokio::io::ReadBuf<'_>, ) -> Poll> { loop { + if self.tcb.retransmission.is_some() { + self.write_notify = Some(cx.waker().clone()); + if matches!(self.as_mut().poll_flush(cx), Poll::Pending) { + return Poll::Pending; + } + } + if let Some(packet) = self.packet_to_send.take() { self.packet_sender .send(packet) .or(Err(ErrorKind::UnexpectedEof))?; } - if *self.tcb.get_state() == TcpState::Closed { + if self.tcb.get_state() == TcpState::Closed { self.shutdown.ready(); return Poll::Ready(Ok(())); } - if *self.tcb.get_state() == TcpState::FinWait2(false) { + if self.tcb.get_state() == TcpState::FinWait2(false) { self.packet_to_send = Some(self.create_rev_packet(NON, DROP_TTL, None, Vec::new())?); self.tcb.change_state(TcpState::Closed); @@ -210,7 +217,7 @@ impl AsyncRead for IpStackTcpStream { } self.tcb.reset_timeout(); - if *self.tcb.get_state() == TcpState::SynReceived(false) { + if self.tcb.get_state() == TcpState::SynReceived(false) { self.packet_to_send = Some(self.create_rev_packet(SYN | ACK, TTL, None, Vec::new())?); self.tcb.add_seq_one(); @@ -230,7 +237,7 @@ impl AsyncRead for IpStackTcpStream { .or(Err(ErrorKind::UnexpectedEof))?; return Poll::Ready(Ok(())); } - if *self.tcb.get_state() == TcpState::FinWait1(true) { + if self.tcb.get_state() == TcpState::FinWait1(true) { self.packet_to_send = Some(self.create_rev_packet(FIN | ACK, TTL, None, Vec::new())?); self.tcb.add_seq_one(); @@ -238,7 +245,7 @@ impl AsyncRead for IpStackTcpStream { self.tcb.change_state(TcpState::FinWait2(true)); continue; } else if matches!(self.shutdown, Shutdown::Pending(_)) - && *self.tcb.get_state() == TcpState::Established + && self.tcb.get_state() == TcpState::Established && self.tcb.get_last_ack() == self.tcb.get_seq() { self.packet_to_send = @@ -263,13 +270,13 @@ impl AsyncRead for IpStackTcpStream { continue; } - if *self.tcb.get_state() == TcpState::SynReceived(true) { + if self.tcb.get_state() == TcpState::SynReceived(true) { if t.flags() == ACK { self.tcb.change_last_ack(t.inner().acknowledgment_number); self.tcb.change_send_window(t.inner().window_size); self.tcb.change_state(TcpState::Established); } - } else if *self.tcb.get_state() == TcpState::Established { + } else if self.tcb.get_state() == TcpState::Established { if t.flags() == ACK { match self.tcb.check_pkt_type(&t, &p.payload) { PacketStatus::WindowUpdate => { @@ -358,7 +365,7 @@ impl AsyncRead for IpStackTcpStream { .add_unordered_packet(t.inner().sequence_number, p.payload); continue; } - } else if *self.tcb.get_state() == TcpState::FinWait1(false) { + } else if self.tcb.get_state() == TcpState::FinWait1(false) { if t.flags() == ACK { self.tcb.change_last_ack(t.inner().acknowledgment_number); self.tcb.add_ack(1); @@ -372,7 +379,7 @@ impl AsyncRead for IpStackTcpStream { self.tcb.change_state(TcpState::FinWait2(true)); continue; } - } else if *self.tcb.get_state() == TcpState::FinWait2(true) { + } else if self.tcb.get_state() == TcpState::FinWait2(true) { if t.flags() == ACK { self.tcb.change_state(TcpState::FinWait2(false)); } else if t.flags() == (FIN | ACK) { @@ -395,7 +402,7 @@ impl AsyncWrite for IpStackTcpStream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - if *self.tcb.get_state() != TcpState::Established { + if self.tcb.get_state() != TcpState::Established { return Poll::Ready(Err(Error::from(ErrorKind::NotConnected))); } self.tcb.reset_timeout(); @@ -430,7 +437,7 @@ impl AsyncWrite for IpStackTcpStream { mut self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { - if *self.tcb.get_state() != TcpState::Established { + if self.tcb.get_state() != TcpState::Established { return Poll::Ready(Err(Error::from(ErrorKind::NotConnected))); } diff --git a/src/stream/unknown.rs b/src/stream/unknown.rs index ede0f13..838d93f 100644 --- a/src/stream/unknown.rs +++ b/src/stream/unknown.rs @@ -45,8 +45,8 @@ impl IpStackUnknownTransport { pub fn payload(&self) -> &[u8] { &self.payload } - pub fn ip_protocol(&self) -> u8 { - self.protocol.0 + pub fn ip_protocol(&self) -> IpNumber { + self.protocol } pub fn send(&self, mut payload: Vec) -> Result<(), Error> { loop {