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
|
//! This build script leverages `bindgen` to generate rust sys files that link to the libnftnl
//! library. It retrieves the includes needed by `bindgen` using `pkg_config`, and tells cargo
//! the directives needed by the linker to link against the exported symbols.
use bindgen;
use lazy_static::lazy_static;
use pkg_config;
use regex::{Captures, Regex};
use std::borrow::Cow;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
const SYS_HEADER_FILE: &str = "include/wrapper.h";
const SYS_BINDINGS_FILE: &str = "src/sys.rs";
const TESTS_HEADER_FILE: &str = "include/tests_wrapper.h";
const TESTS_BINDINGS_FILE: &str = "tests/sys.rs";
const MIN_LIBNFTNL_VERSION: &str = "1.0.6";
const MIN_LIBMNL_VERSION: &str = "1.0.0";
fn main() {
pkg_config_mnl();
let clang_args = pkg_config_nftnl();
generate_sys(clang_args.into_iter());
generate_tests_sys();
}
/// Setup rust linking directives for libnftnl, and return the include directory list.
fn pkg_config_nftnl() -> Vec<String> {
let mut res = vec![];
if let Some(lib_dir) = get_env("LIBNFTNL_LIB_DIR") {
if !lib_dir.is_dir() {
panic!(
"libnftnl library directory does not exist: {}",
lib_dir.display()
);
}
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=nftnl");
} else {
// Trying with pkg-config instead
println!("Minimum libnftnl version: {}", MIN_LIBNFTNL_VERSION);
let pkg_config_res = pkg_config::Config::new()
.atleast_version(MIN_LIBNFTNL_VERSION)
.probe("libnftnl")
.unwrap();
for path in pkg_config_res.include_paths {
res.push(format!("-I{}", path.to_str().unwrap()));
}
}
res
}
/// Setup rust linking directives for libmnl.
fn pkg_config_mnl() {
if let Some(lib_dir) = get_env("LIBMNL_LIB_DIR") {
if !lib_dir.is_dir() {
panic!(
"libmnl library directory does not exist: {}",
lib_dir.display()
);
}
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=mnl");
} else {
// Trying with pkg-config instead
pkg_config::Config::new()
.atleast_version(MIN_LIBMNL_VERSION)
.probe("libmnl")
.unwrap();
}
}
fn get_env(var: &'static str) -> Option<PathBuf> {
println!("cargo:rerun-if-env-changed={}", var);
env::var_os(var).map(PathBuf::from)
}
/// `bindgen`erate a rust sys file from the C headers of the nftnl library.
fn generate_sys(clang_args: impl Iterator<Item = String>) {
// Tell cargo to invalidate the built crate whenever the headers change.
println!("cargo:rerun-if-changed={}", SYS_HEADER_FILE);
let bindings = bindgen::Builder::default()
.header(SYS_HEADER_FILE)
.clang_args(clang_args)
.generate_comments(false)
.prepend_enum_name(false)
.use_core()
.whitelist_function("^nftnl_.+$")
.whitelist_type("^nftnl_.+$")
.whitelist_var("^nftnl_.+$")
.whitelist_var("^NFTNL_.+$")
.blacklist_type("(FILE|iovec)")
.blacklist_type("^_IO_.+$")
.blacklist_type("^__.+$")
.blacklist_type("nlmsghdr")
.raw_line("#![allow(non_camel_case_types)]\n\n")
.raw_line("pub use libc;")
.raw_line("use libc::{c_char, c_int, c_ulong, c_void, iovec, nlmsghdr, FILE};")
.raw_line("use core::option::Option;")
.ctypes_prefix("libc")
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Error: unable to generate bindings");
let mut s = bindings.to_string()
// Add newlines because in alpine bindgen doesn't add them after
// statements.
.replace(" ; ", ";\n")
.replace("#[derive(Debug, Copy, Clone)]", "");
let re = Regex::new(r"libc::(c_[a-z]*)").unwrap();
s = re.replace_all(&s, "$1").into();
let re = Regex::new(r"::core::option::(Option)").unwrap();
s = re.replace_all(&s, "$1").into();
let re = Regex::new(r"_bindgen_ty_[0-9]+").unwrap();
s = re.replace_all(&s, "u32").into();
// Change struct bodies to c_void.
let re = Regex::new(r"(pub struct .*) \{\n *_unused: \[u8; 0\],\n\}\n").unwrap();
s = re.replace_all(&s, "$1(c_void);\n").into();
let re = Regex::new(r"pub type u32 = u32;\n").unwrap();
s = re.replace_all(&s, "").into();
// Write the bindings to the rust header file.
let out_path = PathBuf::from(SYS_BINDINGS_FILE);
File::create(out_path)
.expect("Error: could not create rust header file.")
.write_all(&s.as_bytes())
.expect("Error: could not write to the rust header file.");
}
/// `bindgen`erate a rust sys file from the C kernel headers of the nf_tables capabilities.
/// Used in the rustables tests.
fn generate_tests_sys() {
// Tell cargo to invalidate the built crate whenever the headers change.
println!("cargo:rerun-if-changed={}", TESTS_HEADER_FILE);
let bindings = bindgen::Builder::default()
.header(TESTS_HEADER_FILE)
.generate_comments(false)
.prepend_enum_name(false)
.raw_line("#![allow(non_camel_case_types, dead_code)]\n\n")
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Error: unable to generate bindings needed for tests.");
// Add newlines because in alpine bindgen doesn't add them after statements.
let s = bindings.to_string().replace(" ; ", ";\n");
let s = reformat_units(&s);
// Write the bindings to the rust header file.
let out_path = PathBuf::from(TESTS_BINDINGS_FILE);
File::create(out_path)
.expect("Error: could not create rust header file.")
.write_all(&s.as_bytes())
.expect("Error: could not write to the rust header file.");
}
/// Recast nft_*_attributes from u32 to u16 in header string `header`.
fn reformat_units(header: &str) -> Cow<str> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(pub type nft[a-zA-Z_]*_attributes) = u32;").unwrap();
}
RE.replace_all(header, |captures: &Captures| {
format!("{} = u16;", &captures[1])
})
}
|