네트워크 워터마크는 Anti-DDoS 공격 및 네트워크 트래픽과 같은 보안 분야에서 사용됩니다.메시지에 특성 정보를 추가하는 것이 원칙입니다.TCP 옵션을 기반으로 추가된 필드는 방화벽 및 기타 수정에 의해 제거되지 않습니다.이 기사에서는 BPF 및 Rust를 사용합니다. 효율적인 구현 특정 설명을 달성하기 위해. 1 소스코드는 [] 참조 지적재산권 보호를 위한 이미지 워터마크는 여기에 기재되어 있지 않습니다.
- BPF 드라이버는 c를 사용하여 개발되었습니다.
- Userland 구성 및 로더는 Rust 및 libbpf-rs를 사용하여 개발 속도를 높입니다.
종속성 캡슐화
libbpf-rs 개발을 기반으로 libbpf-rs는 드라이버 개체 및 해당 정적 프로그램, 통신 및 마운트된 프로그램의 리소스 추상화를 포함하여 개발을 위한 API 추상화를 제공합니다.
동시에 libbpf-sys는 안전하지 않은 libbpf, libelf 및 zlib를 캡슐화하며 핵심은 커널의 일부인 libbpf를 기반으로 개발된 정적 링크 라이브러리입니다. elf 파일에서 사용하는 동적으로 연결된 libelf 및 zlib 드라이버를 로드하고 구문 분석하는 자세한 API
프로젝트 디렉토리는 libbpf-cargo 스캐폴딩을 기반으로 생성되며 빌드 명령은 gen 및 make를 호출하여 완료합니다.
프로젝트 디렉토리 구조
libbpf-cargo에 의해 자동으로 생성된 스켈레톤 디렉토리용 코드
netoken\
src\
bpf\ //驱动
.output\ //脚手架自动生成
netoken.skel.rs //=>libbpf_rs
netoken.c ^
vmlinux.h |
main.rs //=>libbpf_rs |
build.rs //=>libbpf_cargo-+
Cargo.toml
libbpf-rs\
.git
libbpf-rs\
libbpf-cargo\
libbpf-xxx의 특정 위치는 Cargo.toml에서 구성할 수 있습니다.
[dependencies]
libbpf-rs = { path = "../libbpf-rs/libbpf-rs" }
[build-dependencies]
libbpf-cargo = { path = "../libbpf-rs/libbpf-cargo" }
스켈레톤 코드 생성 프로세스
- 사용자 프로젝트 build.rs->libbpf-cargo.rs SkeletonBuilder() .bpf.c => .output/ .skel.rs
- 사용자 프로젝트에서 생성된 *.skel.rs 프로세스:
- 객체: 데이터 [u8]
- SkelBuilder()->Open Skel()->*Skel(), progs/maps/links
- build_skel_config()->ObjectSkeletonConfigBuilder::new(DATA)
- builder.name( ).map("").prog("handle_ ").build()
- libbpf-rs는 사용자를 위한 공통 도구를 제공합니다.
- 물체/골격
- 프로그램/지도/링크
- iter/인쇄/쿼리/유틸
- 성능/링버프
- libbpf-cargo는 cargo build, gen/make를 제공할 때 자동으로 skel을 생성합니다.
- *.skel.rs 프로세스 실행
- TcSkelBuilder.open()->OpenTcSkel(obj, 구성)
- OpenTcSkel.load() >> bpf_object__load_skeleton(config)
- OpenTcSkel.load()->TcSkel(obj,config, Tclinks())
- OpenTcSkel.progs()->OpenTcProgs(obj)
- OpenTcSkel.maps()->OpenTcMaps()
- OpenTcSkel.data()->ffi::c_void()
- TcSkel.attach() >> bpf_object__attach_skeleton(config)
- TcSkel.links = TcLinks(handle_tc)
- libbpf.h typedef 구조체 bpf_object_skeleton bos
- libbpf는 커널 bpf_xxx를 3개의 구조체와 4단계 API로 캡슐화하여 커널의 많은 세부 정보를 숨깁니다.
- 오류/인쇄/open_opts/
- bpf_object_open_[버퍼/메모리/xattr]xxx
- bpf_object__load/__next/__set/핀/핀 해제
- bpf_[프로그램/맵/링크]__set/load/fd/xxx;__attach_xxx
- bpf_map__[세트/얻기/찾기_맵]
- bpf_perf/kprob/uprobe/tracepoint/link_xdp/tc_hook
사용자 모드 로딩 프로세스
- 명령줄 매개변수에서 네트워크 카드 이름을 구문 분석하고 시스템에서 네트워크 카드 번호를 가져옵니다.
- 생성된 skelBuilder를 사용하여 드라이버 개체의 메모리 파일 설명자 fd를 점진적으로 가져옵니다.
- 생성된 hookBuilder를 사용하여 네트워크 항목에서 자신을 생성 및 연결하고 egress/ingress를 종료하십시오. 이 프로그램은 이그레스만 사용합니다.
- egress가 커널에 로드되고 실행을 시작합니다.
- tc의 출구 정보 조회
fn main() -> Result<()> {
// 命令行参数解析
let opts = Command::parse();
// 去掉系统的内存限制
bump_memlock_rlimit()?;
// 从Skel里获取信息,从opts里获取ifindex
let builder = netokenSkelBuilder::default();
let open = builder.open()?;
let skel = open.load()?;
let fd = skel.progs().handle_tc().fd();
let ifidx = nix::net::if_::if_nametoindex(opts.iface.as_str())? as i32;
let mut tc_builder = TcHookBuilder::new();
tc_builder
.fd(fd)
.ifindex(ifidx)
.replace(true)
.handle(1)
.priority(1);
// 挂载驱动到TC的egress接口上
let mut egress = tc_builder.hook(TC_EGRESS);
// 执行attach,驱动开始工作
if opts.attach {
if let Err(e) = egress.attach() {
bail!("failed to attach egress hook {}", e);
}
}
// 执行destory,销毁驱动
if opts.destroy {
if let Err(e) = egress.detach() {
println!("failed to detach egress hook {}", e);
}
if let Err(e) = egress.destroy() {
println!("failed to destroy {}", e);
}
}
// 执行query,查询执行驱动的id
match egress.query() {
Err(e) => println!("failed to find egress hook: {}", e),
Ok(prog_id) => println!("found egress hook prog_id: {}", prog_id),
}
// 执行监听perf,收到数据后进入handle_event,收到错误丢包进入handle_lost_events
let perf = PerfBufferBuilder::new(skel.maps_mut().events())
.sample_cb(handle_event)
.lost_cb(handle_lost_events)
.build()?;
// 100ms一次的轮询驱动,有事件后进入上面的event
loop {
perf.poll(Duration::from_millis(100))?;
}
Ok(())
}
tc 도구 기반 구현과 유사
$ tc qdisc add dev xxx
$ tc filter [add|change|replace] dev xxx
$ tc qdisc show dev xxx
드라이브 프로세스
- 입력 파라미터 struct __sk_buff가 tcp 메시지인지 확인하고, 2계층부터 4계층까지 차례로 분석 및 판단
- tcp 패킷이 핸드셰이크 syn 패킷인지 확인합니다. syn 패키지는 두 당사자 간의 협상 기능에 대한 옵션을 제공합니다.
- 다른 정책에서 토큰을 얻으려면 정책 맵을 읽으십시오.
- syn 패키지에 토큰 옵션 추가
// 驱动入口,数据已由内核组装为__sk_buff
SEC("tc")
int handle_tc(struct __sk_buff* ctx) {
struct pkthdr pkt;
RET_IF(pkt_check(ctx, &pkt) != RET_OK);
RET_IF(pkt.tcp->syn != 1 || pkt.tcp->ack != 0);
update_token_by_policy();
RET_IF(extend_options_token(ctx, &pkt, epp_token) != RET_OK);
return TC_ACT_OK;
}
// 检查是否tcp包
BPF_INLNE int pkt_check(struct __sk_buff* ctx, struct pkthdr* pkt) {
pkt->data = (void*)(long)ctx->data;
pkt->data_end = (void*)(long)ctx->data_end;
pkt->eth = pkt->data;
pkt->ipv4 = pkt->data + sizeof(struct ethhdr);
RET_ERR_IF(pkt->eth + 1 > (struct ethhdr*)(pkt->data_end));
RET_ERR_IF(pkt->eth->h_proto != bpf_constant_htons(ETH_P_IP));
RET_ERR_IF(pkt->ipv4 + 1 > (struct iphdr*)(pkt->data_end));
RET_ERR_IF(pkt->ipv4->protocol != IPPROTO_TCP);
pkt->tcp = pkt->data + sizeof(struct ethhdr) + (pkt->ipv4->ihl * 4);
RET_ERR_IF(pkt->tcp + 1 > (struct tcphdr*)(pkt->data_end));
return RET_OK;
}
// 增加tcp的options的token option,对网卡以支持的offload计算checksum的,注释掉加快执行
BPF_INLNE int extend_options_token(struct __sk_buff* ctx, struct pkthdr* pkt, u64 token) {
u32 data_end = ctx->len; // 非线性包总长
u16 sz = sizeof(token);
pkt->ipv4->tot_len = bpf_htons(pkt->ipv4->ihl * 4 + pkt->tcp->doff * 4 + sz);
pkt->tcp->doff = pkt->tcp->doff + sz / 4;
RET_IF(bpf_skb_change_tail(ctx, ctx->len + sz, 0));
RET_IF(bpf_skb_store_bytes(ctx, data_end, &token, sizeof(token), 0));
RET_IF(bpf_l3_csum_replace(ctx, IP_CSUM_OFFSET, 0, bpf_constant_htons(sz), 0));
// RET_IF(bpf_l4_csum_replace(ctx, TCP_CSUM_OFFSET, 0, sz / 4, BPF_F_PSEUDO_HDR | sizeof(u8)))
u16 csum = bpf_csum_diff(0, 0, (u32*)&token, sizeof(token), 0); // 2 tcp pseudo
// RET_IF(bpf_l4_csum_replace(ctx, TCP_CSUM_OFFSET, 0, csum, 0));
update_metrics();
return RET_OK;
}
확장된 xdp
libbpf-rs는 attach_xdp 인터페이스만 제공하며 다른 xdp는 libbpf 프로젝트에서 독립적으로 구현됩니다.
af_xdp의 고급 기능이 필요한 경우 libxdp-rs를 사용해 볼 수 있습니다. 이는 Tencent 직원이 개발하고 주로 Rust 바인딩의 xdp-tools 기능을 사용하며 그 중 독자적으로 개발한 libxdp가 있습니다.
aya와 같은 다른 Rust bpf 프로젝트에 대한 간략한 소개
libpf-rs의 복잡성이 낮고 개발 시작점도 낮으며 안전하지 않은 코드가 거의 없습니다. Aya와 redbpf는 차원이 높고 능력과 야망이 뛰어나므로 당연히 난이도가 더 큽니다.
먼저 녹을 사용하여 드라이버를 작성하고 표준이 없으며 더 안전하지 않고 MaybeUninit, 내 마음이 두렵습니다.
또한 성공적인 개발 사례가 많지 않아 사업 복잡도가 높다면 고려할 수 있다. 하지만 걱정하지 마세요. 커널 5.20이 Rust에 합류한 후에 시작할 수 있습니다.
드라이버는 녹의 표현력을 반영할 수 있습니다.
#![no_std] //
#![no_main] //
use aya_bpf::{ macros::xdp, bindings::xdp_action, programs::XdpContext,
maps::{HashMap, PerfEventArray}, };
use aya_log_ebpf::info;
use myapp_common::PacketLog;
#[map(name = "EVENTS")] // map macro
static mut EVENTS: PerfEventArray<ip_src> =
PerfEventArray::<ip_src>::with_max_entries(1024, 0);
#[xdp(name="myapp")] // hook点用macro实现,很rust
pub fn myapp(ctx: XdpContext) -> u32 {
// match匹配xdp_ctx也很直观
match unsafe { try_myapp(ctx) } {
Ok(ret) => ret,
Err(_) => xdp_action::XDP_ABORTED,
}
}
unsafe fn try_myapp(ctx: XdpContext) -> Result<u32, u32> {
// 方便的printk宏
info!(&ctx, "received a packet");
unsafe {
EVENTS.output(&ctx, &ip_src, 0); // 隐去ip_src的解析过程...
}
Ok(xdp_action::XDP_PASS)
}
#[panic_handler] // hook在异常上
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
}
사용자 상태
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let mut bpf = Bpf::load(include_bytes_aligned!(
"../../target/bpfel-unknown-none/release/myapp"
))?;
let program: &mut Xdp = bpf.program_mut("xdp").unwrap().try_into()?;
program.load()?;
program.attach(&opt.iface, XdpFlags::default())
.context("failed to attach the XDP program with default flags")?;
...
let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut("EVENTS")?)?;
// 下面还可以封装简化下
for cpu_id in online_cpus()? { // iter vec<u32> cpus
let mut buf = perf_array.open(cpu_id, None)?;
task::spawn(async move {
let mut buffers = (0..10)
.map(|_| BytesMut::with_capacity(1024))
.collect::<Vec<_>>();
loop {
let events = buf.read_events(&mut buffers).await.unwrap();
for i in 0..events.read {
let buf = &mut buffers[i];
let ptr = buf.as_ptr() as *const ip_src;
let data = unsafe { ptr.read_unaligned() };
let src_addr = net::Ipv4Addr::from(data.ip_src);
println!("LOG: SRC {}, ACTION {}", src_addr, data.action);
}
}
});
}
signal::ctrl_c().await.expect("failed to listen for event");
Ok::<_, anyhow::Error>(())
}
위는 작은 부분에 불과하며 더 많은 지원이 있습니다.
- 프로브
- 추적점
- 소켓 프로그램
- 분류자
- 씨그룹
- PDP
- LSM
다음은 API 참조로 사용됩니다.
libbpf-rs의 API
struct bpf_map_skeleton { *name, **map }
struct bpf_prog_skeleton { *name, **prog }
struct bpf_object_skeleton { **obj, *maps(skel), *progs(skel) }
int bpf_object__open_skeleton(bos *s, *opts);
int bpf_object__load_skeleton(bos *s);
int bpf_object__attach_skeleton(bos *s);
void bpf_object__detach_skeleton(bos *s);
void bpf_object__destroy_skeleton(bos *s);
build.rs는 자동으로 코드 템플릿 tc.skel.rs를 생성합니다.
마지막으로 데이터 맵의 progs, 맵, 필드를 포함하여 TcSkel 사용자에게 반환되었습니다. bpf.c의 맵에 따라 Generate call->libbpf-cargo::lib.rs::SkeletonBuilder().build_generate(&skel)
pub struct
TcSkelBuilder.ObjectBuilder,
OpenTcSkel.OpenObject,
TcSkel.Object,
OpenTcProgs.OpenObject,
TcProgs.Object,
OpenTcMaps.OpenObject,
TcMaps.Object,
TcLinks.Option<Link>,
TcSkelBuilder.open()->OpenTcSkel(obj, config)
OpenTcSkel.load() >> bpf_object__load_skeleton(config)
OpenTcSkel.load()->TcSkel(obj,config, Tclinks())
OpenTcSkel.progs()->OpenTcProgs(obj)
OpenTcSkel.maps()->OpenTcMaps()
OpenTcSkel.data()->ffi::c_void()
TcSkel.attach() >> bpf_object__attach_skeleton(config)
TcSkel.links = TcLinks(handle_tc)
TcProgs.handle_tc
TcMaps.[ports、data、rodata]
Rust-bindgen에 의해 자동으로 생성된 bindings.rs
10개의 헤더 파일에서 build.rs 규칙을 사용하여 process::Command::new("make") bpf.h libbpf.h btf.h xsk.h bpf_helpers.h bpf_helper_defs.h bpf_tracing.h bpf_endian.h bpf_core_read.h를 생성합니다. libbpf_common.h
libbpf-rs 함수
다양한 도구 제공
tc.rs
TcHookBuilder-> TcHook
tc_builder
.fd(fd)
.ifindex(ifidx)
.replace(true)
.handle(1)
.priority(1);
TcHook
tc_hook
skeleton.rs
SkelConfig는 맵과 prog를 캡슐화합니다.
libbpf_sys::bpf_object_skeleton의 래퍼
수명 주기 obj 및 메모리 보유를 위한 _data/_string_pool
메모리 파괴 드롭을 위한 프로그램/맵의 레이아웃
ObjectSkeletonConfigBuilder.build()->ObjectSkeletonConfig()
libbpf_sys::bpf_object_skeleton()
.build_maps(s, string_pool)->maps_layout
.build_progs(s, string_pool)->progs_layout
// libbpf_sys::bpf_object_skeleton wrap
/// * ensure lifetimes are valid for dependencies (pointers, data buffer)
/// * free any allocated memory on drop
pub struct ObjectSkeletonConfig<'a> {
inner: bpf_object_skeleton,
obj: Box<*mut bpf_object>,
maps: Vec<MapSkelConfig>,
progs: Vec<ProgSkelConfig>,
/// Layout necessary to `dealloc` memory
maps_layout: Option<Layout>,
/// Same as above
progs_layout: Option<Layout>,
/// Hold this reference so that compiler guarantees buffer lives as long as us
_data: &'a [u8],
/// Hold strings alive so pointers to them stay valid
_string_pool: Vec<CString>,
}
query.rs
for prog in ProgInfoIter::default() {
println!("{}", prog.name);
[Program/Map/Btf/Link] Info
program.rs
OpenProgram.[set_[map_ifindex/fd/...]]
Program.[name/sectyion/fd/key_size/value_size/lookup/delte/update/pin/unpin]]
maps.rs
OpenMap.[set_[prog_type/attach_type/ifindex/flags]]
Map.[name/sectyion/fd/pin/unpin/attach[cgroup/perf/uprobe/trace/xdp]]
link.rs bpf 및 후크의 추상화를 관리할 수 있습니다.
Link.[open/update_prog/dsconnet/pin/fd/detach]
bpf_link_type.[xdp/perf_event/cgroup/raw/trace]
object.rs
OpenObject: open but not load [bpf_object/maps/progs/name/map/prog/load] Populate obj.maps/obj.progs
Object: open and loaded object
ObjectBuilder.[name/debug/opts/open_file/open_mem]->OpenObject::new()
ringbuf.rs
RingBuffer.[ring_buffer/poll/consume]
RingBufferBuilder.[RingBufferCallback/add/build]->RingBuffer()
util.rs
str_to_cstring/path_to_cstring/c_ptr_to_string
roundup/num_possible_cpus
parse_ret/parse_ret_i32/parse_ret_usize
libbpf-화물 함수
main.rs
clap Command:[Build/Gen/Make]는 다음 3개 파일에 해당하며, main은 api입니다.
lib.rs
사용자 프로젝트 build.rs에 대한 자동 빌드 및 생성 제공
SkeletonBuilder::new().source(SRC).build_and_generate(&skel)
build()->build::build_single()
generate()->gen::gen_single()
make.rs
- 배치 빌드 및 생성
- 빌드::빌드()
- 세대::세대()
- 마지막으로 Command::new("cargo").arg("build")
build.rs
사용자 프로젝트에 대한 build_single()->compile_one()->Command
build() 用于cargo ->compile()->compile_one()
extract_libbpf_headers_to_disk()
check_progs/check_clang()/
gen.rs
gen->gen_single->gen_skel(debug, name, obj_file, output, rustfmt_path)->
gen_skel_contents()
open_bpf_object()
gen_skel_c_skel_constructor()->libbpf_rs::skeleton::**ObjectSkeletonConfigBuilder**::new(DATA); # skeleton.rs
map/prog/datasec
gen_skel_xxx_defs()?; gen_skel_xxx_getter()?; gen_skel_link_getter()
gen_skel_attach()->libbpf_sys::bpf_object__attach_skeleton(
metadata.rs
cargo时的 to_compile
get()->target_dir, metadata.target_directory.into_std_path_buf()
轮询所有package后,if id == &package.id
get_package()
1