Skip to content

Commit

Permalink
server: support IPv6 handler
Browse files Browse the repository at this point in the history
Signed-off-by: Xiaobo Liu <[email protected]>
  • Loading branch information
cppcoffee committed Jan 11, 2024
1 parent c819c5c commit 57ef12f
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 44 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ $ cargo build --release

## TODO

- IPv6 support
- Allow IP list
- Add query and reject connection Interfaces
- More certificate signing algorithms
Expand Down
14 changes: 9 additions & 5 deletions server/src/iptables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand All @@ -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: {}",
Expand Down
50 changes: 30 additions & 20 deletions server/src/reject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -34,7 +36,6 @@ impl Sender {
TransportChannelType::Layer4(TransportProtocol::Ipv4(IpNextHeaderProtocols::Tcp)),
)?;

/*
let (icmpv6, _) = transport_channel(
0,
TransportChannelType::Layer4(TransportProtocol::Ipv6(IpNextHeaderProtocols::Icmpv6)),
Expand All @@ -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")
}
Expand All @@ -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(())
}
Expand All @@ -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();
Expand All @@ -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(())
}
Expand Down
54 changes: 44 additions & 10 deletions server/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
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<MutableIcmpPacket<'a>> {
let mut icmp_packet =
MutableIcmpPacket::new(&mut data[..]).ok_or(anyhow!("Failed to create ICMP packet"))?;

icmp_packet.set_icmp_type(IcmpTypes::DestinationUnreachable);
icmp_packet.set_icmp_code(IcmpCodes::DestinationPortUnreachable);
icmp_packet.set_payload(&[]);

let checksum = icmp_checksum(&icmp_packet.to_immutable());
icmp_packet.set_checksum(checksum);

Ok(icmp_packet)
}

pub fn build_icmpv6_unreachable<'a>(
data: &'a mut [u8],
src: &Ipv6Addr,
dest: &Ipv6Addr,
) -> Result<MutableIcmpv6Packet<'a>> {
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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(),
Expand All @@ -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]
Expand Down
39 changes: 31 additions & 8 deletions server/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}");
Expand All @@ -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),
}

Expand Down Expand Up @@ -111,6 +114,26 @@ impl Worker {
Ok(verdict)
}

fn ipv6_packet_handler(&mut self, payload: &[u8]) -> Result<Verdict> {
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,
Expand All @@ -119,24 +142,24 @@ impl Worker {
ip_packet: &[u8],
payload: &[u8],
) -> Result<Verdict> {
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(
Expand All @@ -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)
Expand Down

0 comments on commit 57ef12f

Please sign in to comment.