diff --git a/retis-events/src/ovs.rs b/retis-events/src/ovs.rs index 2f7f0516..1f1406c0 100644 --- a/retis-events/src/ovs.rs +++ b/retis-events/src/ovs.rs @@ -51,6 +51,10 @@ pub enum OvsEventType { /// Action execution event. It indicates the datapath has executed an action on a packet. #[serde(rename = "action_execute")] Action(ActionEvent), + + /// Flow lookup event. It indicates the datapath has successfully perfomed a lookup for a key. + #[serde(rename = "flow_lookup")] + DpLookup(LookupEvent), } impl EventFmt for OvsEventType { @@ -63,6 +67,7 @@ impl EventFmt for OvsEventType { RecvUpcall(e) => e, Operation(e) => e, Action(e) => e, + DpLookup(e) => e, }; disp.event_fmt(f, format) @@ -107,6 +112,57 @@ impl EventFmt for UpcallEvent { } } +#[event_type] +#[derive(Copy, Default, PartialEq)] +pub struct Ufid(pub u32, pub u32, pub u32, pub u32); + +impl From<[u32; 4]> for Ufid { + fn from(parts: [u32; 4]) -> Self { + Self(parts[0], parts[1], parts[2], parts[3]) + } +} + +impl fmt::Display for Ufid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}", + self.0, + self.1 >> 16, + self.1 & 0xffff, + self.2 >> 16, + self.2 & 0xffff, + self.3 + ) + } +} + +/// OVS lookup event +#[event_type] +#[derive(Copy, Default, PartialEq)] +pub struct LookupEvent { + /// flow pointer + pub flow: u64, + /// actions pointer + pub sf_acts: u64, + /// Flow UFID. + pub ufid: Ufid, + /// n_mask_hit. + pub n_mask_hit: u32, + /// n_cache_hit. + pub n_cache_hit: u32, +} + +impl EventFmt for LookupEvent { + fn event_fmt(&self, f: &mut Formatter, _: &DisplayFormat) -> fmt::Result { + write!( + f, + "ufid {} hit (mask/cache) {}/{} flow {:x} sf_acts {:x}", + self.ufid, self.n_mask_hit, self.n_cache_hit, self.flow, self.sf_acts, + ) + } +} + /// Upcall enqueue event. #[event_type] #[derive(Copy, Default, PartialEq)] diff --git a/retis/src/bindings/kernel_flow_tbl_lookup_ret_uapi.rs b/retis/src/bindings/kernel_flow_tbl_lookup_ret_uapi.rs new file mode 100644 index 00000000..2d1e210b --- /dev/null +++ b/retis/src/bindings/kernel_flow_tbl_lookup_ret_uapi.rs @@ -0,0 +1,22 @@ +/* automatically generated by rust-bindgen 0.70.1 */ + +pub type __u32 = ::std::os::raw::c_uint; +pub type u32_ = __u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct flow_lookup_ret_event { + pub flow: *mut ::std::os::raw::c_void, + pub sf_acts: *mut ::std::os::raw::c_void, + pub ufid: [u32_; 4usize], + pub n_mask_hit: u32_, + pub n_cache_hit: u32_, +} +impl Default for flow_lookup_ret_event { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} diff --git a/retis/src/bindings/mod.rs b/retis/src/bindings/mod.rs index 6262ae72..e6dd1c5d 100644 --- a/retis/src/bindings/mod.rs +++ b/retis/src/bindings/mod.rs @@ -63,6 +63,7 @@ pub(crate) mod kernel_exec_tp_uapi; pub(crate) mod kernel_upcall_ret_uapi; pub(crate) mod kernel_upcall_tp_uapi; +pub(crate) mod kernel_flow_tbl_lookup_ret_uapi; pub(crate) mod ovs_common_uapi; pub(crate) mod ovs_operation_uapi; pub(crate) mod user_recv_upcall_uapi; diff --git a/retis/src/bindings/ovs_common_uapi.rs b/retis/src/bindings/ovs_common_uapi.rs index 073cd1f7..29f36a59 100644 --- a/retis/src/bindings/ovs_common_uapi.rs +++ b/retis/src/bindings/ovs_common_uapi.rs @@ -27,3 +27,17 @@ impl Default for execute_actions_ctx { } } } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct processing_ctx { + pub skb: *mut ::std::os::raw::c_void, +} +impl Default for processing_ctx { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} diff --git a/retis/src/module/ovs/bpf.rs b/retis/src/module/ovs/bpf.rs index a6c790bf..b4dfab09 100644 --- a/retis/src/module/ovs/bpf.rs +++ b/retis/src/module/ovs/bpf.rs @@ -10,6 +10,7 @@ use crate::{ bindings::{ kernel_enqueue_uapi::upcall_enqueue_event, kernel_exec_tp_uapi::{exec_ct, exec_event, exec_output, exec_recirc, exec_track_event}, + kernel_flow_tbl_lookup_ret_uapi::flow_lookup_ret_event, kernel_upcall_ret_uapi::upcall_ret_event, kernel_upcall_tp_uapi::upcall_event, ovs_operation_uapi::ovs_operation_event, @@ -46,6 +47,8 @@ pub(crate) enum OvsDataType { RecircAction = 8, /// Conntrack action. ConntrackAction = 9, + /// Flow lookup + FlowLookup = 10, } impl OvsDataType { @@ -62,11 +65,25 @@ impl OvsDataType { 7 => OutputAction, 8 => RecircAction, 9 => ConntrackAction, + 10 => FlowLookup, x => bail!("Can't construct a OvsDataType from {}", x), }) } } +pub(super) fn unmarshall_flow_lookup(raw_section: &BpfRawSection) -> Result { + let raw = parse_raw_section::(raw_section)?; + Ok(OvsEvent { + event: OvsEventType::DpLookup(LookupEvent { + flow: raw.flow as usize as u64, + sf_acts: raw.sf_acts as usize as u64, + ufid: raw.ufid.into(), + n_mask_hit: raw.n_mask_hit, + n_cache_hit: raw.n_cache_hit, + }), + }) +} + pub(super) fn unmarshall_upcall(raw_section: &BpfRawSection) -> Result { let raw = parse_raw_section::(raw_section)?; Ok(OvsEvent { @@ -310,6 +327,9 @@ impl RawEventSectionFactory for OvsEventFactory { OvsDataType::ActionExec => { event = Some(unmarshall_exec(section)?); } + OvsDataType::FlowLookup => { + event = Some(unmarshall_flow_lookup(section)?); + } OvsDataType::ActionExecTrack => unmarshall_exec_track( section, event diff --git a/retis/src/module/ovs/bpf/include/ovs_common.h b/retis/src/module/ovs/bpf/include/ovs_common.h index bf0f7cb9..bea22daa 100644 --- a/retis/src/module/ovs/bpf/include/ovs_common.h +++ b/retis/src/module/ovs/bpf/include/ovs_common.h @@ -18,6 +18,7 @@ enum trace_ovs_data_type { OVS_DP_ACTION_OUTPUT = 7, OVS_DP_ACTION_RECIRC = 8, OVS_DP_ACTION_CONNTRACK = 9, + OVS_FLOW_TBL_LOOKUP_RETURN = 10, }; /* Used to keep the context of an upcall operation for its upcall enqueue @@ -54,6 +55,23 @@ struct execute_actions_ctx { bool command; } __binding; +/* Context saved between the begining of ovs_dp_process_packet and the end of + ovs_flow_tbl_lookup_stats. */ +struct processing_ctx { + BINDING_PTR(struct sk_buff *, skb); +} __binding; + +/* Map used used to extract the ufid during the lookup in the regular + * rx path, meaning it is used also for keeping context between the + * beginning of ovs_dp_process_packet and the end of ovs_flow_tbl_lookup_stats. + * Indexed by pid_tgid. */ +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_INFLIGHT_UPCALLS); + __type(key, u64); + __type(value, struct processing_ctx); +} inflight_processing SEC(".maps"); + /* Map used to store context between the begining and end of * ovs_execute_actions calls. Indexed by pid_tgid. */ struct { diff --git a/retis/src/module/ovs/bpf/kernel_flow_tbl_lookup_ret.bpf.c b/retis/src/module/ovs/bpf/kernel_flow_tbl_lookup_ret.bpf.c new file mode 100644 index 00000000..f6a56f3b --- /dev/null +++ b/retis/src/module/ovs/bpf/kernel_flow_tbl_lookup_ret.bpf.c @@ -0,0 +1,74 @@ +#include +#include + +#define MAX_UFID_LENGTH 16 + +struct flow_lookup_ret_event { + BINDING_PTR(struct sw_flow *, flow); + BINDING_PTR(struct sw_flow_actions *, sf_acts); + u32 ufid[MAX_UFID_LENGTH / 4]; + u32 n_mask_hit; + u32 n_cache_hit; +} __binding; + +/* Hook for kretprobe:ovs_flow_tbl_lookup_stats */ +DEFINE_HOOK_RAW( + u64 tid = bpf_get_current_pid_tgid(); + struct flow_lookup_ret_event *ret; + struct processing_ctx *pctx; + struct sw_flow *flow; + u32 ufid_len = 0; + + pctx = bpf_map_lookup_elem(&inflight_processing, &tid); + if (!pctx) + return 0; + + flow = (struct sw_flow *)ctx->regs.ret; + if (!flow) + /* No flows. This is most likely an upcall. + * There's no much we can do other than clean-up + * the map and return. + */ + goto out_clean; + + ufid_len = BPF_CORE_READ(flow, id.ufid_len); + if (!ufid_len) { + log_error("Expected ufid representation expected, found key"); + goto out_clean; + } + + ret = get_event_section(event, COLLECTOR_OVS, + OVS_FLOW_TBL_LOOKUP_RETURN, + sizeof(*ret)); + if (!ret) + goto out_clean; + + if (BPF_CORE_READ_INTO(&ret->ufid, flow, id.ufid)) + log_error("Failed to read the ufid"); + + ret->flow = flow; + + if (bpf_core_read(&ret->sf_acts, sizeof(ret->sf_acts), &flow->sf_acts)) + log_error("Failed to read sf_acts"); + + /* Only log in case of failure while retrieving ancillary + * informations. + */ + if (bpf_probe_read_kernel(&ret->n_mask_hit, sizeof(ret->n_mask_hit), + (void *)ctx->regs.reg[3]) < 0) { + log_error("Failed to retrieve n_mask_hit from 0x%p", + ctx->regs.reg[3]); + } + + if (bpf_probe_read_kernel(&ret->n_cache_hit, sizeof(ret->n_cache_hit), + (void *)ctx->regs.reg[4]) < 0) { + log_error("Failed to retrieve n_cache_hit from 0x%p", + ctx->regs.reg[4]); + } + +out_clean: + bpf_map_delete_elem(&inflight_processing, &tid); + return 0; +) + +char __license[] SEC("license") = "GPL"; diff --git a/retis/src/module/ovs/bpf/kernel_process_packet.bpf.c b/retis/src/module/ovs/bpf/kernel_process_packet.bpf.c new file mode 100644 index 00000000..fb56c67f --- /dev/null +++ b/retis/src/module/ovs/bpf/kernel_process_packet.bpf.c @@ -0,0 +1,24 @@ +#include +#include + +/* Hook for kprobe:ovs_dp_process_packet. */ +DEFINE_HOOK(F_AND, RETIS_ALL_FILTERS, + u64 tid = bpf_get_current_pid_tgid(); + struct processing_ctx pctx = {}; + long err; + + pctx.skb = retis_get_sk_buff(ctx); + if (!pctx.skb) { + log_error("Invalid skb while ovs is processing the packet"); + return 0; + } + + if ((err = bpf_map_update_elem(&inflight_processing, &tid, &pctx, BPF_ANY))) { + log_error("Failed to set processing entry at index %lu with err: %lu", tid, err); + return 0; + } + + return 0; +) + +char __license[] SEC("license") = "GPL"; diff --git a/retis/src/module/ovs/mod.rs b/retis/src/module/ovs/mod.rs index 95c3933d..11614b87 100644 --- a/retis/src/module/ovs/mod.rs +++ b/retis/src/module/ovs/mod.rs @@ -22,6 +22,12 @@ mod hooks { pub(super) mod kernel_exec_tp { include!("bpf/.out/kernel_exec_tp.rs"); } + pub(super) mod kernel_process_packet { + include!("bpf/.out/kernel_process_packet.rs"); + } + pub(super) mod kernel_tbl_lookup_ret { + include!("bpf/.out/kernel_flow_tbl_lookup_ret.rs"); + } pub(super) mod kernel_upcall_tp { include!("bpf/.out/kernel_upcall_tp.rs"); } diff --git a/retis/src/module/ovs/ovs.rs b/retis/src/module/ovs/ovs.rs index 50f9b10d..51b9ed9f 100644 --- a/retis/src/module/ovs/ovs.rs +++ b/retis/src/module/ovs/ovs.rs @@ -12,7 +12,7 @@ use clap::{arg, Parser}; use super::{bpf::OvsEventFactory, hooks}; use crate::{ bindings::{ - ovs_common_uapi::{execute_actions_ctx, upcall_context}, + ovs_common_uapi::{execute_actions_ctx, processing_ctx, upcall_context}, ovs_operation_uapi::upcall_batch, }, cli::{dynamic::DynamicCommand, CliConfig}, @@ -56,6 +56,7 @@ pub(crate) struct OvsModule { track: bool, inflight_upcalls_map: Option, inflight_exec_map: Option, + inflight_processing_map: Option, /* Tracking file descriptors (the maps are owned by the GC) */ flow_exec_tracking_fd: i32, @@ -120,6 +121,8 @@ impl Collector for OvsModule { self.add_upcall_hooks(probes)?; // Exec related hooks self.add_exec_hooks(probes)?; + // Processing related hooks + self.add_processing_hooks(probes)?; Ok(()) } @@ -204,6 +207,23 @@ impl OvsModule { .or_else(|e| bail!("Could not create the inflight_exec map: {}", e)) } + fn create_inflight_processing_map() -> Result { + let opts = libbpf_sys::bpf_map_create_opts { + sz: mem::size_of::() as libbpf_sys::size_t, + ..Default::default() + }; + + libbpf_rs::MapHandle::create( + libbpf_rs::MapType::Hash, + Some("inflight_processing"), + mem::size_of::() as u32, + mem::size_of::() as u32, + 50, + &opts, + ) + .or_else(|e| bail!("Could not create the inflight_exec map: {}", e)) + } + fn create_inflight_upcalls_map() -> Result { let opts = libbpf_sys::bpf_map_create_opts { sz: mem::size_of::() as libbpf_sys::size_t, @@ -261,7 +281,7 @@ impl OvsModule { nhandlers as u32, &opts, ) - .or_else(|e| bail!("Could not create the upcall_batches map: {}", e))?, + .or_else(|e| bail!("Could not create the pid_to_batch map: {}", e))?, ); /* Populate pid_to_batch map. */ @@ -347,6 +367,35 @@ impl OvsModule { Ok(()) } + /// Add dp processing hooks. + fn add_processing_hooks(&mut self, probes: &mut ProbeBuilderManager) -> Result<()> { + let inflight_processing_map = Self::create_inflight_processing_map()?; + // ovs_dp_process_packet kprobe + let mut ovs_dp_process_packet_hook = Hook::from(hooks::kernel_process_packet::DATA); + ovs_dp_process_packet_hook.reuse_map( + "inflight_processing", + inflight_processing_map.as_fd().as_raw_fd(), + )?; + let mut probe = Probe::kprobe(Symbol::from_name("ovs_dp_process_packet")?)?; + probe.set_option(ProbeOption::NoGenericHook)?; + probe.add_hook(ovs_dp_process_packet_hook)?; + probes.register_probe(probe)?; + + // ovs_flow_tbl_lookup_stats kretprobe + let mut ovs_flow_tbl_lookup_stats = Hook::from(hooks::kernel_tbl_lookup_ret::DATA); + ovs_flow_tbl_lookup_stats.reuse_map( + "inflight_processing", + inflight_processing_map.as_fd().as_raw_fd(), + )?; + let mut probe = Probe::kretprobe(Symbol::from_name("ovs_flow_tbl_lookup_stats")?)?; + probe.set_option(ProbeOption::NoGenericHook)?; + probe.add_hook(ovs_flow_tbl_lookup_stats)?; + probes.register_probe(probe)?; + + self.inflight_processing_map = Some(inflight_processing_map); + Ok(()) + } + /// Add USDT hooks. fn add_usdt_hooks(&mut self, probes: &mut ProbeBuilderManager) -> Result<()> { let ovs = Process::from_cmd("ovs-vswitchd")?; diff --git a/retis/src/process/tracking.rs b/retis/src/process/tracking.rs index 0d7dfbd2..92af2ac7 100644 --- a/retis/src/process/tracking.rs +++ b/retis/src/process/tracking.rs @@ -133,6 +133,7 @@ impl AddTracking { self.process_skb(event)?; } }, + DpLookup(_lookup) => todo!(), } } else { // It's not an OVS event, try skb-only tracking.