use std::net::IpAddr;
use anyhow::Context;
use netlink_packet_utils::{
byteorder::{ByteOrder, NativeEndian},
nla::{DefaultNla, Nla, NlaBuffer},
parsers::{parse_string, parse_u32, parse_u8},
DecodeError, Emitable, Parseable,
};
use crate::{
ip::{emit_ip_addr, ip_addr_len, parse_ip_addr, IpProtocol},
route::{RouteProtocol, RouteRealm},
rule::{RulePortRange, RuleUidRange},
};
const FRA_DST: u16 = 1;
const FRA_SRC: u16 = 2;
const FRA_IIFNAME: u16 = 3;
const FRA_GOTO: u16 = 4;
const FRA_PRIORITY: u16 = 6;
const FRA_FWMARK: u16 = 10;
const FRA_FLOW: u16 = 11;
const FRA_TUN_ID: u16 = 12;
const FRA_SUPPRESS_IFGROUP: u16 = 13;
const FRA_SUPPRESS_PREFIXLEN: u16 = 14;
const FRA_TABLE: u16 = 15;
const FRA_FWMASK: u16 = 16;
const FRA_OIFNAME: u16 = 17;
const FRA_L3MDEV: u16 = 19;
const FRA_UID_RANGE: u16 = 20;
const FRA_PROTOCOL: u16 = 21;
const FRA_IP_PROTO: u16 = 22;
const FRA_SPORT_RANGE: u16 = 23;
const FRA_DPORT_RANGE: u16 = 24;
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum RuleAttribute {
Destination(IpAddr),
Source(IpAddr),
Iifname(String),
Goto(u32),
Priority(u32),
FwMark(u32),
FwMask(u32),
Realm(RouteRealm),
TunId(u32),
SuppressIfGroup(u32),
SuppressPrefixLen(u32),
Table(u32),
Oifname(String),
L3MDev(bool),
UidRange(RuleUidRange),
Protocol(RouteProtocol),
IpProtocol(IpProtocol),
SourcePortRange(RulePortRange),
DestinationPortRange(RulePortRange),
Other(DefaultNla),
}
impl Nla for RuleAttribute {
fn value_len(&self) -> usize {
match self {
Self::Destination(ip) | Self::Source(ip) => ip_addr_len(ip),
Self::UidRange(v) => v.buffer_len(),
Self::SourcePortRange(v) | Self::DestinationPortRange(v) => {
v.buffer_len()
}
Self::Iifname(s) | Self::Oifname(s) => s.as_bytes().len() + 1,
Self::Priority(_)
| Self::FwMark(_)
| Self::FwMask(_)
| Self::TunId(_)
| Self::Goto(_)
| Self::SuppressIfGroup(_)
| Self::SuppressPrefixLen(_)
| Self::Table(_) => 4,
Self::Realm(v) => v.buffer_len(),
Self::L3MDev(_) | Self::Protocol(_) | Self::IpProtocol(_) => 1,
Self::Other(attr) => attr.value_len(),
}
}
fn kind(&self) -> u16 {
match self {
Self::Destination(_) => FRA_DST,
Self::Source(_) => FRA_SRC,
Self::Iifname(_) => FRA_IIFNAME,
Self::Goto(_) => FRA_GOTO,
Self::Priority(_) => FRA_PRIORITY,
Self::FwMark(_) => FRA_FWMARK,
Self::FwMask(_) => FRA_FWMASK,
Self::Realm(_) => FRA_FLOW,
Self::TunId(_) => FRA_TUN_ID,
Self::SuppressIfGroup(_) => FRA_SUPPRESS_IFGROUP,
Self::SuppressPrefixLen(_) => FRA_SUPPRESS_PREFIXLEN,
Self::Table(_) => FRA_TABLE,
Self::Oifname(_) => FRA_OIFNAME,
Self::L3MDev(_) => FRA_L3MDEV,
Self::UidRange(_) => FRA_UID_RANGE,
Self::Protocol(_) => FRA_PROTOCOL,
Self::IpProtocol(_) => FRA_IP_PROTO,
Self::SourcePortRange(_) => FRA_SPORT_RANGE,
Self::DestinationPortRange(_) => FRA_DPORT_RANGE,
Self::Other(attr) => attr.kind(),
}
}
fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Destination(ip) | Self::Source(ip) => {
emit_ip_addr(ip, buffer)
}
Self::SourcePortRange(v) | Self::DestinationPortRange(v) => {
v.emit(buffer)
}
Self::UidRange(v) => v.emit(buffer),
Self::Iifname(s) | Self::Oifname(s) => {
buffer[..s.len()].copy_from_slice(s.as_bytes())
}
Self::Realm(v) => v.emit(buffer),
Self::Priority(value)
| Self::FwMark(value)
| Self::FwMask(value)
| Self::TunId(value)
| Self::Goto(value)
| Self::SuppressIfGroup(value)
| Self::SuppressPrefixLen(value)
| Self::Table(value) => NativeEndian::write_u32(buffer, *value),
Self::L3MDev(value) => buffer[0] = (*value).into(),
Self::IpProtocol(value) => buffer[0] = i32::from(*value) as u8,
Self::Protocol(value) => buffer[0] = u8::from(*value),
Self::Other(attr) => attr.emit_value(buffer),
}
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
for RuleAttribute
{
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
FRA_DST => Self::Destination(
parse_ip_addr(payload)
.context(format!("Invalid FRA_DST value {payload:?}"))?,
),
FRA_SRC => Self::Source(
parse_ip_addr(payload)
.context(format!("Invalid FRA_SRC value {payload:?}"))?,
),
FRA_IIFNAME => Self::Iifname(
parse_string(payload).context("invalid FRA_IIFNAME value")?,
),
FRA_GOTO => Self::Goto(
parse_u32(payload).context("invalid FRA_GOTO value")?,
),
FRA_PRIORITY => Self::Priority(
parse_u32(payload).context("invalid FRA_PRIORITY value")?,
),
FRA_FWMARK => Self::FwMark(
parse_u32(payload).context("invalid FRA_FWMARK value")?,
),
FRA_FLOW => Self::Realm(
RouteRealm::parse(payload).context("invalid FRA_FLOW value")?,
),
FRA_TUN_ID => Self::TunId(
parse_u32(payload).context("invalid FRA_TUN_ID value")?,
),
FRA_SUPPRESS_IFGROUP => Self::SuppressIfGroup(
parse_u32(payload)
.context("invalid FRA_SUPPRESS_IFGROUP value")?,
),
FRA_SUPPRESS_PREFIXLEN => Self::SuppressPrefixLen(
parse_u32(payload)
.context("invalid FRA_SUPPRESS_PREFIXLEN value")?,
),
FRA_TABLE => Self::Table(
parse_u32(payload).context("invalid FRA_TABLE value")?,
),
FRA_FWMASK => Self::FwMask(
parse_u32(payload).context("invalid FRA_FWMASK value")?,
),
FRA_OIFNAME => Self::Oifname(
parse_string(payload).context("invalid FRA_OIFNAME value")?,
),
FRA_L3MDEV => Self::L3MDev(
parse_u8(payload).context("invalid FRA_L3MDEV value")? > 0,
),
FRA_UID_RANGE => Self::UidRange(
RuleUidRange::parse(payload)
.context("invalid FRA_UID_RANGE value")?,
),
FRA_PROTOCOL => Self::Protocol(
parse_u8(payload)
.context("invalid FRA_PROTOCOL value")?
.into(),
),
FRA_IP_PROTO => Self::IpProtocol(IpProtocol::from(
parse_u8(payload).context("invalid FRA_IP_PROTO value")? as i32,
)),
FRA_SPORT_RANGE => Self::SourcePortRange(
RulePortRange::parse(payload)
.context("invalid FRA_SPORT_RANGE value")?,
),
FRA_DPORT_RANGE => Self::DestinationPortRange(
RulePortRange::parse(payload)
.context("invalid FRA_DPORT_RANGE value")?,
),
_ => Self::Other(
DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?,
),
})
}
}