Repository navigation
Expand file tree
/
Copy pathbuild.rs
More file actions
192 lines (174 loc) · 7.58 KB
/
Copy pathbuild.rs
File metadata and controls
192 lines (174 loc) · 7.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// build.rs
use std::env;
#[cfg(all(unix, feature = "bpf"))]
use std::ffi::{OsStr, OsString};
#[cfg(all(unix, feature = "bpf"))]
use std::path::{Path, PathBuf};
#[cfg(all(unix, feature = "bpf"))]
use libbpf_cargo::SkeletonBuilder;
const SRC: &str = "src/security/firewall/bpf/xdp.bpf.c";
const HEADER_DIR: &str = "src/security/firewall/bpf";
fn main() {
println!("cargo:rerun-if-changed={}", SRC);
println!("cargo:rerun-if-changed={}/filter.h", HEADER_DIR);
println!("cargo:rerun-if-changed={}/xdp_maps.h", HEADER_DIR);
println!(
"cargo:rerun-if-changed={}/lib/tcp_fingerprinting.h",
HEADER_DIR
);
println!("cargo:rerun-if-changed={}/lib/firewall.h", HEADER_DIR);
println!("cargo:rerun-if-changed={}/lib/helper.h", HEADER_DIR);
println!(
"cargo:rerun-if-changed={}/lib/latency_tracking.h",
HEADER_DIR
);
println!("cargo:rerun-if-changed={}/include/common.h", HEADER_DIR);
println!("cargo:rerun-if-changed={}/lib/ids_export.h", HEADER_DIR);
println!("cargo:rerun-if-env-changed=SYNAPSE_IP_MAP_MAX_V4");
println!("cargo:rerun-if-env-changed=SYNAPSE_IP_MAP_MAX_V6");
// dendrite BPF headers (JA4 capture, structs, maps)
println!("cargo:rerun-if-changed=../dendrite/src/bpf/ja4/ja4_capture.h");
println!("cargo:rerun-if-changed=../dendrite/src/bpf/ja4/structs.h");
println!("cargo:rerun-if-changed=../dendrite/src/bpf/ja4/maps.h");
// On Windows, both synapse's direct dendrite dependency and thalamus's
// transitive dendrite (pinned to a different rev) compile the same
// afxdp_capture_shim C symbols. Tell the MSVC linker to pick the first
// definition instead of erroring on duplicates.
if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
println!("cargo:rustc-link-arg=/FORCE:MULTIPLE");
}
let bpf_feature_set = env::var_os("CARGO_FEATURE_BPF").is_some();
let bpf_enabled = bpf_feature_set;
// eBPF/libbpf is linux/unix oriented — skip on other platforms.
#[cfg(not(unix))]
if bpf_enabled {
println!(
"cargo:warning=BPF feature enabled but target is not unix; skipping BPF skeleton generation"
);
}
#[cfg(unix)]
if !bpf_enabled {
println!(
"cargo:warning=BPF support disabled at build time; skipping BPF skeleton generation"
);
}
#[cfg(all(unix, feature = "bpf"))]
if bpf_enabled {
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH must be set");
let vmlinux_include = vmlinux::include_path_root().join(arch);
assert!(Path::new(&vmlinux_include).exists(), "vmlinux.h not found");
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set"));
let bpf_include = manifest_dir.join(HEADER_DIR);
let include = bpf_include.join("include");
let lib = bpf_include.join("lib");
// dendrite BPF headers (ja4 capture, shared structs).
// Try sibling directory first (local dev), then fall back to cargo
// git checkout (Docker / CI where dendrite is a git dependency).
let dendrite_bpf = {
let local = manifest_dir.join("../dendrite/src/bpf");
if local.exists() {
local
} else {
find_dendrite_bpf_in_cargo()
.expect("dendrite BPF headers not found: checked ../dendrite/src/bpf and $CARGO_HOME/git/checkouts/dendrite-*")
}
};
// ✅ Construct full output path in OUT_DIR
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR must be set"));
let skel_path = out_dir.join("xdp.skel.rs");
// ✅ Pass the full path to build_and_generate
// Build a single args Vec so optional -D overrides are appended rather
// than replacing the fixed -I flags (SkeletonBuilder::clang_args replaces).
let mut clang_args: Vec<OsString> = [
OsStr::new("-I"),
vmlinux_include.as_os_str(),
OsStr::new("-I"),
bpf_include.as_os_str(),
OsStr::new("-I"),
include.as_os_str(),
OsStr::new("-I"),
lib.as_os_str(),
OsStr::new("-O3"), // Maximum optimizations to reduce program size
OsStr::new("-fno-unroll-loops"), // Prevent loop unrolling to reduce instruction count
OsStr::new("-Wall"), // Enable all warnings
OsStr::new("-Wextra"), // Extra warnings
OsStr::new("-DBPF_NO_PRESERVE_ACCESS_INDEX"), // Disable preserve_access_index for older clang
OsStr::new("-Ubpf"), // Undefine bpf macro to avoid conflict with struct netns_bpf bpf
OsStr::new("-I"),
dendrite_bpf.as_os_str(), // dendrite BPF headers (ja4 capture, etc.)
]
.iter()
.copied()
.map(OsStr::to_os_string)
.collect();
clang_args.extend(build_ip_map_defines().into_iter().map(OsString::from));
SkeletonBuilder::new()
.source(SRC)
.clang_args(&clang_args)
.build_and_generate(skel_path.to_str().expect("Invalid UTF-8 in path"))
.expect("Failed to generate skeleton");
println!("✅ Wrote XDP skeleton to: {:?}", skel_path);
}
}
/// Build `-D` overrides for IP map sizes from env vars.
/// SYNAPSE_IP_MAP_MAX_V4 / SYNAPSE_IP_MAP_MAX_V6 let packagers tune the caps
/// for memory-constrained targets without editing the source.
#[cfg(all(unix, feature = "bpf"))]
fn build_ip_map_defines() -> Vec<String> {
let mut args = Vec::new();
if let Ok(v4) = env::var("SYNAPSE_IP_MAP_MAX_V4")
&& v4.parse::<u64>().is_ok()
{
args.push(format!("-DCITADEL_IP_MAP_MAX_V4={}", v4));
}
if let Ok(v6) = env::var("SYNAPSE_IP_MAP_MAX_V6")
&& v6.parse::<u64>().is_ok()
{
args.push(format!("-DCITADEL_IP_MAP_MAX_V6={}", v6));
}
args
}
/// Locate the dendrite `src/bpf` directory inside cargo's git checkout cache.
/// Returns `None` if not found (e.g., dendrite is a path dependency).
#[cfg(all(unix, feature = "bpf"))]
fn find_dendrite_bpf_in_cargo() -> Option<PathBuf> {
let cargo_home = env::var("CARGO_HOME")
.ok()
.or_else(|| dirs_next().map(|h| format!("{}/.cargo", h)))?;
let checkouts = Path::new(&cargo_home).join("git/checkouts");
if !checkouts.exists() {
return None;
}
// Multiple dendrite revisions co-exist in cargo's git cache: synapse's
// direct pin alongside transitive pins from cortex/thalamus. Older revs
// ship `ja4/maps.h` without the `ja4_client_events` ringbuf, so a plain
// existence check returns the wrong checkout under non-deterministic
// readdir order and the generated skel is missing the ringbuf field.
// Match on the marker symbol so only the rev that actually defines the
// ringbuf is selected.
for entry in std::fs::read_dir(&checkouts).ok()?.flatten() {
let name = entry.file_name();
if !name.to_string_lossy().starts_with("dendrite") {
continue;
}
for rev in std::fs::read_dir(entry.path()).ok()?.flatten() {
let bpf_dir = rev.path().join("src/bpf");
let maps_h = bpf_dir.join("ja4/maps.h");
if !maps_h.exists() {
continue;
}
if std::fs::read_to_string(&maps_h)
.map(|s| s.contains("ja4_client_events"))
.unwrap_or(false)
{
return Some(bpf_dir);
}
}
}
None
}
#[cfg(all(unix, feature = "bpf"))]
fn dirs_next() -> Option<String> {
env::var("HOME").ok()
}