diff --git a/README.md b/README.md index fe7e3d1..4fea7de 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ $ cargo build --release ## TODO -- IPv6 support - Allow IP list - Add query and reject connection Interfaces - More certificate signing algorithms diff --git a/server/src/iptables.rs b/server/src/iptables.rs index e4011e8..61cfe16 100644 --- a/server/src/iptables.rs +++ b/server/src/iptables.rs @@ -50,7 +50,9 @@ fn iptables_rule_add(protocol: &str, port: u16, queue_opts: &[String]) -> Result args.push(opt.as_str()); } - iptables_command(&args)?; + for program in &["iptables", "ip6tables"] { + iptables_command(program, &args)?; + } Ok(()) } @@ -66,15 +68,17 @@ fn iptables_rule_del(protocol: &str, port: u16, queue_opts: &[String]) -> Result args.push(opt.as_str()); } - iptables_command(&args)?; + for program in &["iptables", "ip6tables"] { + iptables_command(program, &args)?; + } Ok(()) } -fn iptables_command(args: &[&str]) -> Result<()> { - debug!("command: iptables {:?}", args); +fn iptables_command(program: &str, args: &[&str]) -> Result<()> { + debug!("command: {program} {:?}", args); - let output = Command::new("iptables").args(args).output()?; + let output = Command::new(program).args(args).output()?; if !output.status.success() { bail!( "iptables command failed: {}", diff --git a/server/src/reject.rs b/server/src/reject.rs index 071e308..75417b9 100644 --- a/server/src/reject.rs +++ b/server/src/reject.rs @@ -12,14 +12,16 @@ use pnet::transport::{ use crate::util; -const ICMP_HEADER_SIZE: usize = 8; +const ICMP_UNREACHABLE_HEADER_SIZE: usize = 8; const UDP_CHECKSUM_OFFSET: usize = 6; +const BUFFER_SIZE: usize = 128; + pub struct Sender { icmp: TransportSender, tcp: TransportSender, - //icmpv6: TransportSender, - //tcp6: TransportSender, + icmpv6: TransportSender, + tcp6: TransportSender, } impl Sender { @@ -34,7 +36,6 @@ impl Sender { TransportChannelType::Layer4(TransportProtocol::Ipv4(IpNextHeaderProtocols::Tcp)), )?; - /* let (icmpv6, _) = transport_channel( 0, TransportChannelType::Layer4(TransportProtocol::Ipv6(IpNextHeaderProtocols::Icmpv6)), @@ -44,27 +45,25 @@ impl Sender { 0, TransportChannelType::Layer4(TransportProtocol::Ipv6(IpNextHeaderProtocols::Tcp)), )?; - */ Ok(Self { icmp, tcp, - //icmpv6, - //tcp6, + icmpv6, + tcp6, }) } - pub fn emit_icmpv4_unreachable( + pub fn emit_icmp_unreachable( &mut self, + source: &IpAddr, destination: &IpAddr, ip_packet: &[u8], udp_header: &UdpPacket, ) -> Result<()> { - const BUFFER_SIZE: usize = 128; - - let udp_packet = util::packet_header(udp_header); + let udp_packet_header = util::packet_header(udp_header); - let length = ICMP_HEADER_SIZE + ip_packet.len() + udp_packet.len(); + let length = ICMP_UNREACHABLE_HEADER_SIZE + ip_packet.len() + udp_packet_header.len(); if length >= BUFFER_SIZE { bail!("Packet too large") } @@ -74,20 +73,28 @@ impl Sender { // ip header let ip_packet_len = ip_packet.len(); - let (_, right) = buffer.split_at_mut(ICMP_HEADER_SIZE); + let (_, right) = buffer.split_at_mut(ICMP_UNREACHABLE_HEADER_SIZE); right[..ip_packet_len].copy_from_slice(ip_packet); // udp header - let udp_packet_len = udp_packet.len(); + let udp_packet_len = udp_packet_header.len(); let (_, right) = right.split_at_mut(ip_packet_len); - right[..udp_packet_len].copy_from_slice(udp_packet); + right[..udp_packet_len].copy_from_slice(udp_packet_header); // zero out udp header checksum right[UDP_CHECKSUM_OFFSET..udp_packet_len].copy_from_slice(&[0, 0]); - let icmp_packet = util::build_icmpv4_unreachable(&mut buffer[..length])?; - - self.icmp.send_to(icmp_packet, *destination)?; + match (source, destination) { + (IpAddr::V4(_), IpAddr::V4(_)) => { + let icmp_packet = util::build_icmpv4_unreachable(&mut buffer[..length])?; + self.icmp.send_to(icmp_packet, *destination)?; + } + (IpAddr::V6(src), IpAddr::V6(dest)) => { + let icmp_packet = util::build_icmpv6_unreachable(&mut buffer[..length], src, dest)?; + self.icmpv6.send_to(icmp_packet, *destination)?; + } + _ => bail!("IP version mismatch"), + } Ok(()) } @@ -98,7 +105,6 @@ impl Sender { source: &IpAddr, tcp_header: &TcpPacket, ) -> Result<()> { - const BUFFER_SIZE: usize = 64; let tcp_min_size = TcpPacket::minimum_packet_size(); let buffer = MaybeUninit::<[u8; BUFFER_SIZE]>::uninit(); @@ -107,7 +113,11 @@ impl Sender { let tcp_reset_packet = util::build_tcp_reset(&mut buffer[..tcp_min_size], destination, source, tcp_header)?; - self.tcp.send_to(tcp_reset_packet, *destination)?; + if destination.is_ipv4() { + self.tcp.send_to(tcp_reset_packet, *destination)?; + } else { + self.tcp6.send_to(tcp_reset_packet, *destination)?; + } Ok(()) } diff --git a/server/src/util.rs b/server/src/util.rs index dbb56d7..f0e871b 100644 --- a/server/src/util.rs +++ b/server/src/util.rs @@ -1,10 +1,15 @@ -use std::net::IpAddr; +use std::net::{IpAddr, Ipv6Addr}; use anyhow::{anyhow, bail, Result}; use pnet::packet::icmp::destination_unreachable::IcmpCodes; -use pnet::packet::icmp::{IcmpTypes, MutableIcmpPacket}; +use pnet::packet::icmp::{checksum as icmp_checksum, IcmpTypes, MutableIcmpPacket}; +use pnet::packet::icmpv6::{ + checksum as icmp6_checksum, Icmpv6Code, Icmpv6Types, MutableIcmpv6Packet, +}; use pnet::packet::tcp::{self, MutableTcpPacket, TcpFlags, TcpPacket}; -use pnet::packet::{util, Packet}; +use pnet::packet::Packet; + +const PORT_UNREACHABLE: u8 = 4; pub fn build_icmpv4_unreachable<'a>(data: &'a mut [u8]) -> Result> { let mut icmp_packet = @@ -12,8 +17,27 @@ pub fn build_icmpv4_unreachable<'a>(data: &'a mut [u8]) -> Result( + data: &'a mut [u8], + src: &Ipv6Addr, + dest: &Ipv6Addr, +) -> Result> { + let mut icmp_packet = + MutableIcmpv6Packet::new(&mut data[..]).ok_or(anyhow!("Failed to create ICMPv6 packet"))?; + + icmp_packet.set_icmpv6_type(Icmpv6Types::DestinationUnreachable); + icmp_packet.set_icmpv6_code(Icmpv6Code(PORT_UNREACHABLE)); + icmp_packet.set_payload(&[]); - let checksum = util::checksum(icmp_packet.packet(), 1); + let checksum = icmp6_checksum(&icmp_packet.to_immutable(), src, dest); icmp_packet.set_checksum(checksum); Ok(icmp_packet) @@ -53,6 +77,7 @@ pub fn build_tcp_reset<'a>( bail!("Source and destination IP addresses must be both IPv4 or IPv6") } }; + tcp_packet.set_checksum(checksum); Ok(tcp_packet) @@ -104,7 +129,7 @@ mod tests { #[test] fn test_build_icmpv4_unreachable() { let mut data = vec![0u8; 128]; - let mut icmp_packet = build_icmpv4_unreachable(&mut data).unwrap(); + let icmp_packet = build_icmpv4_unreachable(&mut data).unwrap(); assert_eq!( icmp_packet.get_icmp_type(), @@ -116,14 +141,23 @@ mod tests { IcmpCodes::DestinationPortUnreachable ); + assert_eq!(icmp_packet.get_checksum(), 0xfcfc); + } + + #[test] + fn test_build_icmpv6_unreachable() { + let mut data = vec![0u8; 128]; + let src = Ipv6Addr::new(1, 1, 1, 1, 1, 1, 1, 1); + let dest = Ipv6Addr::new(2, 2, 2, 2, 2, 2, 2, 2); + let icmp_packet = build_icmpv6_unreachable(&mut data, &src, &dest).unwrap(); + assert_eq!( - icmp_packet.get_checksum(), - util::checksum(icmp_packet.packet(), 1) + icmp_packet.get_icmpv6_type(), + Icmpv6Types::DestinationUnreachable ); - let checksum = util::checksum(icmp_packet.packet(), 1); - icmp_packet.set_checksum(checksum); - assert_eq!(icmp_packet.get_checksum(), checksum); + assert_eq!(icmp_packet.get_icmpv6_code(), Icmpv6Code(PORT_UNREACHABLE)); + assert_eq!(icmp_packet.get_checksum(), 0xFE29); } #[test] diff --git a/server/src/worker.rs b/server/src/worker.rs index e8d32e6..e7703cd 100644 --- a/server/src/worker.rs +++ b/server/src/worker.rs @@ -7,6 +7,7 @@ use anyhow::{anyhow, Result}; use nfq::{Queue, Verdict}; use pnet::packet::ip::{IpNextHeaderProtocol, IpNextHeaderProtocols}; use pnet::packet::ipv4::Ipv4Packet; +use pnet::packet::ipv6::Ipv6Packet; use pnet::packet::tcp::TcpPacket; use pnet::packet::udp::UdpPacket; use pnet::packet::Packet; @@ -62,6 +63,8 @@ impl Worker { return; } + info!("nfq {queue_num} worker started"); + loop { if let Err(e) = self.event_handler(&mut queue) { error!("nfq {queue_num} failed handle event: {e}"); @@ -81,7 +84,7 @@ impl Worker { match version { 4 => verdict = self.ipv4_packet_handler(payload)?, - //6 => verdict = self.ipv6_packet_handler( payload)?, + 6 => verdict = self.ipv6_packet_handler(payload)?, x => error!("nfq {} received unknown IP version: {x}", self.queue_num), } @@ -111,6 +114,26 @@ impl Worker { Ok(verdict) } + fn ipv6_packet_handler(&mut self, payload: &[u8]) -> Result { + let ip_header = Ipv6Packet::new(payload).ok_or(anyhow!("Malformed IPv6 packet"))?; + + let source = IpAddr::V6(ip_header.get_source()); + let destination = IpAddr::V6(ip_header.get_destination()); + let protocol = ip_header.get_next_header(); + + let ip_packet = util::packet_header(&ip_header); + + let verdict = self.transport_protocol_handler( + source, + destination, + protocol, + ip_packet, + ip_header.payload(), + )?; + + Ok(verdict) + } + fn transport_protocol_handler( &mut self, src_ip: IpAddr, @@ -119,24 +142,24 @@ impl Worker { ip_packet: &[u8], payload: &[u8], ) -> Result { + let mut verdict = Verdict::Drop; + match protocol { IpNextHeaderProtocols::Udp => { - let verdict = self.udp_packet_handler(src_ip, dst_ip, ip_packet, payload)?; - return Ok(verdict); + verdict = self.udp_packet_handler(src_ip, dst_ip, ip_packet, payload)?; } IpNextHeaderProtocols::Tcp => { - let verdict = self.tcp_packet_handler(src_ip, dst_ip, payload)?; - return Ok(verdict); + verdict = self.tcp_packet_handler(src_ip, dst_ip, payload)?; } _ => { debug!( - "nfq {} unknown transport protocol: {protocol}", + "nfq {} unknown transport protocol: {protocol}, skip it", self.queue_num ); } } - Ok(Verdict::Drop) + Ok(verdict) } fn udp_packet_handler( @@ -159,7 +182,7 @@ impl Worker { if verdict != Verdict::Accept { self.sender - .emit_icmpv4_unreachable(&src_ip, ip_packet, &udp_header)?; + .emit_icmp_unreachable(&dst_ip, &src_ip, ip_packet, &udp_header)?; } Ok(verdict)