Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server: support IPv6 handler #9

Merged
merged 1 commit into from
Jan 12, 2024
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: 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