use crate::netlink::{
Connection, MessageBuilder, Result, Route,
connection::{ack_request, create_request, replace_request},
message::NlMsgType,
types::tc::{TcMsg, TcaAttr, tc_handle},
};
fn build_tcmsg(dev: &str, parent: &str, protocol: u16, priority: u16) -> Result<TcMsg> {
let ifindex = crate::util::get_ifindex(dev).map_err(crate::netlink::Error::InvalidMessage)?;
let parent_handle = tc_handle::parse(parent).ok_or_else(|| {
crate::netlink::Error::InvalidMessage(format!("invalid parent handle: {}", parent))
})?;
let info = ((protocol as u32) << 16) | (priority as u32);
Ok(TcMsg {
tcm_family: 0,
tcm_pad1: 0,
tcm_pad2: 0,
tcm_ifindex: ifindex as i32,
tcm_handle: 0,
tcm_parent: parent_handle,
tcm_info: info,
})
}
pub fn parse_protocol(name: &str) -> Result<u16> {
Ok(match name.to_lowercase().as_str() {
"all" => 0x0003, "ip" => 0x0800, "ipv6" => 0x86DD, "arp" => 0x0806, "802.1q" | "vlan" => 0x8100, "802.1ad" => 0x88A8, "mpls_uc" => 0x8847, "mpls_mc" => 0x8848, _ => {
if let Some(hex) = name.strip_prefix("0x") {
u16::from_str_radix(hex, 16).map_err(|_| {
crate::netlink::Error::InvalidMessage(format!("invalid protocol: {}", name))
})?
} else {
name.parse().map_err(|_| {
crate::netlink::Error::InvalidMessage(format!("unknown protocol: {}", name))
})?
}
}
})
}
pub fn format_protocol(proto: u16) -> String {
match proto {
0x0003 => "all".to_string(),
0x0800 => "ip".to_string(),
0x86DD => "ipv6".to_string(),
0x0806 => "arp".to_string(),
0x8100 => "802.1Q".to_string(),
0x88A8 => "802.1ad".to_string(),
0x8847 => "mpls_uc".to_string(),
0x8848 => "mpls_mc".to_string(),
_ => format!("0x{:04x}", proto),
}
}
pub fn add_options(builder: &mut MessageBuilder, kind: &str, params: &[String]) -> Result<()> {
if params.is_empty() {
return Ok(());
}
let options_token = builder.nest_start(TcaAttr::Options as u16);
match kind {
"u32" => add_u32_options(builder, params)?,
"flower" => add_flower_options(builder, params)?,
"basic" | "matchall" => add_basic_options(builder, params)?,
"fw" => add_fw_options(builder, params)?,
"bpf" => add_bpf_options(builder, params)?,
_ => {
}
}
builder.nest_end(options_token);
Ok(())
}
pub async fn add(
conn: &Connection<Route>,
dev: &str,
parent: &str,
protocol: &str,
prio: Option<u16>,
kind: &str,
params: &[String],
) -> Result<()> {
let proto = parse_protocol(protocol)?;
let priority = prio.unwrap_or(0);
let tcmsg = build_tcmsg(dev, parent, proto, priority)?;
let mut builder = create_request(NlMsgType::RTM_NEWTFILTER);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, kind);
add_options(&mut builder, kind, params)?;
conn.send_ack(builder).await?;
Ok(())
}
pub async fn del(
conn: &Connection<Route>,
dev: &str,
parent: &str,
protocol: Option<&str>,
prio: Option<u16>,
kind: Option<&str>,
) -> Result<()> {
let proto = if let Some(p) = protocol {
parse_protocol(p)?
} else {
0
};
let priority = prio.unwrap_or(0);
let tcmsg = build_tcmsg(dev, parent, proto, priority)?;
let mut builder = ack_request(NlMsgType::RTM_DELTFILTER);
builder.append(&tcmsg);
if let Some(k) = kind {
builder.append_attr_str(TcaAttr::Kind as u16, k);
}
conn.send_ack(builder).await?;
Ok(())
}
pub async fn replace(
conn: &Connection<Route>,
dev: &str,
parent: &str,
protocol: &str,
prio: Option<u16>,
kind: &str,
params: &[String],
) -> Result<()> {
let proto = parse_protocol(protocol)?;
let priority = prio.unwrap_or(0);
let tcmsg = build_tcmsg(dev, parent, proto, priority)?;
let mut builder = replace_request(NlMsgType::RTM_NEWTFILTER);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, kind);
add_options(&mut builder, kind, params)?;
conn.send_ack(builder).await?;
Ok(())
}
pub async fn change(
conn: &Connection<Route>,
dev: &str,
parent: &str,
protocol: &str,
prio: Option<u16>,
kind: &str,
params: &[String],
) -> Result<()> {
let proto = parse_protocol(protocol)?;
let priority = prio.unwrap_or(0);
let tcmsg = build_tcmsg(dev, parent, proto, priority)?;
let mut builder = ack_request(NlMsgType::RTM_NEWTFILTER);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, kind);
add_options(&mut builder, kind, params)?;
conn.send_ack(builder).await?;
Ok(())
}
fn add_u32_options(builder: &mut MessageBuilder, params: &[String]) -> Result<()> {
use crate::netlink::types::tc::filter::u32::*;
let mut sel = TcU32Sel::new();
let mut has_classid = false;
let mut i = 0;
while i < params.len() {
match params[i].as_str() {
"classid" | "flowid" if i + 1 < params.len() => {
let classid = tc_handle::parse(¶ms[i + 1]).ok_or_else(|| {
crate::netlink::Error::InvalidMessage("invalid classid".into())
})?;
builder.append_attr_u32(TCA_U32_CLASSID, classid);
sel.set_terminal();
has_classid = true;
i += 2;
}
"match" if i + 1 < params.len() => {
i += 1;
match params[i].as_str() {
"ip" if i + 2 < params.len() => {
i += 1;
i = parse_ip_match(&mut sel, params, i)?;
}
"ip6" if i + 2 < params.len() => {
i += 1;
i = parse_ip6_match(&mut sel, params, i)?;
}
"tcp" | "udp" if i + 2 < params.len() => {
let proto = params[i].as_str();
i += 1;
i = parse_l4_match(&mut sel, params, i, proto)?;
}
"u32" if i + 3 < params.len() => {
i += 1;
let val = parse_hex_or_dec(¶ms[i])?;
i += 1;
let mask = parse_hex_or_dec(¶ms[i])?;
i += 1;
let off = parse_offset(params, &mut i)?;
sel.add_key(pack_key32(val, mask, off));
}
"u16" if i + 3 < params.len() => {
i += 1;
let val = parse_hex_or_dec(¶ms[i])? as u16;
i += 1;
let mask = parse_hex_or_dec(¶ms[i])? as u16;
i += 1;
let off = parse_offset(params, &mut i)?;
sel.add_key(pack_key16(val, mask, off));
}
"u8" if i + 3 < params.len() => {
i += 1;
let val = parse_hex_or_dec(¶ms[i])? as u8;
i += 1;
let mask = parse_hex_or_dec(¶ms[i])? as u8;
i += 1;
let off = parse_offset(params, &mut i)?;
sel.add_key(pack_key8(val, mask, off));
}
_ => {
return Err(crate::netlink::Error::InvalidMessage(format!(
"unknown match type: {}",
params[i]
)));
}
}
}
"divisor" if i + 1 < params.len() => {
let divisor: u32 = params[i + 1]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid divisor".into()))?;
builder.append_attr_u32(TCA_U32_DIVISOR, divisor);
i += 2;
}
"link" if i + 1 < params.len() => {
let link = parse_u32_handle(¶ms[i + 1])?;
builder.append_attr_u32(TCA_U32_LINK, link);
i += 2;
}
"ht" if i + 1 < params.len() => {
let ht = parse_u32_handle(¶ms[i + 1])?;
builder.append_attr_u32(TCA_U32_HASH, ht);
i += 2;
}
_ => i += 1,
}
}
if sel.hdr.nkeys > 0 || has_classid {
builder.append_attr(TCA_U32_SEL, &sel.to_bytes());
}
Ok(())
}
fn parse_ip_match(
sel: &mut crate::netlink::types::tc::filter::u32::TcU32Sel,
params: &[String],
mut i: usize,
) -> Result<usize> {
use crate::netlink::types::tc::filter::u32::*;
match params[i].as_str() {
"src" if i + 1 < params.len() => {
i += 1;
let (addr, mask) = parse_ip_prefix(¶ms[i])?;
sel.add_key(TcU32Key::new(addr.to_be(), mask.to_be(), 12));
i += 1;
}
"dst" if i + 1 < params.len() => {
i += 1;
let (addr, mask) = parse_ip_prefix(¶ms[i])?;
sel.add_key(TcU32Key::new(addr.to_be(), mask.to_be(), 16));
i += 1;
}
"sport" if i + 1 < params.len() => {
i += 1;
let port: u16 = params[i]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
sel.add_key(pack_key16(port, 0xffff, 20));
i += 1;
}
"dport" if i + 1 < params.len() => {
i += 1;
let port: u16 = params[i]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
sel.add_key(pack_key16(port, 0xffff, 22));
i += 1;
}
"protocol" if i + 1 < params.len() => {
i += 1;
let proto: u8 = match params[i].as_str() {
"tcp" => 6,
"udp" => 17,
"icmp" => 1,
"gre" => 47,
_ => params[i].parse().map_err(|_| {
crate::netlink::Error::InvalidMessage("invalid protocol".into())
})?,
};
sel.add_key(pack_key8(proto, 0xff, 9));
i += 1;
}
"tos" | "dsfield" if i + 1 < params.len() => {
i += 1;
let tos = parse_hex_or_dec(¶ms[i])? as u8;
i += 1;
let mask = if i < params.len() && !is_u32_keyword(¶ms[i]) {
let m = parse_hex_or_dec(¶ms[i])? as u8;
i += 1;
m
} else {
0xff
};
sel.add_key(pack_key8(tos, mask, 1));
}
_ => {
return Err(crate::netlink::Error::InvalidMessage(format!(
"unknown ip match: {}",
params[i]
)));
}
}
Ok(i)
}
fn parse_ip6_match(
sel: &mut crate::netlink::types::tc::filter::u32::TcU32Sel,
params: &[String],
mut i: usize,
) -> Result<usize> {
match params[i].as_str() {
"src" if i + 1 < params.len() => {
i += 1;
let keys = parse_ipv6_prefix(¶ms[i], 8)?;
for key in keys {
sel.add_key(key);
}
i += 1;
}
"dst" if i + 1 < params.len() => {
i += 1;
let keys = parse_ipv6_prefix(¶ms[i], 24)?;
for key in keys {
sel.add_key(key);
}
i += 1;
}
"sport" if i + 1 < params.len() => {
i += 1;
let port: u16 = params[i]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
sel.add_key(crate::netlink::types::tc::filter::u32::pack_key16(
port, 0xffff, 40,
));
i += 1;
}
"dport" if i + 1 < params.len() => {
i += 1;
let port: u16 = params[i]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
sel.add_key(crate::netlink::types::tc::filter::u32::pack_key16(
port, 0xffff, 42,
));
i += 1;
}
_ => {
return Err(crate::netlink::Error::InvalidMessage(format!(
"unknown ip6 match: {}",
params[i]
)));
}
}
Ok(i)
}
fn parse_l4_match(
sel: &mut crate::netlink::types::tc::filter::u32::TcU32Sel,
params: &[String],
mut i: usize,
_proto: &str,
) -> Result<usize> {
use crate::netlink::types::tc::filter::u32::TcU32Key;
match params[i].as_str() {
"src" if i + 1 < params.len() => {
i += 1;
let port: u16 = params[i]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
sel.add_key(TcU32Key::with_nexthdr(
((port as u32) << 16).to_be(),
0xffff0000u32.to_be(),
0,
));
i += 1;
}
"dst" if i + 1 < params.len() => {
i += 1;
let port: u16 = params[i]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
sel.add_key(TcU32Key::with_nexthdr(
(port as u32).to_be(),
0x0000ffffu32.to_be(),
0,
));
i += 1;
}
_ => {
return Err(crate::netlink::Error::InvalidMessage(format!(
"unknown tcp/udp match: {}",
params[i]
)));
}
}
Ok(i)
}
fn parse_hex_or_dec(s: &str) -> Result<u32> {
if let Some(hex) = s.strip_prefix("0x") {
u32::from_str_radix(hex, 16)
} else if let Some(hex) = s.strip_prefix("0X") {
u32::from_str_radix(hex, 16)
} else {
s.parse()
}
.map_err(|_| crate::netlink::Error::InvalidMessage(format!("invalid number: {}", s)))
}
fn parse_offset(params: &[String], i: &mut usize) -> Result<i32> {
if *i < params.len() && params[*i] == "at" {
*i += 1;
if *i < params.len() {
let off: i32 = params[*i]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid offset".into()))?;
*i += 1;
return Ok(off);
}
}
Err(crate::netlink::Error::InvalidMessage(
"expected 'at OFFSET'".into(),
))
}
fn parse_u32_handle(s: &str) -> Result<u32> {
if let Some(hex) = s.strip_prefix("0x") {
return u32::from_str_radix(hex, 16)
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid handle".into()));
}
let parts: Vec<&str> = s.split(':').collect();
match parts.len() {
1 => {
let htid = u32::from_str_radix(parts[0], 16)
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid handle".into()))?;
Ok(htid << 20)
}
2 => {
let htid = if parts[0].is_empty() {
0
} else {
u32::from_str_radix(parts[0], 16)
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid handle".into()))?
};
let hash = if parts[1].is_empty() {
0
} else {
u32::from_str_radix(parts[1], 16)
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid handle".into()))?
};
Ok((htid << 20) | (hash << 12))
}
3 => {
let htid = if parts[0].is_empty() {
0
} else {
u32::from_str_radix(parts[0], 16)
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid handle".into()))?
};
let hash = if parts[1].is_empty() {
0
} else {
u32::from_str_radix(parts[1], 16)
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid handle".into()))?
};
let node = if parts[2].is_empty() {
0
} else {
u32::from_str_radix(parts[2], 16)
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid handle".into()))?
};
Ok((htid << 20) | (hash << 12) | node)
}
_ => Err(crate::netlink::Error::InvalidMessage(
"invalid handle format".into(),
)),
}
}
fn parse_ip_prefix(s: &str) -> Result<(u32, u32)> {
let (addr_str, prefix_len) = if let Some((a, p)) = s.split_once('/') {
let plen: u8 = p
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid prefix length".into()))?;
(a, plen)
} else {
(s, 32)
};
let addr: std::net::Ipv4Addr = addr_str
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid IP address".into()))?;
let mask = if prefix_len == 0 {
0
} else {
0xffffffffu32 << (32 - prefix_len)
};
Ok((u32::from(addr), mask))
}
fn parse_ipv6_prefix(
s: &str,
base_off: i32,
) -> Result<Vec<crate::netlink::types::tc::filter::u32::TcU32Key>> {
use crate::netlink::types::tc::filter::u32::TcU32Key;
let (addr_str, prefix_len) = if let Some((a, p)) = s.split_once('/') {
let plen: u8 = p
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid prefix length".into()))?;
(a, plen as u32)
} else {
(s, 128)
};
let addr: std::net::Ipv6Addr = addr_str
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid IPv6 address".into()))?;
let octets = addr.octets();
let mut keys = Vec::new();
let mut remaining = prefix_len;
for i in 0..4 {
if remaining == 0 {
break;
}
let word_offset = base_off + (i * 4) as i32;
let word = u32::from_be_bytes([
octets[i * 4],
octets[i * 4 + 1],
octets[i * 4 + 2],
octets[i * 4 + 3],
]);
let bits = remaining.min(32);
let mask = if bits == 32 {
0xffffffff
} else {
0xffffffffu32 << (32 - bits)
};
keys.push(TcU32Key::new(word.to_be(), mask.to_be(), word_offset));
remaining = remaining.saturating_sub(32);
}
Ok(keys)
}
fn is_u32_keyword(s: &str) -> bool {
matches!(
s,
"match" | "classid" | "flowid" | "divisor" | "link" | "ht" | "at"
)
}
fn add_flower_options(builder: &mut MessageBuilder, params: &[String]) -> Result<()> {
use crate::netlink::types::tc::filter::flower::*;
let mut i = 0;
while i < params.len() {
match params[i].as_str() {
"classid" | "flowid" if i + 1 < params.len() => {
let classid = tc_handle::parse(¶ms[i + 1]).ok_or_else(|| {
crate::netlink::Error::InvalidMessage("invalid classid".into())
})?;
builder.append_attr_u32(TCA_FLOWER_CLASSID, classid);
i += 2;
}
"ip_proto" if i + 1 < params.len() => {
let proto = parse_ip_proto(¶ms[i + 1]).ok_or_else(|| {
crate::netlink::Error::InvalidMessage(format!(
"invalid ip_proto: {}",
params[i + 1]
))
})?;
builder.append_attr_u8(TCA_FLOWER_KEY_IP_PROTO, proto);
i += 2;
}
"dst_port" if i + 1 < params.len() => {
let port: u16 = params[i + 1]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
builder.append_attr_u16_be(TCA_FLOWER_KEY_TCP_DST, port);
i += 2;
}
"src_port" if i + 1 < params.len() => {
let port: u16 = params[i + 1]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid port".into()))?;
builder.append_attr_u16_be(TCA_FLOWER_KEY_TCP_SRC, port);
i += 2;
}
"dst_ip" if i + 1 < params.len() => {
i += 1;
if params[i].contains(':') {
let (addr, mask) = parse_ipv6_prefix_flower(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_IPV6_DST, &addr);
builder.append_attr(TCA_FLOWER_KEY_IPV6_DST_MASK, &mask);
} else {
let (addr, mask) = parse_ip_prefix(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_IPV4_DST, &addr.to_be_bytes());
builder.append_attr(TCA_FLOWER_KEY_IPV4_DST_MASK, &mask.to_be_bytes());
}
i += 1;
}
"src_ip" if i + 1 < params.len() => {
i += 1;
if params[i].contains(':') {
let (addr, mask) = parse_ipv6_prefix_flower(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_IPV6_SRC, &addr);
builder.append_attr(TCA_FLOWER_KEY_IPV6_SRC_MASK, &mask);
} else {
let (addr, mask) = parse_ip_prefix(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_IPV4_SRC, &addr.to_be_bytes());
builder.append_attr(TCA_FLOWER_KEY_IPV4_SRC_MASK, &mask.to_be_bytes());
}
i += 1;
}
"dst_mac" if i + 1 < params.len() => {
let mac = parse_mac_addr(¶ms[i + 1])?;
builder.append_attr(TCA_FLOWER_KEY_ETH_DST, &mac);
builder.append_attr(TCA_FLOWER_KEY_ETH_DST_MASK, &[0xff; 6]);
i += 2;
}
"src_mac" if i + 1 < params.len() => {
let mac = parse_mac_addr(¶ms[i + 1])?;
builder.append_attr(TCA_FLOWER_KEY_ETH_SRC, &mac);
builder.append_attr(TCA_FLOWER_KEY_ETH_SRC_MASK, &[0xff; 6]);
i += 2;
}
"eth_type" if i + 1 < params.len() => {
let eth_type: u16 = match params[i + 1].as_str() {
"ip" | "ipv4" => 0x0800,
"ipv6" => 0x86dd,
"arp" => 0x0806,
"vlan" | "802.1q" => 0x8100,
"802.1ad" => 0x88a8,
_ => parse_hex_or_dec(¶ms[i + 1])? as u16,
};
builder.append_attr_u16_be(TCA_FLOWER_KEY_ETH_TYPE, eth_type);
i += 2;
}
"vlan_id" if i + 1 < params.len() => {
let id: u16 = params[i + 1]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid vlan_id".into()))?;
if id == 0 || id > 4094 {
return Err(crate::netlink::Error::InvalidMessage(
"vlan_id must be 1-4094".into(),
));
}
builder.append_attr_u16(TCA_FLOWER_KEY_VLAN_ID, id);
i += 2;
}
"vlan_prio" if i + 1 < params.len() => {
let prio: u8 = params[i + 1].parse().map_err(|_| {
crate::netlink::Error::InvalidMessage("invalid vlan_prio".into())
})?;
if prio > 7 {
return Err(crate::netlink::Error::InvalidMessage(
"vlan_prio must be 0-7".into(),
));
}
builder.append_attr_u8(TCA_FLOWER_KEY_VLAN_PRIO, prio);
i += 2;
}
"ip_tos" if i + 1 < params.len() => {
let (val, mask) = parse_value_mask_u8(¶ms[i + 1])?;
builder.append_attr_u8(TCA_FLOWER_KEY_IP_TOS, val);
builder.append_attr_u8(TCA_FLOWER_KEY_IP_TOS_MASK, mask);
i += 2;
}
"ip_ttl" if i + 1 < params.len() => {
let (val, mask) = parse_value_mask_u8(¶ms[i + 1])?;
builder.append_attr_u8(TCA_FLOWER_KEY_IP_TTL, val);
builder.append_attr_u8(TCA_FLOWER_KEY_IP_TTL_MASK, mask);
i += 2;
}
"tcp_flags" if i + 1 < params.len() => {
let (flags, mask) = parse_tcp_flags(¶ms[i + 1])?;
builder.append_attr_u16_be(TCA_FLOWER_KEY_TCP_FLAGS, flags);
builder.append_attr_u16_be(TCA_FLOWER_KEY_TCP_FLAGS_MASK, mask);
i += 2;
}
"ct_state" if i + 1 < params.len() => {
let state = parse_ct_state(¶ms[i + 1])?;
builder.append_attr_u16(TCA_FLOWER_KEY_CT_STATE, state);
builder.append_attr_u16(TCA_FLOWER_KEY_CT_STATE_MASK, state);
i += 2;
}
"ct_zone" if i + 1 < params.len() => {
let zone: u16 = params[i + 1]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid ct_zone".into()))?;
builder.append_attr_u16(TCA_FLOWER_KEY_CT_ZONE, zone);
builder.append_attr_u16(TCA_FLOWER_KEY_CT_ZONE_MASK, 0xffff);
i += 2;
}
"ct_mark" if i + 1 < params.len() => {
let (val, mask) = parse_value_mask_u32(¶ms[i + 1])?;
builder.append_attr_u32(TCA_FLOWER_KEY_CT_MARK, val);
builder.append_attr_u32(TCA_FLOWER_KEY_CT_MARK_MASK, mask);
i += 2;
}
"enc_key_id" if i + 1 < params.len() => {
let id: u32 = params[i + 1].parse().map_err(|_| {
crate::netlink::Error::InvalidMessage("invalid enc_key_id".into())
})?;
builder.append_attr_u32(TCA_FLOWER_KEY_ENC_KEY_ID, id.to_be());
i += 2;
}
"enc_dst_ip" if i + 1 < params.len() => {
i += 1;
if params[i].contains(':') {
let (addr, mask) = parse_ipv6_prefix_flower(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV6_DST, &addr);
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV6_DST_MASK, &mask);
} else {
let (addr, mask) = parse_ip_prefix(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV4_DST, &addr.to_be_bytes());
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV4_DST_MASK, &mask.to_be_bytes());
}
i += 1;
}
"enc_src_ip" if i + 1 < params.len() => {
i += 1;
if params[i].contains(':') {
let (addr, mask) = parse_ipv6_prefix_flower(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV6_SRC, &addr);
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK, &mask);
} else {
let (addr, mask) = parse_ip_prefix(¶ms[i])?;
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV4_SRC, &addr.to_be_bytes());
builder.append_attr(TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK, &mask.to_be_bytes());
}
i += 1;
}
"enc_dst_port" if i + 1 < params.len() => {
let port: u16 = params[i + 1].parse().map_err(|_| {
crate::netlink::Error::InvalidMessage("invalid enc_dst_port".into())
})?;
builder.append_attr_u16_be(TCA_FLOWER_KEY_ENC_UDP_DST_PORT, port);
i += 2;
}
"skip_hw" => {
builder.append_attr_u32(TCA_FLOWER_FLAGS, TCA_CLS_FLAGS_SKIP_HW);
i += 1;
}
"skip_sw" => {
builder.append_attr_u32(TCA_FLOWER_FLAGS, TCA_CLS_FLAGS_SKIP_SW);
i += 1;
}
"indev" if i + 1 < params.len() => {
builder.append_attr_str(TCA_FLOWER_INDEV, ¶ms[i + 1]);
i += 2;
}
_ => i += 1,
}
}
Ok(())
}
fn parse_mac_addr(s: &str) -> Result<[u8; 6]> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 6 {
return Err(crate::netlink::Error::InvalidMessage(format!(
"invalid MAC address: {}",
s
)));
}
let mut mac = [0u8; 6];
for (i, part) in parts.iter().enumerate() {
mac[i] = u8::from_str_radix(part, 16).map_err(|_| {
crate::netlink::Error::InvalidMessage(format!("invalid MAC address: {}", s))
})?;
}
Ok(mac)
}
fn parse_ipv6_prefix_flower(s: &str) -> Result<([u8; 16], [u8; 16])> {
let (addr_str, prefix_len) = if let Some((a, p)) = s.split_once('/') {
let plen: u8 = p
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid prefix length".into()))?;
(a, plen)
} else {
(s, 128)
};
let addr: std::net::Ipv6Addr = addr_str
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid IPv6 address".into()))?;
let mut mask = [0u8; 16];
for (i, byte) in mask.iter_mut().enumerate() {
let bits = if (i * 8) < prefix_len as usize {
let remaining = prefix_len as usize - (i * 8);
if remaining >= 8 { 8 } else { remaining }
} else {
0
};
*byte = if bits == 8 { 0xff } else { 0xff << (8 - bits) };
}
Ok((addr.octets(), mask))
}
fn parse_value_mask_u8(s: &str) -> Result<(u8, u8)> {
if let Some((val, mask)) = s.split_once('/') {
let v = parse_hex_or_dec(val)? as u8;
let m = parse_hex_or_dec(mask)? as u8;
Ok((v, m))
} else {
let v = parse_hex_or_dec(s)? as u8;
Ok((v, 0xff))
}
}
fn parse_value_mask_u32(s: &str) -> Result<(u32, u32)> {
if let Some((val, mask)) = s.split_once('/') {
let v = parse_hex_or_dec(val)?;
let m = parse_hex_or_dec(mask)?;
Ok((v, m))
} else {
let v = parse_hex_or_dec(s)?;
Ok((v, 0xffffffff))
}
}
fn parse_tcp_flags(s: &str) -> Result<(u16, u16)> {
let (flags_str, mask_str) = if let Some((f, m)) = s.split_once('/') {
(f, Some(m))
} else {
(s, None)
};
let mut flags: u16 = 0;
for flag in flags_str.split('+') {
flags |= match flag.to_lowercase().as_str() {
"fin" => 0x01,
"syn" => 0x02,
"rst" => 0x04,
"psh" => 0x08,
"ack" => 0x10,
"urg" => 0x20,
"ece" => 0x40,
"cwr" => 0x80,
_ => parse_hex_or_dec(flag)? as u16,
};
}
let mask = if let Some(m) = mask_str {
let mut mask: u16 = 0;
for flag in m.split('+') {
mask |= match flag.to_lowercase().as_str() {
"fin" => 0x01,
"syn" => 0x02,
"rst" => 0x04,
"psh" => 0x08,
"ack" => 0x10,
"urg" => 0x20,
"ece" => 0x40,
"cwr" => 0x80,
_ => parse_hex_or_dec(flag)? as u16,
};
}
mask
} else {
flags
};
Ok((flags, mask))
}
fn parse_ct_state(s: &str) -> Result<u16> {
use crate::netlink::types::tc::filter::flower::*;
let mut state: u16 = 0;
for part in s.split('+') {
state |= match part.to_lowercase().as_str() {
"new" => TCA_FLOWER_KEY_CT_FLAGS_NEW,
"established" | "est" => TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED,
"related" | "rel" => TCA_FLOWER_KEY_CT_FLAGS_RELATED,
"tracked" | "trk" => TCA_FLOWER_KEY_CT_FLAGS_TRACKED,
"invalid" | "inv" => TCA_FLOWER_KEY_CT_FLAGS_INVALID,
"reply" | "rpl" => TCA_FLOWER_KEY_CT_FLAGS_REPLY,
_ => {
return Err(crate::netlink::Error::InvalidMessage(format!(
"unknown ct_state: {}",
part
)));
}
};
}
Ok(state)
}
fn add_basic_options(builder: &mut MessageBuilder, params: &[String]) -> Result<()> {
use crate::netlink::types::tc::filter::basic::*;
let mut i = 0;
while i < params.len() {
match params[i].as_str() {
"classid" | "flowid" if i + 1 < params.len() => {
let classid = tc_handle::parse(¶ms[i + 1]).ok_or_else(|| {
crate::netlink::Error::InvalidMessage("invalid classid".into())
})?;
builder.append_attr_u32(TCA_BASIC_CLASSID, classid);
i += 2;
}
_ => i += 1,
}
}
Ok(())
}
fn add_fw_options(builder: &mut MessageBuilder, params: &[String]) -> Result<()> {
use crate::netlink::types::tc::filter::fw::*;
let mut i = 0;
while i < params.len() {
match params[i].as_str() {
"classid" | "flowid" if i + 1 < params.len() => {
let classid = tc_handle::parse(¶ms[i + 1]).ok_or_else(|| {
crate::netlink::Error::InvalidMessage("invalid classid".into())
})?;
builder.append_attr_u32(TCA_FW_CLASSID, classid);
i += 2;
}
"mask" if i + 1 < params.len() => {
let mask = parse_hex_or_dec(¶ms[i + 1])?;
builder.append_attr_u32(TCA_FW_MASK, mask);
i += 2;
}
_ => i += 1,
}
}
Ok(())
}
fn add_bpf_options(builder: &mut MessageBuilder, params: &[String]) -> Result<()> {
use crate::netlink::types::tc::filter::{
bpf::*,
flower::{TCA_CLS_FLAGS_SKIP_HW, TCA_CLS_FLAGS_SKIP_SW},
};
let mut i = 0;
let mut has_da = false;
while i < params.len() {
match params[i].as_str() {
"classid" | "flowid" if i + 1 < params.len() => {
let classid = tc_handle::parse(¶ms[i + 1]).ok_or_else(|| {
crate::netlink::Error::InvalidMessage("invalid classid".into())
})?;
builder.append_attr_u32(TCA_BPF_CLASSID, classid);
i += 2;
}
"fd" if i + 1 < params.len() => {
let fd: u32 = params[i + 1]
.parse()
.map_err(|_| crate::netlink::Error::InvalidMessage("invalid fd".into()))?;
builder.append_attr_u32(TCA_BPF_FD, fd);
i += 2;
}
"name" | "section" if i + 1 < params.len() => {
builder.append_attr_str(TCA_BPF_NAME, ¶ms[i + 1]);
i += 2;
}
"object-pinned" | "pinned" if i + 1 < params.len() => {
builder.append_attr_str(TCA_BPF_NAME, ¶ms[i + 1]);
i += 2;
}
"direct-action" | "da" => {
has_da = true;
i += 1;
}
"skip_hw" => {
builder.append_attr_u32(TCA_BPF_FLAGS_GEN, TCA_CLS_FLAGS_SKIP_HW);
i += 1;
}
"skip_sw" => {
builder.append_attr_u32(TCA_BPF_FLAGS_GEN, TCA_CLS_FLAGS_SKIP_SW);
i += 1;
}
_ => i += 1,
}
}
if has_da {
builder.append_attr_u32(TCA_BPF_FLAGS, TCA_BPF_FLAG_ACT_DIRECT);
}
Ok(())
}