├── LICENSE ├── README.md ├── asm.go ├── asm_test.go ├── ebpf.go ├── fd.go ├── go.mod ├── go.sum ├── integration_test.go ├── map.go ├── map_test.go ├── prog.go ├── prog_test.go ├── u64ptr_32.go └── u64ptr_64.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Andrei Tudor Călin 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 12 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ebpf 2 | ================ 3 | 4 | `import "acln.ro/ebpf"` 5 | 6 | eBPF package for Go. Currently under active development. 7 | 8 | See documentation at https://godoc.org/acln.ro/ebpf. 9 | 10 | ### license 11 | 12 | Package ebpf is distributed under the ISC license. A copy of the license 13 | can be found in the LICENSE file. 14 | 15 | TODO(acln): leave a note about the samples, figure out what the right license 16 | is there, since they're based on GPL samples from the Linux source tree. 17 | -------------------------------------------------------------------------------- /asm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | "io" 21 | "strconv" 22 | "unsafe" 23 | ) 24 | 25 | var hostByteOrder binary.ByteOrder 26 | 27 | func init() { 28 | var buf [2]byte 29 | u16ptr := (*uint16)(unsafe.Pointer(&buf[0])) 30 | *u16ptr = 0x1234 31 | switch buf[0] { 32 | case 0x12: 33 | hostByteOrder = binary.BigEndian 34 | case 0x34: 35 | hostByteOrder = binary.LittleEndian 36 | } 37 | } 38 | 39 | // Opcode is an eBPF opcode. 40 | type Opcode uint8 41 | 42 | // Class returns the instruction class of o. 43 | func (o Opcode) Class() Class { 44 | return Class(o & classMask) 45 | } 46 | 47 | // Size returns the size of the instruction, applicable if 48 | // o represents a load or a store. 49 | func (o Opcode) Size() Size { 50 | return Size(o & sizeMask) 51 | } 52 | 53 | // Mode returns the address mode of the instruction, applicable 54 | // if o represents a load or a store. 55 | func (o Opcode) Mode() Mode { 56 | return Mode(o & modeMask) 57 | } 58 | 59 | // ALUOp returns the ALU operation associated with o, applicable 60 | // if o.Class() is ALU or ALU64. 61 | func (o Opcode) ALUOp() ALUOp { 62 | return ALUOp(o & aluOpMask) 63 | } 64 | 65 | // JumpCond returns the jump condition associated with o, applicable 66 | // if o.Class() is JMP. 67 | func (o Opcode) JumpCond() JumpCond { 68 | return JumpCond(o & jumpCondMask) 69 | } 70 | 71 | // SourceOperand returns the source operand associated with o, applicable 72 | // if o.Class() is ALU or ALU64. 73 | func (o Opcode) SourceOperand() SourceOperand { 74 | return SourceOperand(o & sourceOperandMask) 75 | } 76 | 77 | func (o Opcode) String() string { 78 | // TODO(acln): how does this relate to Instruction.String()? 79 | return errNotImplemented.Error() 80 | } 81 | 82 | // Class is an eBPF instruction class. 83 | type Class uint8 84 | 85 | // Instruction classes. 86 | const ( 87 | LD Class = 0x00 88 | LDX Class = 0x01 89 | ST Class = 0x02 90 | STX Class = 0x03 91 | ALU Class = 0x04 92 | JMP Class = 0x05 93 | ALU64 Class = 0x07 94 | ) 95 | 96 | var classStrings = map[Class]string{ 97 | LD: "ld", 98 | LDX: "ldx", 99 | ST: "st", 100 | STX: "stx", 101 | ALU: "alu", 102 | JMP: "jmp", 103 | ALU64: "alu64", 104 | } 105 | 106 | func (c Class) String() string { 107 | s, ok := classStrings[c] 108 | if !ok { 109 | return "unknown class" 110 | } 111 | return s 112 | } 113 | 114 | const classMask = 0x07 115 | 116 | // Size is the size of a load or store instruction. 117 | type Size uint8 118 | 119 | // Instruction widths. 120 | const ( 121 | W Size = 0x00 // 32 bit 122 | H Size = 0x08 // 16 bit 123 | B Size = 0x10 // 8 bit 124 | DW Size = 0x18 // 64 bit 125 | ) 126 | 127 | var sizeStrings = map[Size]string{ 128 | W: "w", 129 | H: "h", 130 | B: "b", 131 | DW: "dw", 132 | } 133 | 134 | func (sz Size) String() string { 135 | s, ok := sizeStrings[sz] 136 | if !ok { 137 | return "unknown size" 138 | } 139 | return s 140 | } 141 | 142 | const sizeMask = 0x18 143 | 144 | // Mode is the addres mode of a load or store instruction. 145 | type Mode uint8 146 | 147 | // Valid address modes. 148 | const ( 149 | IMM Mode = 0x00 150 | ABS Mode = 0x20 151 | IND Mode = 0x40 152 | MEM Mode = 0x60 153 | LEN Mode = 0x80 154 | MSH Mode = 0xa0 155 | XADD Mode = 0xc0 156 | ) 157 | 158 | var modeStrings = map[Mode]string{ 159 | IMM: "imm", 160 | ABS: "abs", 161 | IND: "ind", 162 | MEM: "mem", 163 | LEN: "len", 164 | MSH: "msh", 165 | XADD: "xadd", 166 | } 167 | 168 | func (m Mode) String() string { 169 | s, ok := modeStrings[m] 170 | if !ok { 171 | return "unknown mode" 172 | } 173 | return s 174 | } 175 | 176 | const modeMask = 0xe0 177 | 178 | // ALUOp specifies an ALU operation. 179 | type ALUOp uint8 180 | 181 | // Valid ALU operations. 182 | const ( 183 | ADD ALUOp = 0x00 184 | SUB ALUOp = 0x10 185 | MUL ALUOp = 0x20 186 | DIV ALUOp = 0x30 187 | OR ALUOp = 0x40 188 | AND ALUOp = 0x50 189 | LSH ALUOp = 0x60 190 | RSH ALUOp = 0x70 191 | NEG ALUOp = 0x80 192 | MOD ALUOp = 0x90 193 | XOR ALUOp = 0xa0 194 | MOV ALUOp = 0xb0 195 | ARSH ALUOp = 0xc0 196 | END ALUOp = 0xd0 197 | ) 198 | 199 | var aluOpStrings = map[ALUOp]string{ 200 | ADD: "add", 201 | SUB: "sub", 202 | MUL: "mul", 203 | DIV: "div", 204 | OR: "or", 205 | AND: "and", 206 | LSH: "lsh", 207 | RSH: "rsh", 208 | NEG: "neg", 209 | MOD: "mod", 210 | XOR: "xor", 211 | MOV: "mov", 212 | ARSH: "arsh", 213 | END: "end", 214 | } 215 | 216 | func (op ALUOp) String() string { 217 | s, ok := aluOpStrings[op] 218 | if !ok { 219 | return "unknown ALU op" 220 | } 221 | return s 222 | } 223 | 224 | const aluOpMask = 0xf0 225 | 226 | // JumpCond specifies a jump condition. 227 | type JumpCond uint8 228 | 229 | // Valid jump conditions. 230 | const ( 231 | JA JumpCond = 0x00 232 | JEQ JumpCond = 0x10 233 | JGT JumpCond = 0x20 234 | JGE JumpCond = 0x30 235 | JSET JumpCond = 0x40 236 | JNE JumpCond = 0x50 237 | JSGT JumpCond = 0x60 238 | JSGE JumpCond = 0x70 239 | CALL JumpCond = 0x80 240 | EXIT JumpCond = 0x90 241 | JLT JumpCond = 0xa0 242 | JLE JumpCond = 0xb0 243 | JSLT JumpCond = 0xc0 244 | JSLE JumpCond = 0xd0 245 | ) 246 | 247 | var jumpCondStrings = map[JumpCond]string{ 248 | JA: "ja", 249 | JEQ: "jeq", 250 | JGT: "jgt", 251 | JGE: "jge", 252 | JSET: "jset", 253 | JNE: "jne", 254 | JSGT: "jsgt", 255 | JSGE: "jsge", 256 | CALL: "call", 257 | EXIT: "exit", 258 | JLT: "jlt", 259 | JLE: "jle", 260 | JSLT: "jslt", 261 | JSLE: "jsle", 262 | } 263 | 264 | func (jc JumpCond) String() string { 265 | s, ok := jumpCondStrings[jc] 266 | if !ok { 267 | return "unknown jump condition" 268 | } 269 | return s 270 | } 271 | 272 | const jumpCondMask = 0xf0 273 | 274 | // SourceOperand specifies the souce operand for an instruction. 275 | type SourceOperand uint8 276 | 277 | // Source operands. 278 | const ( 279 | // K specifies the 32 bit immediate as the source operand. 280 | K SourceOperand = 0x00 281 | 282 | // X specifies the source register as the source operand. 283 | X SourceOperand = 0x08 284 | ) 285 | 286 | var sourceOperandStrings = map[SourceOperand]string{ 287 | K: "k", 288 | X: "x", 289 | } 290 | 291 | func (op SourceOperand) String() string { 292 | s, ok := sourceOperandStrings[op] 293 | if !ok { 294 | return "unknown source operand" 295 | } 296 | return s 297 | } 298 | 299 | const sourceOperandMask = 0x08 300 | 301 | // Register is an eBPF register. 302 | type Register uint8 303 | 304 | // Valid eBPF registers. 305 | // 306 | // When calling kernel functions, R0 holds the return value, R1 - 307 | // R5 are destroyed and set to unreadable, and R6 - R9 are preserved 308 | // (callee-saved). R10 or FP is the read-only frame pointer. 309 | const ( 310 | R0 Register = iota 311 | R1 312 | R2 313 | R3 314 | R4 315 | R5 316 | R6 317 | R7 318 | R8 319 | R9 320 | R10 321 | FP = R10 322 | 323 | // PseudoMapFD is used to specify a map file descriptor 324 | // for loading, in a 64 bit immediate load instruction. 325 | PseudoMapFD Register = 1 326 | 327 | // PseudoCall is used to specify a kernel function to call, 328 | // in a call instruction. 329 | PseudoCall Register = 1 330 | ) 331 | 332 | var registerStrings = map[Register]string{ 333 | R0: "r0", 334 | R1: "r1", 335 | R2: "r2", 336 | R3: "r3", 337 | R4: "r4", 338 | R5: "r5", 339 | R6: "r6", 340 | R7: "r7", 341 | R8: "r8", 342 | R9: "r9", 343 | FP: "fp", 344 | } 345 | 346 | func (r Register) String() string { 347 | // We don't care about PseudoMapFD and PseudoCall here. 348 | // Code that cares deals with them in the larger context 349 | // of one specific instruction. 350 | s, ok := registerStrings[r] 351 | if !ok { 352 | return "unknown register" 353 | } 354 | return s 355 | } 356 | 357 | // KernelFunc is a function callable by eBPF programs from inside the kernel. 358 | type KernelFunc int32 359 | 360 | // Kernel functions. 361 | const ( 362 | KernelFunctionUnspec KernelFunc = iota // bpf_unspec 363 | 364 | MapLookupElem // bpf_map_lookup_elem 365 | MapUpdateElem // bpf_map_update_elem 366 | MapDeleteElem // bpf_map_delete_elem 367 | ProbeRead // bpf_probe_read 368 | KTimeGetNS // bpf_ktime_get_ns 369 | TracePrintk // bpf_trace_printk 370 | GetPrandomU32 // bpf_get_prandom_u32 371 | GetSMPProcessorID // bpf_get_smp_processor_id 372 | SKBStoreBytes // bpf_skb_store_bytes 373 | L3CSumReplace // bpf_l3_csum_replace 374 | L4CSumReplace // bpf_l4_csum_replace 375 | TailCall // bpf_tail_call 376 | CloneRedirect // bpf_clone_redirect 377 | GetCurrentPIDTGID // bpf_get_current_pid_tgid 378 | GetCurrentUIDGID // bpf_get_current_uid_gid 379 | GetCurrentComm // bpf_get_current_comm 380 | GetCGroupClassID // bpf_get_cgroup_classid 381 | SKBVLanPush // bpf_skb_vlan_push 382 | SKBVLanPop // bpf_skb_vlan_pop 383 | SKBGetTunnelKey // bpf_skb_get_tunnel_key 384 | SKBSetTunnelKey // bpf_skb_set_tunnel_key 385 | PerfEventRead // bpf_perf_event_read 386 | Redirect // bpf_redirect 387 | GetRouteRealm // bpf_get_route_realm 388 | PerfEventOutput // bpf_perf_event_output 389 | SKBLoadBytes // bpf_skb_load_bytes 390 | GetStackID // bpf_get_stackid 391 | CSumDiff // bpf_csum_diff 392 | SKBGetTunnelOpt // bpf_skb_get_tunnel_opt 393 | SKBSetTunnelOpt // bpf_skb_set_tunnel_opt 394 | SKBChangeProto // bpf_skb_change_proto 395 | SKBChangeType // bpf_skb_change_type 396 | SKBUnderCGroup // bpf_skb_under_cgroup 397 | GetHashRecalc // bpf_get_hash_recalc 398 | GetCurrentTask // bpf_get_current_task 399 | ProbeWriteUser // bpf_probe_write_user 400 | CurrentTaskUnderCGroup // bpf_current_task_under_cgroup 401 | SKBChangeTail // bpf_skb_change_tail 402 | SKBPullData // bpf_skb_pull_data 403 | CSumUpdate // bpf_csum_update 404 | SetHashInvalid // bpf_set_hash_invalid 405 | GetNUMANodeID // bpf_get_numa_node_id 406 | SKBChangeHEad // bpf_skb_change_head 407 | XDPAdjustHead // bpf_xdp_adjust_head 408 | ProbeReadStr // bpf_probe_read_str 409 | GetSocketCookie // bpf_get_socket_cookie 410 | GetSocketUID // bpf_get_socket_uid 411 | SetHash // bpf_set_hash 412 | SetSockopt // bpf_setsockopt 413 | SKBAdjustRoom // bpf_skb_adjust_room 414 | RedirectMap // bpf_redirect_map 415 | SKRedirectMap // bpf_sk_redirect_map 416 | SockMapUpdate // bpf_sock_map_update 417 | XDPAdjustMeta // bpf_xdp_adjust_meta 418 | PerfEventReadValue // bpf_perf_event_read_value 419 | PerfProgReadValue // bpf_perf_prog_read_value 420 | GetSockopt // bpf_getsockopt 421 | OverrideReturn // bpf_override_return 422 | SockOpsCBFlagsSet // bpf_sock_ops_cb_flags_set 423 | MsgRedirectMap // bpf_msg_redirect_map 424 | MsgApplyBytes // bpf_msg_apply_bytes 425 | MsgCorkBytes // bpf_msg_cork_bytes 426 | MsgPullData // bpf_msg_pull_data 427 | Bind // bpf_bind 428 | XDPAdjustTail // bpf_xdp_adjust_tail 429 | SKBGetXFRMState // bpf_skb_get_xfrm_state 430 | GetStack // bpf_get_stack 431 | SKBLoadBytesRelative // bpf_skb_load_bytes_relative 432 | FibLookup // bpf_fib_lookup 433 | SockHashUpdate // bpf_sock_hash_update 434 | MsgRedirectHash // bpf_msg_redirect_hash 435 | SKRedirectHash // bpf_sk_redirect_hash 436 | LWTPushEncap // bpf_lwt_push_encap 437 | LWTSeg6StoreBytes // bpf_lwt_seg6_store_bytes 438 | LWTSeg6AdjustSRH // bpf_lwt_seg6_adjust_srh 439 | LWTSeg6Action // bpf_lwt_seg6_action 440 | RCRepeat // bpf_rc_repeat 441 | RCKeydown // bpf_rc_keydown 442 | SKBCGroupID // bpf_skb_cgroup_id 443 | GetCurrentCGroupID // bpf_get_current_cgroup_id 444 | GetLocalStorage // bpf_get_local_storage 445 | SKSelectReuseport // bpf_sk_select_reuseport 446 | SKBAncestorCGroupID // bpf_skb_ancestor_cgroup_id 447 | SKLookupTCP // bpf_sk_lookup_tcp 448 | SKLookupUDP // bpf_sk_lookup_udp 449 | SKRelease // bpf_sk_release 450 | MapPushElem // bpf_map_push_elem 451 | MapPopElem // bpf_map_pop_elem 452 | MapPeekElem // bpf_map_peek_elem 453 | MsgPushData // bpf_msg_push_data 454 | ) 455 | 456 | // String returns the name of the kernel function represented by fn. 457 | func (fn KernelFunc) String() string { 458 | s, ok := kernelFuncStrings[fn] 459 | if !ok { 460 | return "unknown kernel function" 461 | } 462 | return s 463 | } 464 | 465 | var kernelFuncStrings = map[KernelFunc]string{ 466 | MapLookupElem: "bpf_map_lookup_elem", 467 | MapUpdateElem: "bpf_map_update_elem", 468 | MapDeleteElem: "bpf_map_delete_elem", 469 | ProbeRead: "bpf_probe_read", 470 | KTimeGetNS: "bpf_ktime_get_ns", 471 | TracePrintk: "bpf_trace_printk", 472 | GetPrandomU32: "bpf_get_prandom_u32", 473 | GetSMPProcessorID: "bpf_get_smp_processor_id", 474 | SKBStoreBytes: "bpf_skb_store_bytes", 475 | L3CSumReplace: "bpf_l3_csum_replace", 476 | L4CSumReplace: "bpf_l4_csum_replace", 477 | TailCall: "bpf_tail_call", 478 | CloneRedirect: "bpf_clone_redirect", 479 | GetCurrentPIDTGID: "bpf_get_current_pid_tgid", 480 | GetCurrentUIDGID: "bpf_get_current_uid_gid", 481 | GetCurrentComm: "bpf_get_current_comm", 482 | GetCGroupClassID: "bpf_get_cgroup_classid", 483 | SKBVLanPush: "bpf_skb_vlan_push", 484 | SKBVLanPop: "bpf_skb_vlan_pop", 485 | SKBGetTunnelKey: "bpf_skb_get_tunnel_key", 486 | SKBSetTunnelKey: "bpf_skb_set_tunnel_key", 487 | PerfEventRead: "bpf_perf_event_read", 488 | Redirect: "bpf_redirect", 489 | GetRouteRealm: "bpf_get_route_realm", 490 | PerfEventOutput: "bpf_perf_event_output", 491 | SKBLoadBytes: "bpf_skb_load_bytes", 492 | GetStackID: "bpf_get_stackid", 493 | CSumDiff: "bpf_csum_diff", 494 | SKBGetTunnelOpt: "bpf_skb_get_tunnel_opt", 495 | SKBSetTunnelOpt: "bpf_skb_set_tunnel_opt", 496 | SKBChangeProto: "bpf_skb_change_proto", 497 | SKBChangeType: "bpf_skb_change_type", 498 | SKBUnderCGroup: "bpf_skb_under_cgroup", 499 | GetHashRecalc: "bpf_get_hash_recalc", 500 | GetCurrentTask: "bpf_get_current_task", 501 | ProbeWriteUser: "bpf_probe_write_user", 502 | CurrentTaskUnderCGroup: "bpf_current_task_under_cgroup", 503 | SKBChangeTail: "bpf_skb_change_tail", 504 | SKBPullData: "bpf_skb_pull_data", 505 | CSumUpdate: "bpf_csum_update", 506 | SetHashInvalid: "bpf_set_hash_invalid", 507 | GetNUMANodeID: "bpf_get_numa_node_id", 508 | SKBChangeHEad: "bpf_skb_change_head", 509 | XDPAdjustHead: "bpf_xdp_adjust_head", 510 | ProbeReadStr: "bpf_probe_read_str", 511 | GetSocketCookie: "bpf_get_socket_cookie", 512 | GetSocketUID: "bpf_get_socket_uid", 513 | SetHash: "bpf_set_hash", 514 | SetSockopt: "bpf_setsockopt", 515 | SKBAdjustRoom: "bpf_skb_adjust_room", 516 | RedirectMap: "bpf_redirect_map", 517 | SKRedirectMap: "bpf_sk_redirect_map", 518 | SockMapUpdate: "bpf_sock_map_update", 519 | XDPAdjustMeta: "bpf_xdp_adjust_meta", 520 | PerfEventReadValue: "bpf_perf_event_read_value", 521 | PerfProgReadValue: "bpf_perf_prog_read_value", 522 | GetSockopt: "bpf_getsockopt", 523 | OverrideReturn: "bpf_override_return", 524 | SockOpsCBFlagsSet: "bpf_sock_ops_cb_flags_set", 525 | MsgRedirectMap: "bpf_msg_redirect_map", 526 | MsgApplyBytes: "bpf_msg_apply_bytes", 527 | MsgCorkBytes: "bpf_msg_cork_bytes", 528 | MsgPullData: "bpf_msg_pull_data", 529 | Bind: "bpf_bind", 530 | XDPAdjustTail: "bpf_xdp_adjust_tail", 531 | SKBGetXFRMState: "bpf_skb_get_xfrm_state", 532 | GetStack: "bpf_get_stack", 533 | SKBLoadBytesRelative: "bpf_skb_load_bytes_relative", 534 | FibLookup: "bpf_fib_lookup", 535 | SockHashUpdate: "bpf_sock_hash_update", 536 | MsgRedirectHash: "bpf_msg_redirect_hash", 537 | SKRedirectHash: "bpf_sk_redirect_hash", 538 | LWTPushEncap: "bpf_lwt_push_encap", 539 | LWTSeg6StoreBytes: "bpf_lwt_seg6_store_bytes", 540 | LWTSeg6AdjustSRH: "bpf_lwt_seg6_adjust_srh", 541 | LWTSeg6Action: "bpf_lwt_seg6_action", 542 | RCRepeat: "bpf_rc_repeat", 543 | RCKeydown: "bpf_rc_keydown", 544 | SKBCGroupID: "bpf_skb_cgroup_id", 545 | GetCurrentCGroupID: "bpf_get_current_cgroup_id", 546 | GetLocalStorage: "bpf_get_local_storage", 547 | SKSelectReuseport: "bpf_sk_select_reuseport", 548 | SKBAncestorCGroupID: "bpf_skb_ancestor_cgroup_id", 549 | SKLookupTCP: "bpf_sk_lookup_tcp", 550 | SKLookupUDP: "bpf_sk_lookup_udp", 551 | SKRelease: "bpf_sk_release", 552 | MapPushElem: "bpf_map_push_elem", 553 | MapPopElem: "bpf_map_pop_elem", 554 | MapPeekElem: "bpf_map_peek_elem", 555 | MsgPushData: "bpf_msg_push_data", 556 | } 557 | 558 | // MaxInstructions is the maximum number of instructions in a BPF or eBPF program. 559 | const MaxInstructions = 4096 560 | 561 | // Instruction is an eBPF instruction. 562 | // 563 | // Note that Instruction does not pack the destination and source registers 564 | // into a single 8 bit field, as the kernel ABI demands. This means that 565 | // Instruction values are not suitable for loading into the kernel. 566 | type Instruction struct { 567 | Opcode Opcode 568 | Dst Register 569 | Src Register 570 | Off int16 571 | Imm int32 572 | } 573 | 574 | func (ins Instruction) pack(bo binary.ByteOrder) instruction { 575 | i := instruction{ 576 | Opcode: uint8(ins.Opcode), 577 | Off: ins.Off, 578 | Imm: ins.Imm, 579 | } 580 | switch bo { 581 | case binary.LittleEndian: 582 | i.Registers = uint8(ins.Src<<4) | uint8(ins.Dst) 583 | case binary.BigEndian: 584 | i.Registers = uint8(ins.Dst<<4) | uint8(ins.Src) 585 | default: 586 | panic("ebpf: bad byte order: want binary.LittleEndian or binary.BigEndian") 587 | } 588 | return i 589 | } 590 | 591 | // instruction is an assembled eBPF instruction, suitable for passing 592 | // into the Linux kernel. 593 | type instruction struct { 594 | Opcode uint8 595 | Registers uint8 596 | Off int16 597 | Imm int32 598 | } 599 | 600 | func (i instruction) unpack(bo binary.ByteOrder) Instruction { 601 | ins := Instruction{ 602 | Opcode: Opcode(i.Opcode), 603 | Off: i.Off, 604 | Imm: i.Imm, 605 | } 606 | switch bo { 607 | case binary.LittleEndian: 608 | ins.Dst = Register(i.Registers & 0x0f) 609 | ins.Src = Register(i.Registers >> 4) 610 | case binary.BigEndian: 611 | ins.Dst = Register(i.Registers >> 4) 612 | ins.Src = Register(i.Registers & 0x0f) 613 | default: 614 | panic("ebpf: bad byte order: want binary.LittleEndian or binary.BigEndian") 615 | } 616 | return ins 617 | } 618 | 619 | // InstructionStream is a stream of eBPF instructions. The zero value is an 620 | // empty InstructionStream which assembles instructions in host byte order. 621 | // 622 | // After instructions on 32 bit subregisters, the destination register is 623 | // zero extended into 64 bits. 624 | // 625 | // Comments on InstructionStream methods which emit instructions may contain 626 | // pseudocode that loosely describes the semantics of the instruction. The 627 | // conventions are the following: 628 | // 629 | // "$sym" means the value represented by the symbol, after it is resolved. 630 | // 631 | // "uintsz" represents an unsigned integer, with size given by the sz 632 | // parameter. 633 | type InstructionStream struct { 634 | // ByteOrder is the byte order to produce instructions for. 635 | // 636 | // If it is nil, it defaults to the host byte order. 637 | // 638 | // If set, it must be one of binary.LittleEndian or binary.BigEndian, 639 | // otherwise method calls on the InstructionStream will panic. 640 | ByteOrder binary.ByteOrder 641 | 642 | insns []instruction 643 | mapSyms map[string][]int 644 | imm64Syms map[string][]int 645 | imm32Syms map[string][]int 646 | usesSymbols bool 647 | resolved bool 648 | } 649 | 650 | func (s *InstructionStream) empty() bool { 651 | return len(s.insns) == 0 652 | } 653 | 654 | func (s *InstructionStream) hasUnresolvedSymbols() bool { 655 | if s.usesSymbols { 656 | return !s.resolved 657 | } 658 | return false 659 | } 660 | 661 | func (s *InstructionStream) instructions() []instruction { 662 | // Make a copy, to avoid aliasing surprises. 663 | insns := make([]instruction, len(s.insns)) 664 | copy(insns, s.insns) 665 | return insns 666 | } 667 | 668 | func (s *InstructionStream) byteOrder() binary.ByteOrder { 669 | if s.ByteOrder != nil { 670 | return s.ByteOrder 671 | } 672 | return hostByteOrder 673 | } 674 | 675 | // PrintTo prints the instruction stream in textual form to w. 676 | func (s *InstructionStream) PrintTo(w io.Writer) error { 677 | sew := &stickyErrorWriter{w: w} 678 | for index, ins := range s.insns { 679 | s.printInstruction(sew, index, ins) 680 | if index < len(s.insns)-1 { 681 | io.WriteString(sew, "\n") 682 | } 683 | } 684 | return sew.err 685 | } 686 | 687 | func (s *InstructionStream) printInstruction(w io.Writer, index int, rawins instruction) { 688 | var bo binary.ByteOrder 689 | if s.ByteOrder == nil { 690 | bo = hostByteOrder 691 | } 692 | 693 | ins := rawins.unpack(bo) 694 | 695 | switch class := ins.Opcode.Class(); class { 696 | case ALU, ALU64: 697 | opstr := ins.Opcode.ALUOp().String() // "add" 698 | 699 | if class == ALU64 { 700 | opstr += "64" // "add64" 701 | } else { 702 | opstr += "32" // "add32" 703 | } 704 | 705 | opstr += "\t" + ins.Dst.String() + ", " // "add64 r1, " 706 | 707 | if ins.Opcode.SourceOperand() == X { 708 | opstr += ins.Src.String() // "add64 r1, r3" 709 | } else { 710 | // Symbolic or direct immediate. Find out which. 711 | if sym := s.symbol(index); sym == "" { 712 | opstr += "#" + strconv.Itoa(int(ins.Imm)) // "add64 r1, #128" 713 | } else { 714 | opstr += "$" + sym // "add64 r1, $offset" 715 | } 716 | } 717 | 718 | io.WriteString(w, opstr) 719 | 720 | case JMP: 721 | cond := ins.Opcode.JumpCond() 722 | 723 | switch cond { 724 | case CALL: 725 | // "call bpf_probe_read" 726 | fmt.Fprintf(w, "call\t%s", KernelFunc(ins.Imm).String()) 727 | return 728 | case EXIT: 729 | // "exit" 730 | io.WriteString(w, cond.String()) 731 | return 732 | } 733 | 734 | opstr := cond.String() + "\t" // "jeq " 735 | opstr += ins.Dst.String() + ", " // "jeq r1, " 736 | 737 | if ins.Opcode.SourceOperand() == X { 738 | opstr += ins.Src.String() + ", " // "jeq r1, r2, " 739 | } else { 740 | if sym := s.symbol(index); sym == "" { 741 | opstr += "#" + strconv.Itoa(int(ins.Imm)) // jeq r1, #128" 742 | } else { 743 | opstr += "$" + sym // "jeq r1, $proto" 744 | } 745 | opstr += ", " // "jeq r1, $proto, " 746 | } 747 | 748 | if ins.Off >= 0 { 749 | opstr += "+" // "jeq r1, #128, +" 750 | } 751 | 752 | opstr += strconv.Itoa(int(ins.Off)) // "jeq r1, #128, +2" 753 | 754 | io.WriteString(w, opstr) 755 | 756 | case ST, STX: 757 | opstr := class.String() + ins.Opcode.Size().String() + "\t[" // "sth [" 758 | opstr += ins.Dst.String() // "sth [r1" 759 | if ins.Off >= 0 { 760 | opstr += "+" // "sth [r1+" 761 | } 762 | opstr += strconv.Itoa(int(ins.Off)) + "], " // "sth [r1+24], " 763 | 764 | if class == ST { 765 | if sym := s.symbol(index); sym == "" { 766 | opstr += "#" + strconv.Itoa(int(ins.Imm)) // "sth [r1+24], #42" 767 | } else { 768 | opstr += "$" + sym // "sth [r1+24], $something" 769 | } 770 | } else { 771 | opstr += ins.Src.String() // "stxw [r1+24], r3" 772 | } 773 | 774 | io.WriteString(w, opstr) 775 | } 776 | } 777 | 778 | func (s *InstructionStream) symbol(index int) string { 779 | symbolMaps := []map[string][]int{ 780 | s.mapSyms, 781 | s.imm64Syms, 782 | s.imm32Syms, 783 | } 784 | for _, symbolMap := range symbolMaps { 785 | for sym, indexes := range symbolMap { 786 | for _, symindex := range indexes { 787 | if index == symindex { 788 | return sym 789 | } 790 | } 791 | } 792 | } 793 | return "" 794 | } 795 | 796 | type stickyErrorWriter struct { 797 | w io.Writer 798 | err error 799 | } 800 | 801 | func (sew *stickyErrorWriter) Write(p []byte) (int, error) { 802 | if sew.err != nil { 803 | return 0, sew.err 804 | } 805 | n, err := sew.w.Write(p) 806 | sew.err = err 807 | return n, err 808 | } 809 | 810 | // Raw emits a raw instruction. 811 | func (s *InstructionStream) Raw(ins Instruction) { 812 | s.insns = append(s.insns, ins.pack(s.byteOrder())) 813 | } 814 | 815 | // RawSym emits a raw instruction with a symbolic 32 bit immediate. ins.Imm is ignored. 816 | func (s *InstructionStream) RawSym(ins Instruction, sym string) { 817 | // We ignore ins.Imm, but there is no need to overwrite it here, 818 | // or do anything else to it. If sym does not resolve, we can't load 819 | // the program anyway. 820 | s.Raw(ins) 821 | s.addImm32Sym(sym, len(s.insns)-1) 822 | } 823 | 824 | // 64 bit MOVs and ALU operations. 825 | 826 | func alu64Opcode(op ALUOp, operand SourceOperand) Opcode { 827 | return Opcode(ALU64) | Opcode(op) | Opcode(operand) 828 | } 829 | 830 | // Mov64Reg emits a move on 64 bit registers. 831 | // 832 | // dst = src 833 | func (s *InstructionStream) Mov64Reg(dst, src Register) { 834 | s.Raw(Instruction{ 835 | Opcode: alu64Opcode(MOV, X), 836 | Dst: dst, 837 | Src: src, 838 | }) 839 | } 840 | 841 | // Mov64Imm emits a 64 bit move of a 32 bit immediate into a register. 842 | // 843 | // dst = imm 844 | func (s *InstructionStream) Mov64Imm(dst Register, imm int32) { 845 | s.Raw(Instruction{ 846 | Opcode: alu64Opcode(MOV, K), 847 | Dst: dst, 848 | Imm: imm, 849 | }) 850 | } 851 | 852 | // Mov64Sym emits a 64 bit move of a 32 bit symbolic immediate into 853 | // a register. 854 | // 855 | // dst = $sym 856 | func (s *InstructionStream) Mov64Sym(dst Register, sym string) { 857 | s.RawSym(Instruction{ 858 | Opcode: alu64Opcode(MOV, K), 859 | Dst: dst, 860 | }, sym) 861 | } 862 | 863 | // ALU64Reg emits a 64 bit ALU operation on registers. 864 | // 865 | // dst = dst src 866 | func (s *InstructionStream) ALU64Reg(op ALUOp, dst, src Register) { 867 | s.Raw(Instruction{ 868 | Opcode: alu64Opcode(op, X), 869 | Dst: dst, 870 | Src: src, 871 | }) 872 | } 873 | 874 | // ALU64Imm emits a 64 bit ALU instruction on a register and a 32 bit 875 | // immediate. 876 | // 877 | // dst = dst imm 878 | func (s *InstructionStream) ALU64Imm(op ALUOp, dst Register, imm int32) { 879 | s.Raw(Instruction{ 880 | Opcode: alu64Opcode(op, K), 881 | Dst: dst, 882 | Imm: imm, 883 | }) 884 | } 885 | 886 | // ALU64Sym emits a 64 bit ALU instruction on a register and a 32 bit 887 | // symbolic immediate. 888 | // 889 | // dst = dst $sym 890 | func (s *InstructionStream) ALU64Sym(op ALUOp, dst Register, sym string) { 891 | s.RawSym(Instruction{ 892 | Opcode: alu64Opcode(op, K), 893 | Dst: dst, 894 | }, sym) 895 | } 896 | 897 | // 32 bit MOVs and ALU operations 898 | 899 | func alu32Opcode(op ALUOp, operand SourceOperand) Opcode { 900 | return Opcode(ALU) | Opcode(op) | Opcode(operand) 901 | } 902 | 903 | // Mov32Reg emits a move on 32 bit subregisters. 904 | // 905 | // dst = int32(src) 906 | func (s *InstructionStream) Mov32Reg(dst, src Register) { 907 | s.Raw(Instruction{ 908 | Opcode: alu32Opcode(MOV, X), 909 | Dst: dst, 910 | Src: src, 911 | }) 912 | } 913 | 914 | // Mov32Imm emits a move of a 32 bit immediate into a register. 915 | // 916 | // dst = imm 917 | func (s *InstructionStream) Mov32Imm(dst Register, imm int32) { 918 | s.Raw(Instruction{ 919 | Opcode: alu32Opcode(MOV, K), 920 | Dst: dst, 921 | Imm: imm, 922 | }) 923 | } 924 | 925 | // Mov32Sym emits a move of a symbolic 32 bit immediate into a 926 | // register. 927 | // 928 | // dst = $sym 929 | func (s *InstructionStream) Mov32Sym(dst Register, sym string) { 930 | s.RawSym(Instruction{ 931 | Opcode: alu32Opcode(MOV, K), 932 | Dst: dst, 933 | }, sym) 934 | } 935 | 936 | // ALU32Reg emits a 32 bit ALU operation on registers. 937 | // 938 | // dst = dst src 939 | func (s *InstructionStream) ALU32Reg(op ALUOp, dst, src Register) { 940 | s.Raw(Instruction{ 941 | Opcode: alu32Opcode(op, X), 942 | Dst: dst, 943 | Src: src, 944 | }) 945 | } 946 | 947 | // ALU32Imm emits a 32 bit ALU instruction on a register and a 32 bit 948 | // immediate. 949 | // 950 | // dst = int32(dst) imm 951 | func (s *InstructionStream) ALU32Imm(op ALUOp, dst Register, imm int32) { 952 | s.Raw(Instruction{ 953 | Opcode: alu32Opcode(op, K), 954 | Dst: dst, 955 | Imm: imm, 956 | }) 957 | } 958 | 959 | // ALU32Sym emits a 32 bit ALU instruction on a register and a 32 bit 960 | // symbolic immediate. 961 | // 962 | // dst = int32(dst) $sym 963 | func (s *InstructionStream) ALU32Sym(op ALUOp, dst Register, sym string) { 964 | s.RawSym(Instruction{ 965 | Opcode: alu32Opcode(op, K), 966 | Dst: dst, 967 | }, sym) 968 | } 969 | 970 | // Memory loads and stores. 971 | 972 | func memOpcode(class Class, size Size, mode Mode) Opcode { 973 | return Opcode(class) | Opcode(size) | Opcode(mode) 974 | } 975 | 976 | // MemLoad emids a memory load. 977 | // 978 | // dst = *(uintsz *)(src + off) 979 | func (s *InstructionStream) MemLoad(sz Size, dst, src Register, off int16) { 980 | s.Raw(Instruction{ 981 | Opcode: memOpcode(LDX, sz, MEM), 982 | Dst: dst, 983 | Src: src, 984 | Off: off, 985 | }) 986 | } 987 | 988 | // MemStoreReg emits a memory store from a register. 989 | // 990 | // *(uintsz *)(dst + off) = src 991 | func (s *InstructionStream) MemStoreReg(sz Size, dst, src Register, off int16) { 992 | s.Raw(Instruction{ 993 | Opcode: memOpcode(STX, sz, MEM), 994 | Dst: dst, 995 | Src: src, 996 | Off: off, 997 | }) 998 | } 999 | 1000 | // MemStoreImm emits a memory store from a 32 bit immediate. 1001 | // 1002 | // *(uintsz *)(dst + off) = imm 1003 | func (s *InstructionStream) MemStoreImm(sz Size, dst Register, off int16, imm int32) { 1004 | s.Raw(Instruction{ 1005 | Opcode: memOpcode(ST, sz, MEM), // TODO(acln): investigate this 1006 | Dst: dst, 1007 | Off: off, 1008 | Imm: imm, 1009 | }) 1010 | } 1011 | 1012 | // MemStoreSym emits a memory store from a 32 bit symbolic immediate. 1013 | // 1014 | // *(uintsz *)(dst + off) = $sym 1015 | func (s *InstructionStream) MemStoreSym(sz Size, dst Register, off int16, sym string) { 1016 | s.RawSym(Instruction{ 1017 | Opcode: memOpcode(ST, sz, MEM), // TODO(acln): investigate this 1018 | Dst: dst, 1019 | Off: off, 1020 | }, sym) 1021 | } 1022 | 1023 | // Conditional jumps. 1024 | 1025 | func jumpOpcode(cond JumpCond, operand SourceOperand) Opcode { 1026 | return Opcode(JMP) | Opcode(cond) | Opcode(operand) 1027 | } 1028 | 1029 | // JumpReg emits a conditional jump against registers. 1030 | // 1031 | // if dst src { goto pc + off } 1032 | func (s *InstructionStream) JumpReg(cond JumpCond, dst, src Register, off int16) { 1033 | s.Raw(Instruction{ 1034 | Opcode: jumpOpcode(cond, X), 1035 | Dst: dst, 1036 | Src: src, 1037 | Off: off, 1038 | }) 1039 | } 1040 | 1041 | // JumpImm emits a conditional jump against a 32 bit immediate. 1042 | // 1043 | // if dst imm { goto pc + off } 1044 | func (s *InstructionStream) JumpImm(cond JumpCond, dst Register, imm int32, off int16) { 1045 | s.Raw(Instruction{ 1046 | Opcode: jumpOpcode(cond, K), 1047 | Dst: dst, 1048 | Off: off, 1049 | Imm: imm, 1050 | }) 1051 | } 1052 | 1053 | // JumpSym emits a contitional jump against a symbolic 32 bit immediate. 1054 | // 1055 | // if dst $sym { goto pc + off } 1056 | func (s *InstructionStream) JumpSym(cond JumpCond, dst Register, sym string, off int16) { 1057 | s.RawSym(Instruction{ 1058 | Opcode: jumpOpcode(cond, K), 1059 | Dst: dst, 1060 | Off: off, 1061 | }, sym) 1062 | } 1063 | 1064 | // Special instructions. 1065 | 1066 | // LoadImm64 emits the special 'load 64 bit immediate' instruction. 1067 | // 1068 | // dst = imm 1069 | func (s *InstructionStream) LoadImm64(dst Register, imm int64) { 1070 | s.Raw(Instruction{ 1071 | Opcode: memOpcode(LD, DW, IMM), 1072 | Dst: dst, 1073 | Imm: int32(imm), 1074 | }) 1075 | s.Raw(Instruction{ 1076 | Imm: int32(imm >> 32), 1077 | }) 1078 | } 1079 | 1080 | // LoadImm64Sym emits the special 'load 64 bit immediate' instruction, 1081 | // with a symbolic immediate. 1082 | // 1083 | // dst = $sym 1084 | func (s *InstructionStream) LoadImm64Sym(dst Register, sym string) { 1085 | s.Raw(Instruction{ 1086 | Opcode: memOpcode(LD, DW, IMM), 1087 | Dst: dst, 1088 | }) 1089 | s.Raw(Instruction{}) 1090 | s.addImm64Sym(sym, len(s.insns)-2) 1091 | } 1092 | 1093 | // LoadMapFD emits the special 'load map file descriptor' instruction. 1094 | // 1095 | // dst = ptr_to_map_fd($mapName) 1096 | func (s *InstructionStream) LoadMapFD(dst Register, mapName string) { 1097 | s.Raw(Instruction{ 1098 | Opcode: memOpcode(LD, DW, IMM), 1099 | Dst: dst, 1100 | Src: PseudoMapFD, 1101 | }) 1102 | s.Raw(Instruction{}) 1103 | s.addMapSym(mapName, len(s.insns)-2) 1104 | } 1105 | 1106 | // LoadAbs emits the special "direct packet access" instruction. 1107 | // 1108 | // r0 = *(uintsz *)(skb->data + imm) 1109 | func (s *InstructionStream) LoadAbs(sz Size, imm int32) { 1110 | s.Raw(Instruction{ 1111 | Opcode: memOpcode(LD, sz, ABS), 1112 | Imm: imm, 1113 | }) 1114 | } 1115 | 1116 | // LoadAbsSym emits the special "direct packet access" instruction, 1117 | // with a symbolic immediate. 1118 | // 1119 | // r0 = *(uintsz *)(skb->data + $sym) 1120 | func (s *InstructionStream) LoadAbsSym(sz Size, sym string) { 1121 | s.RawSym(Instruction{ 1122 | Opcode: memOpcode(LD, sz, ABS), 1123 | }, sym) 1124 | } 1125 | 1126 | // AtomicAdd64 emits a 64 bit atomic add to a memory location. 1127 | // 1128 | // *(uint64 *)(dst + off) += src 1129 | func (s *InstructionStream) AtomicAdd64(dst, src Register, off int16) { 1130 | s.Raw(Instruction{ 1131 | Opcode: memOpcode(STX, DW, XADD), 1132 | Dst: dst, 1133 | Src: src, 1134 | Off: off, 1135 | }) 1136 | } 1137 | 1138 | // AtomicAdd32 emits a 32 bit atomic add to a memory location. 1139 | // 1140 | // *(uint32 *)(dst + off) += src 1141 | func (s *InstructionStream) AtomicAdd32(dst, src Register, off int16) { 1142 | s.Raw(Instruction{ 1143 | Opcode: memOpcode(STX, W, XADD), 1144 | Dst: dst, 1145 | Src: src, 1146 | Off: off, 1147 | }) 1148 | } 1149 | 1150 | // Call emits a kernel function call instruction. 1151 | func (s *InstructionStream) Call(fn KernelFunc) { 1152 | s.Raw(Instruction{ 1153 | Opcode: Opcode(JMP) | Opcode(CALL), 1154 | Imm: int32(fn), 1155 | }) 1156 | } 1157 | 1158 | // Exit emits a program exit instruction. 1159 | func (s *InstructionStream) Exit() { 1160 | s.Raw(Instruction{ 1161 | Opcode: Opcode(JMP) | Opcode(EXIT), 1162 | }) 1163 | } 1164 | 1165 | // Symbol handling routines. 1166 | 1167 | // SymbolTable is a symbol table for an eBPF program. 1168 | type SymbolTable struct { 1169 | // Maps contains eBPF maps. Symbol names are derived from the 1170 | // ObjectName fields, which must be unique across all maps in the 1171 | // collection. 1172 | Maps []*Map 1173 | 1174 | // Imm32 maps symbol names to 32 bit immediate values. 1175 | Imm32 map[string]int32 1176 | 1177 | // Imm64 maps symbol names to 64 bit immediate values. 1178 | Imm64 map[string]int64 1179 | } 1180 | 1181 | // Resolve resolves symbols in the instruction stream using the specified 1182 | // symbol table. 1183 | // 1184 | // If a symbol referenced by the instruction stream is not found in the 1185 | // symbol table, Resolve returns an UnresolvedSymbolError, and the instruction 1186 | // stream cannot be assembled and loaded into the kernel. 1187 | func (s *InstructionStream) Resolve(symtab *SymbolTable) error { 1188 | mapsByName := map[string]*Map{} 1189 | for _, m := range symtab.Maps { 1190 | mapsByName[m.ObjectName] = m 1191 | } 1192 | if err := s.resolveMapSyms(mapsByName); err != nil { 1193 | return err 1194 | } 1195 | if err := s.resolveImm32Syms(symtab.Imm32); err != nil { 1196 | return err 1197 | } 1198 | if err := s.resolveImm64Syms(symtab.Imm64); err != nil { 1199 | return err 1200 | } 1201 | s.resolved = true 1202 | return nil 1203 | } 1204 | 1205 | // UnresolvedSymbolError captures an unresolved symbol in an instruction 1206 | // stream. 1207 | type UnresolvedSymbolError struct { 1208 | // Kind is the kind of the symbol: "map", "imm32" or "imm64". 1209 | Kind string 1210 | 1211 | // Name is the name of the symbol. 1212 | Name string 1213 | 1214 | // Opcode is the opcode of the first instruction that references 1215 | // the symbol. 1216 | Opcode Opcode 1217 | 1218 | // Index is the index (in the instruction stream) of the first 1219 | // instruction that references the symbol. 1220 | Index int 1221 | } 1222 | 1223 | func (e *UnresolvedSymbolError) Error() string { 1224 | return fmt.Sprintf("epbf: unresolved %s symbol %q for %v instruction at index %d", 1225 | e.Kind, e.Name, e.Opcode, e.Index) 1226 | } 1227 | 1228 | func (s *InstructionStream) resolveMapSyms(maps map[string]*Map) error { 1229 | for name, indices := range s.mapSyms { 1230 | m, ok := maps[name] 1231 | if !ok { 1232 | return &UnresolvedSymbolError{ 1233 | Kind: "map", 1234 | Name: name, 1235 | Opcode: Opcode(s.insns[indices[0]].Opcode), 1236 | Index: indices[0], 1237 | } 1238 | } 1239 | fd, err := m.readFD() 1240 | if err != nil { 1241 | // The map isn't valid. Nothing to do but bail out. 1242 | // TODO(acln): annotate this more? 1243 | return err 1244 | } 1245 | for _, index := range indices { 1246 | // TODO(acln): is this correct? investigate 1247 | s.insns[index].Imm = int32(fd) 1248 | s.insns[index+1].Imm = int32(fd >> 32) 1249 | } 1250 | } 1251 | return nil 1252 | } 1253 | 1254 | func (s *InstructionStream) resolveImm32Syms(values map[string]int32) error { 1255 | for name, indices := range s.imm32Syms { 1256 | imm, ok := values[name] 1257 | if !ok { 1258 | return &UnresolvedSymbolError{ 1259 | Kind: "imm32", 1260 | Name: name, 1261 | Opcode: Opcode(s.insns[indices[0]].Opcode), 1262 | Index: indices[0], 1263 | } 1264 | } 1265 | for _, index := range indices { 1266 | s.insns[index].Imm = imm 1267 | } 1268 | } 1269 | return nil 1270 | } 1271 | 1272 | func (s *InstructionStream) resolveImm64Syms(values map[string]int64) error { 1273 | for name, indices := range s.imm64Syms { 1274 | imm, ok := values[name] 1275 | if !ok { 1276 | return &UnresolvedSymbolError{ 1277 | Kind: "imm64", 1278 | Name: name, 1279 | Opcode: Opcode(s.insns[indices[0]].Opcode), 1280 | Index: indices[0], 1281 | } 1282 | } 1283 | for _, index := range indices { 1284 | // TODO(acln): is this correct? investigate 1285 | s.insns[index].Imm = int32(imm) 1286 | s.insns[index+1].Imm = int32(imm >> 32) 1287 | } 1288 | } 1289 | return nil 1290 | } 1291 | 1292 | func (s *InstructionStream) addMapSym(name string, index int) { 1293 | if s.mapSyms == nil { 1294 | s.mapSyms = make(map[string][]int) 1295 | } 1296 | s.mapSyms[name] = append(s.mapSyms[name], index) 1297 | s.usesSymbols = true 1298 | } 1299 | 1300 | func (s *InstructionStream) addImm32Sym(name string, index int) { 1301 | if s.imm32Syms == nil { 1302 | s.imm32Syms = make(map[string][]int) 1303 | } 1304 | s.imm32Syms[name] = append(s.imm32Syms[name], index) 1305 | s.usesSymbols = true 1306 | } 1307 | 1308 | func (s *InstructionStream) addImm64Sym(name string, index int) { 1309 | if s.imm64Syms == nil { 1310 | s.imm64Syms = make(map[string][]int) 1311 | } 1312 | s.imm64Syms[name] = append(s.imm64Syms[name], index) 1313 | s.usesSymbols = true 1314 | } 1315 | -------------------------------------------------------------------------------- /asm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | ) 21 | 22 | func TestInstructionStreamPrint(t *testing.T) { 23 | var s InstructionStream 24 | 25 | s.ALU64Reg(ADD, R1, R2) 26 | s.ALU64Imm(ADD, R1, 42) 27 | s.ALU64Sym(ADD, R1, "foo") 28 | s.Call(Redirect) 29 | s.JumpImm(JEQ, R2, 23, 2) 30 | s.JumpReg(JNE, R3, R4, -3) 31 | s.JumpSym(JLE, R6, "threshold", 5) 32 | s.MemStoreReg(W, R1, R2, 8) 33 | s.MemStoreImm(H, R1, 16, 42) 34 | s.MemStoreSym(B, R7, 24, "something") 35 | s.Exit() 36 | 37 | buf := new(bytes.Buffer) 38 | if err := s.PrintTo(buf); err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | t.Logf("\n%s", buf.String()) 43 | } 44 | -------------------------------------------------------------------------------- /ebpf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf 16 | 17 | import ( 18 | "errors" 19 | "os" 20 | "unsafe" 21 | 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | // Error checking routines. 26 | // 27 | // TODO(acln): remove when 1.13 is out 28 | 29 | type unwrapper interface { 30 | Unwrap() error 31 | } 32 | 33 | func unwrap(err error) error { 34 | for { 35 | werr, ok := err.(unwrapper) 36 | if !ok { 37 | break 38 | } 39 | err = werr.Unwrap() 40 | } 41 | return err 42 | } 43 | 44 | func IsNotExist(err error) bool { 45 | return os.IsNotExist(unwrap(err)) 46 | } 47 | 48 | func IsExist(err error) bool { 49 | return os.IsExist(unwrap(err)) 50 | } 51 | 52 | func IsTooBig(err error) bool { 53 | err = unwrap(err) 54 | if se, ok := err.(*os.SyscallError); ok { 55 | err = se.Err 56 | } 57 | return err == unix.E2BIG 58 | } 59 | 60 | func IsPerm(err error) bool { 61 | return os.IsPermission(unwrap(err)) 62 | } 63 | 64 | // command is a bpf(2) command. 65 | type command int 66 | 67 | // bpf(2) commands. 68 | const ( 69 | cmdMapCreate command = iota 70 | cmdMapLookup 71 | cmdMapUpdate 72 | cmdMapDeleteElem 73 | cmdMapGetNextKey 74 | cmdProgLoad 75 | cmdObjPin 76 | cmdObjGet 77 | cmdProgAttach 78 | cmdProgDetach 79 | cmdProgTestRun 80 | cmdProgGetNextID 81 | cmdMapGetNextID 82 | cmdProgGetFDByID 83 | cmdMapGetFDByID 84 | cmdObjGetInfoByFD 85 | cmdProgQuery 86 | cmdRawTracepointOpen 87 | cmdBTFLoad 88 | cmdBTFGetFDByID 89 | cmdTaskFDQuery 90 | ) 91 | 92 | func (cmd command) valid() bool { 93 | return cmd >= cmdMapCreate && cmd <= cmdTaskFDQuery 94 | } 95 | 96 | var commandNames = [...]string{ 97 | cmdMapCreate: "bpf_map_create", 98 | cmdMapLookup: "bpf_map_lookup_elem", 99 | cmdMapUpdate: "bpf_map_update_elem", 100 | cmdMapDeleteElem: "bpf_map_delete_elem", 101 | cmdMapGetNextKey: "bpf_map_get_next_key", 102 | cmdProgLoad: "bpf_prog_load", 103 | cmdObjPin: "bpf_obj_pin", 104 | cmdObjGet: "bpf_obj_get", 105 | cmdProgAttach: "bpf_prog_attach", 106 | cmdProgDetach: "bpf_prog_detach", 107 | cmdProgTestRun: "bpf_prog_test_run", 108 | cmdProgGetNextID: "bpf_prog_get_next_id", 109 | cmdMapGetNextID: "bpf_map_get_next_id", 110 | cmdProgGetFDByID: "bpf_prog_get_fd_by_id", 111 | cmdMapGetFDByID: "bpf_map_get_fd_by_id", 112 | cmdObjGetInfoByFD: "bpf_obj_get_info_by_fd", 113 | cmdProgQuery: "bpf_prog_query", 114 | cmdRawTracepointOpen: "bpf_raw_tracepoint_open", 115 | cmdBTFLoad: "bpf_btf_load", 116 | cmdBTFGetFDByID: "bpf_btf_get_fd_by_id", 117 | cmdTaskFDQuery: "bpf_task_fd_query", 118 | } 119 | 120 | func (cmd command) String() string { 121 | if !cmd.valid() { 122 | return "bpf_unknown" 123 | } 124 | return commandNames[cmd] 125 | } 126 | 127 | // errNotImplemented signals that a feature is not implemented. 128 | // 129 | // TODO(acln): remove this when we no longer need it 130 | var errNotImplemented = errors.New("ebpf: not implemented") 131 | 132 | // objectName is a null-terminated string, at most 15 bytes long. 133 | type objectName [16]byte 134 | 135 | // newObjectName creates a new object name from s. s must not contain null 136 | // bytes. If s is longer than 15 bytes, tailing bytes are truncated. 137 | func newObjectName(s string) objectName { 138 | var name objectName 139 | if len(s) == 0 { 140 | return name 141 | } 142 | n := copy(name[:], s) 143 | name[n-1] = 0 144 | return name 145 | } 146 | 147 | // Low level data transformation routines. 148 | 149 | // uint32Bytes creates a []byte b such that &b[0] is i. 150 | func uint32Bytes(i *uint32) []byte { 151 | return (*[4]byte)(unsafe.Pointer(i))[:] 152 | } 153 | 154 | // bptr creates a u64ptr which carries &b[0]. If len(b) == 0, bptr returns 155 | // the null pointer. 156 | func bptr(b []byte) u64ptr { 157 | if len(b) == 0 { 158 | return u64ptr{p: unsafe.Pointer(nil)} 159 | } 160 | return u64ptr{p: unsafe.Pointer(&b[0])} 161 | } 162 | 163 | // iptr creates a u64ptr which carries &insns[0]. 164 | func iptr(insns []instruction) u64ptr { 165 | return u64ptr{p: unsafe.Pointer(&insns[0])} 166 | } 167 | 168 | // nullTerminatedString creates a null terminated string from s. 169 | func nullTerminatedString(s string) []byte { 170 | b := make([]byte, len(s)+1) 171 | copy(b, s) 172 | return b 173 | } 174 | -------------------------------------------------------------------------------- /fd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf 16 | 17 | import ( 18 | "errors" 19 | "os" 20 | "unsafe" 21 | 22 | "acln.ro/rc/v2" 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | var fdLifetimeRegistry *rc.LifetimeRegistry // set when testing 27 | 28 | // bpfFD is a bpf(2) file descriptor. 29 | type bpfFD struct { 30 | rcfd rc.FD 31 | } 32 | 33 | // RawFD returns the raw integer file descriptor associated with bfd. 34 | func (bfd *bpfFD) RawFD() (int, error) { 35 | var raw int 36 | 37 | err := bfd.rcfd.Do(func(rawfd int) error { 38 | raw = rawfd 39 | return nil 40 | }) 41 | if err != nil { 42 | return -1, err 43 | } 44 | return raw, nil 45 | } 46 | 47 | // Init initializes the file descriptor. 48 | func (bfd *bpfFD) Init(rawfd int, closeFunc func(int) error) error { 49 | bfd.rcfd.TrackLifetime(fdLifetimeRegistry) 50 | return bfd.rcfd.Init(rawfd, closeFunc) 51 | } 52 | 53 | // Close closes the file descriptor. 54 | func (bfd *bpfFD) Close() error { 55 | err := bfd.rcfd.Close() 56 | return rc.WrapSyscallError("close", err) 57 | } 58 | 59 | // MapLookup wraps the BPF_MAP_LOOKUP_ELEM command. 60 | func (bfd *bpfFD) MapLookup(k, v []byte) error { 61 | type mapLookupAttr struct { 62 | FD uint32 63 | Key u64ptr 64 | Value u64ptr 65 | _ uint64 66 | } 67 | 68 | const ( 69 | cmd = cmdMapLookup 70 | size = unsafe.Sizeof(mapLookupAttr{}) 71 | ) 72 | 73 | err := bfd.rcfd.Do(func(rawfd int) error { 74 | attr := mapLookupAttr{ 75 | FD: uint32(rawfd), 76 | Key: bptr(k), 77 | Value: bptr(v), 78 | } 79 | return bfd.bpf(cmd, unsafe.Pointer(&attr), size) 80 | }) 81 | return wrapCmdError(cmd, err) 82 | } 83 | 84 | // MapUpdate wraps the BPF_MAP_UPDATE command. 85 | func (bfd *bpfFD) MapUpdate(k, v []byte, flag uint64) error { 86 | type mapUpdateAttr struct { 87 | FD uint32 88 | Key u64ptr 89 | Value u64ptr 90 | Flag uint64 91 | } 92 | 93 | const ( 94 | cmd = cmdMapUpdate 95 | size = unsafe.Sizeof(mapUpdateAttr{}) 96 | ) 97 | 98 | err := bfd.rcfd.Do(func(rawfd int) error { 99 | attr := mapUpdateAttr{ 100 | FD: uint32(rawfd), 101 | Key: bptr(k), 102 | Value: bptr(v), 103 | Flag: flag, 104 | } 105 | return bfd.bpf(cmd, unsafe.Pointer(&attr), size) 106 | }) 107 | return wrapCmdError(cmd, err) 108 | } 109 | 110 | // MapDeleteElem wraps BPF_MAP_DELETE_ELEM. 111 | func (bfd *bpfFD) MapDeleteElem(k []byte) error { 112 | type mapDeleteElemAttr struct { 113 | FD uint32 114 | Key u64ptr 115 | _ uint64 116 | _ uint64 117 | } 118 | 119 | const ( 120 | cmd = cmdMapDeleteElem 121 | size = unsafe.Sizeof(mapDeleteElemAttr{}) 122 | ) 123 | 124 | err := bfd.rcfd.Do(func(rawfd int) error { 125 | attr := mapDeleteElemAttr{ 126 | FD: uint32(rawfd), 127 | Key: bptr(k), 128 | } 129 | return bfd.bpf(cmd, unsafe.Pointer(&attr), size) 130 | }) 131 | return wrapCmdError(cmd, err) 132 | } 133 | 134 | // FindFirstMapKey finds the first key in the map using BPF_MAP_GET_NEXT_KEY. 135 | // 136 | // If the specified hint is nil and BPF_MAP_GET_NEXT_KEY returns EFAULT, 137 | // FindFirstMapKey returns an error describing that the key hint must 138 | // be present. 139 | func (bfd *bpfFD) FindFirstMapKey(hint, firstKey []byte) error { 140 | err := bfd.MapGetNextKeyNoWrap(hint, firstKey) 141 | if err == unix.EFAULT && hint == nil { 142 | return errors.New("missing initial key hint for map iteration") 143 | } 144 | return wrapCmdError(cmdMapGetNextKey, err) 145 | } 146 | 147 | // MapGetNextKeyNoWrap wraps BPF_MAP_GET_NEXT_KEY, but does not wrap 148 | // the result in a higher level error. 149 | func (bfd *bpfFD) MapGetNextKeyNoWrap(k, next []byte) error { 150 | type mapGetNextKeyAttr struct { 151 | FD uint32 152 | Key u64ptr 153 | NextKey u64ptr 154 | _ uint64 155 | } 156 | 157 | const ( 158 | cmd = cmdMapGetNextKey 159 | size = unsafe.Sizeof(mapGetNextKeyAttr{}) 160 | ) 161 | 162 | return bfd.rcfd.Do(func(rawfd int) error { 163 | attr := mapGetNextKeyAttr{ 164 | FD: uint32(rawfd), 165 | Key: bptr(k), 166 | NextKey: bptr(next), 167 | } 168 | return bfd.bpf(cmd, unsafe.Pointer(&attr), size) 169 | }) 170 | } 171 | 172 | // ProgAttach attaches the program to the specified socket at the specified 173 | // level. 174 | func (bfd *bpfFD) ProgAttach(sockfd int, level int) error { 175 | const opt = unix.SO_ATTACH_BPF 176 | 177 | err := bfd.rcfd.Do(func(rawfd int) error { 178 | return unix.SetsockoptInt(sockfd, level, opt, rawfd) 179 | }) 180 | return os.NewSyscallError("setsockopt", err) 181 | } 182 | 183 | // ProgDetach detaches the program from the specified socket at the specified 184 | // level. 185 | func (bfd *bpfFD) ProgDetach(sockfd int, level int) error { 186 | const opt = unix.SO_DETACH_BPF 187 | 188 | err := bfd.rcfd.Do(func(rawfd int) error { 189 | return unix.SetsockoptInt(sockfd, level, opt, rawfd) 190 | }) 191 | return os.NewSyscallError("setsockopt", err) 192 | } 193 | 194 | // ProgTestRun executes a program test run. 195 | func (bfd *bpfFD) ProgTestRun(tr TestRun) (*TestResults, error) { 196 | // TODO(acln): document input and output params 197 | type progTestRunAttr struct { 198 | ProgFD uint32 199 | Retval uint32 200 | DataSizeIn uint32 201 | DataSizeOut uint32 202 | DataIn u64ptr 203 | DataOut u64ptr 204 | Repeat uint32 205 | Duration uint32 206 | } 207 | 208 | const ( 209 | cmd = cmdProgTestRun 210 | size = unsafe.Sizeof(progTestRunAttr{}) 211 | ) 212 | 213 | var results *TestResults 214 | 215 | err := bfd.rcfd.Do(func(rawfd int) error { 216 | attr := progTestRunAttr{ 217 | ProgFD: uint32(rawfd), 218 | DataSizeIn: uint32(len(tr.Input)), 219 | DataSizeOut: uint32(len(tr.Output)), 220 | DataIn: bptr(tr.Input), 221 | DataOut: bptr(tr.Output), 222 | Repeat: tr.Repeat, 223 | } 224 | err := bfd.bpf(cmdProgTestRun, unsafe.Pointer(&attr), size) 225 | if err != nil { 226 | return err 227 | } 228 | results = &TestResults{ 229 | ReturnValue: attr.Retval, 230 | Duration: attr.Duration, 231 | Output: tr.Output[:attr.DataSizeOut], 232 | TestRun: tr, 233 | } 234 | return nil 235 | }) 236 | return results, wrapCmdError(cmd, err) 237 | } 238 | 239 | func (bfd *bpfFD) bpf(cmd command, attr unsafe.Pointer, size uintptr) error { 240 | _, err := bpf(cmd, attr, size) 241 | return err 242 | } 243 | 244 | // wrapCmdError wraps an error from the specified bpf(2) command. 245 | func wrapCmdError(cmd command, err error) error { 246 | return rc.WrapSyscallError(cmd.String(), err) 247 | } 248 | 249 | // bpf calls bpf(2) with the specified arguments. It executes the command 250 | // cmd with attributes attr. size must be unsafe.Sizeof the object attr is 251 | // pointing to. 252 | func bpf(cmd command, attr unsafe.Pointer, size uintptr) (int, error) { 253 | r, _, e := unix.Syscall( 254 | unix.SYS_BPF, 255 | uintptr(cmd), 256 | uintptr(attr), 257 | size, 258 | ) 259 | if e != 0 { 260 | return int(r), e 261 | } 262 | return int(r), nil 263 | } 264 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module acln.ro/ebpf 2 | 3 | require ( 4 | acln.ro/rc/v2 v2.2.0 5 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 6 | ) 7 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | acln.ro/rc/v2 v2.2.0 h1:5+XoJ4mQOufXeSC1/bkmc44vXf56gff/MTBfyeVrFes= 2 | acln.ro/rc/v2 v2.2.0/go.mod h1:tkQuGOU6VkJy3Tx23hcERiXDvnr8OMuZxX8HgclXD/k= 3 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU= 4 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 5 | -------------------------------------------------------------------------------- /integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf_test 16 | 17 | import ( 18 | "io" 19 | "io/ioutil" 20 | "net" 21 | "syscall" 22 | "testing" 23 | "time" 24 | "unsafe" 25 | 26 | "acln.ro/ebpf" 27 | 28 | "golang.org/x/sys/unix" 29 | ) 30 | 31 | const ( 32 | offsetofIPHeader = 23 // ETH_HLEN + offsetof(struct iphdr, protocol) 33 | offsetofLinkLayer = -0x200000 // SKF_LL_OFF 34 | l4offsetofIPHeader = offsetofLinkLayer + offsetofIPHeader 35 | ) 36 | 37 | func TestTCP4Snoop(t *testing.T) { 38 | s := newTCPSnoop(t, "tcp4") 39 | defer s.Close() 40 | // Write a little data to the connection. 41 | if _, err := s.Write([]byte("hello")); err != nil { 42 | t.Fatalf("failed to write data: %v", err) 43 | } 44 | // We should have seen a packet. 45 | count, err := s.Count(unix.IPPROTO_TCP) 46 | if err != nil { 47 | t.Fatalf("failed to look up packet count: %v", err) 48 | } 49 | if count != 1 { 50 | t.Fatalf("packet count = %d, want %d", count, 1) 51 | } 52 | // Now, detach the program. 53 | if err := s.Detach(); err != nil { 54 | t.Fatalf("failed to detach from socket: %v", err) 55 | } 56 | // Write more data. 57 | if _, err := s.Write([]byte("world")); err != nil { 58 | t.Fatalf("failed to write data: %v", err) 59 | } 60 | // The count should still be 1. 61 | count, err = s.Count(unix.IPPROTO_TCP) 62 | if err != nil { 63 | t.Fatalf("failed to look up packet count: %v", err) 64 | } 65 | if count != 1 { 66 | t.Fatalf("after detach, packet count = %d, want %d", count, 1) 67 | } 68 | } 69 | 70 | type tcpSnoop struct { 71 | client net.Conn 72 | server net.Conn 73 | rawClient syscall.RawConn 74 | packetsByProto *ebpf.Map 75 | prog *ebpf.Prog 76 | } 77 | 78 | func newTCPSnoop(t *testing.T, network string) *tcpSnoop { 79 | t.Helper() 80 | const arrName = "snoop_counters" 81 | packetsByProto := &ebpf.Map{ 82 | Type: ebpf.MapArray, 83 | KeySize: 4, 84 | ValueSize: 8, 85 | MaxEntries: 256, 86 | ObjectName: arrName, 87 | } 88 | if err := packetsByProto.Init(); err != nil { 89 | t.Fatalf("failed to initialize array: %v", err) 90 | } 91 | var s ebpf.InstructionStream 92 | s.Mov64Reg(ebpf.R6, ebpf.R1) // r6 = r1 93 | s.LoadAbs(ebpf.B, l4offsetofIPHeader) // r0 = iphdr->protocol 94 | s.MemStoreReg(ebpf.W, ebpf.FP, ebpf.R0, -4) // store r0 to a slot on the stack 95 | s.Mov64Reg(ebpf.R2, ebpf.FP) // r2 = fp 96 | s.ALU64Imm(ebpf.ADD, ebpf.R2, -4) // r2 -= 4 97 | s.LoadMapFD(ebpf.R1, arrName) // load pointer to map in r1 98 | s.Call(ebpf.MapLookupElem) // call bpf_map_lookup_elem 99 | s.JumpImm(ebpf.JEQ, ebpf.R0, 0, 2) // if r0 == 0, pc += 2 100 | s.Mov64Imm(ebpf.R1, 1) // r1 = 1 101 | s.AtomicAdd64(ebpf.R0, ebpf.R1, 0) // xadd r0 += R1 102 | s.Mov64Imm(ebpf.R0, 0) // r0 = 0 103 | s.Exit() // return 104 | symtab := &ebpf.SymbolTable{ 105 | Maps: []*ebpf.Map{packetsByProto}, 106 | } 107 | if err := s.Resolve(symtab); err != nil { 108 | t.Fatalf("failed to resolve symbol table: %v", err) 109 | } 110 | prog := &ebpf.Prog{ 111 | Type: ebpf.ProgTypeSocketFilter, 112 | License: "ISC", 113 | StrictAlignment: true, 114 | ObjectName: "snoop_prog", 115 | } 116 | loadLog, err := prog.Load(&s) 117 | if testing.Verbose() { 118 | t.Logf("program load log: %s", loadLog) 119 | } 120 | if err != nil { 121 | t.Fatalf("failed to load program: %v", err) 122 | } 123 | client, server := newStreamSocketPair(t, network) 124 | sysClient, ok := client.(syscall.Conn) 125 | if !ok { 126 | t.Fatal("client does not implement syscall.Conn") 127 | } 128 | rawClient, err := sysClient.SyscallConn() 129 | if err != nil { 130 | t.Fatalf("failed to obtain syscall.RawConn: %v", err) 131 | } 132 | if err := prog.AttachToSocket(rawClient); err != nil { 133 | t.Fatalf("failed to attach to socket: %v", err) 134 | } 135 | return &tcpSnoop{ 136 | client: client, 137 | server: server, 138 | rawClient: rawClient, 139 | packetsByProto: packetsByProto, 140 | prog: prog, 141 | } 142 | } 143 | 144 | const snoopServerReadTimeout = 1 * time.Millisecond 145 | 146 | // Write writes b on the client connection and waits for the server to 147 | // read len(b) bytes. 148 | func (s *tcpSnoop) Write(b []byte) (int, error) { 149 | errch := make(chan error) 150 | go func() { 151 | deadline := time.Now().Add(snoopServerReadTimeout) 152 | if err := s.server.SetDeadline(deadline); err != nil { 153 | errch <- err 154 | return 155 | } 156 | _, err := io.CopyN(ioutil.Discard, s.server, int64(len(b))) 157 | errch <- err 158 | }() 159 | n, err := s.client.Write(b) 160 | srverr := <-errch 161 | if err != nil { 162 | return n, err 163 | } 164 | return n, srverr 165 | } 166 | 167 | // Detach detaches the snoop from the client connection. 168 | func (s *tcpSnoop) Detach() error { 169 | return s.prog.DetachFromSocket(s.rawClient) 170 | } 171 | 172 | // Count returns the number of packets with the given protocol the snoop has seen. 173 | func (s *tcpSnoop) Count(proto uint32) (uint64, error) { 174 | var count uint64 175 | if err := s.packetsByProto.Lookup(uint32b(&proto), uint64b(&count)); err != nil { 176 | return 0, err 177 | } 178 | return count, nil 179 | } 180 | 181 | func (s *tcpSnoop) Close() error { 182 | s.prog.Unload() 183 | s.packetsByProto.Close() 184 | s.server.Close() 185 | return s.client.Close() 186 | } 187 | 188 | func newStreamSocketPair(t *testing.T, network string) (client, server net.Conn) { 189 | t.Helper() 190 | ln, err := net.Listen(network, ":3000") 191 | if err != nil { 192 | t.Fatal(err) 193 | } 194 | defer ln.Close() 195 | errch := make(chan error) 196 | go func() { 197 | s, err := ln.Accept() 198 | if err != nil { 199 | errch <- err 200 | } 201 | server = s 202 | errch <- nil 203 | }() 204 | c, err := net.Dial(ln.Addr().Network(), ln.Addr().String()) 205 | if err != nil { 206 | <-errch 207 | t.Fatal(err) 208 | } 209 | if err := <-errch; err != nil { 210 | t.Fatal(err) 211 | } 212 | client = c 213 | return client, server 214 | } 215 | 216 | func uint32b(v *uint32) []byte { 217 | const size = unsafe.Sizeof(*v) 218 | return (*[size]byte)(unsafe.Pointer(v))[:] 219 | } 220 | 221 | func uint64b(v *uint64) []byte { 222 | const size = unsafe.Sizeof(*v) 223 | return (*[size]byte)(unsafe.Pointer(v))[:] 224 | } 225 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf 16 | 17 | // Low level map routines. 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "unsafe" 23 | 24 | "golang.org/x/sys/unix" 25 | ) 26 | 27 | // MapType is the type of an eBPF map. 28 | type MapType uint32 29 | 30 | // Supported map types. 31 | const ( 32 | MapUnspec MapType = iota 33 | 34 | MapHash 35 | MapArray 36 | MapProgArray 37 | MapPerfEventArray 38 | MapPerCPUHash 39 | MapPerCPUArray 40 | MapStackTrace 41 | MapCGroupArray 42 | MapLRUHash 43 | MapLRUPerCPUHash 44 | MapLPMTrie 45 | MapArrayOfMaps 46 | MapHashOfMaps 47 | MapDevmap 48 | MapSockmap 49 | MapCPUmap 50 | MapXSKMap 51 | MapSockhash 52 | MapCGroupStorage 53 | MapReuseportSockarray 54 | 55 | maxMapType // keep at the end 56 | ) 57 | 58 | // Map configures an eBPF map. 59 | // 60 | // Before use, a Map must be initialized using the Init method. KeySize, 61 | // and MaxEntries must be non-zero. For arrays, KeySize must be 4. 62 | // 63 | // TODO(acln): investigate if ValueSize can be zero. 64 | // 65 | // ObjectName names the map. Names must not contain the NULL character. Names 66 | // longer than 15 bytes are truncated. 67 | // 68 | // A Map must not be copied after initialization. After initialization, 69 | // it is safe (in the data race sense) to call methods on the Map from 70 | // multiple goroutines concurrently. However, it may not always be safe to 71 | // do so from the perspective of the actual eBPF map semantics. For example, 72 | // writes to hash maps are atomic, while writes to arrays are not. Consult 73 | // the bpf documentation for more details. 74 | // 75 | // TODO(acln): eitehr expand on where to find said documentation, or document 76 | // the semantics more precisely. 77 | type Map struct { 78 | Type MapType 79 | KeySize uint32 80 | ValueSize uint32 81 | MaxEntries uint32 82 | Flags uint32 // TODO(acln): investigate these 83 | InnerMap *Map // TODO(acln): is this right? 84 | NUMANode uint32 85 | ObjectName string 86 | InterfaceIndex uint32 // TODO(acln): document this 87 | 88 | // TODO(acln): add the BTF bits at some point 89 | 90 | // fd is the inner map FD. It is a pointer, to prevent callers 91 | // from copying a Map and sweeping the actual FD from under us. 92 | fd *mapFD 93 | } 94 | 95 | // Init initializes the map. 96 | func (m *Map) Init() error { 97 | if m.Type == MapUnspec { 98 | return errors.New("ebpf: unspecified map type") 99 | } 100 | if m.Type >= maxMapType { 101 | return fmt.Errorf("ebpf: invalid map type %d", m.Type) 102 | } 103 | if m.KeySize == 0 { 104 | return errors.New("ebpf: map KeySize not configured") 105 | } 106 | if m.MaxEntries == 0 { 107 | return errors.New("ebpf: map MaxEntries not configured") 108 | } 109 | cfg := mapAttr{ 110 | Type: m.Type, 111 | KeySize: m.KeySize, 112 | ValueSize: m.ValueSize, 113 | MaxEntries: m.MaxEntries, 114 | Flags: m.Flags, 115 | InnerMapFD: 0, // TODO(acln): fix this 116 | NUMANode: m.NUMANode, 117 | Name: newObjectName(m.ObjectName), 118 | InterfaceIndex: m.InterfaceIndex, 119 | } 120 | fd := new(mapFD) 121 | if err := fd.Init(&cfg); err != nil { 122 | return wrapMapOpError("init", err) 123 | } 124 | m.fd = fd 125 | return nil 126 | } 127 | 128 | var errUninitializedMap = errors.New("ebpf: use of uninitialized map") 129 | 130 | // Lookup looks up the value for k and stores it in v. If k is not found 131 | // in the map, Lookup returns an error such that IsNotExist(err) == true. 132 | func (m *Map) Lookup(k, v []byte) error { 133 | if m.fd == nil { 134 | return errUninitializedMap 135 | } 136 | return wrapMapOpError("lookup", m.fd.Lookup(k, v)) 137 | } 138 | 139 | // Set sets the value for k to v. If an entry for k exists in the map, 140 | // it will be overwritten. 141 | func (m *Map) Set(k, v []byte) error { 142 | if m.fd == nil { 143 | return errUninitializedMap 144 | } 145 | return wrapMapOpError("set", m.fd.Update(k, v, mapUpdateAny)) 146 | } 147 | 148 | // Create creates a new entry for k in the map, and sets the value to v. 149 | // If an entry for k exists in the map, Create returns an error such that 150 | // IsExist(err) == true. 151 | func (m *Map) Create(k, v []byte) error { 152 | if m.fd == nil { 153 | return errUninitializedMap 154 | } 155 | return wrapMapOpError("create", m.fd.Update(k, v, mapUpdateNoexist)) 156 | } 157 | 158 | // Update updates the entry for k to v. If an entry for k does not exist in 159 | // the map, Update returns an error such that IsNotExist(err) == true. 160 | func (m *Map) Update(k, v []byte) error { 161 | if m.fd == nil { 162 | return errUninitializedMap 163 | } 164 | return wrapMapOpError("update", m.fd.Update(k, v, mapUpdateExist)) 165 | } 166 | 167 | // DeleteElem deletes the entry for k. If an entry for k does not exist in 168 | // the map, DeleteElem returns an error such that IsNotExist(err) == true. 169 | func (m *Map) DeleteElem(k []byte) error { 170 | if m.fd == nil { 171 | return errUninitializedMap 172 | } 173 | return wrapMapOpError("delete", m.fd.DeleteElem(k)) 174 | } 175 | 176 | // MapIterFunc is a map iterator function. 177 | type MapIterFunc func(key, value []byte) (stop bool) 178 | 179 | // Iter iterates over all keys in the map and calls fn for each key-value 180 | // pair. If fn returns true or the final element of the map is reached, 181 | // iteration stops. fn must not retain the arguments it is called with. 182 | // 183 | // startHint optionally specifies a key that does *not* exist in the map, such 184 | // that Iterate can begin iteration from the first key that does. Due to the 185 | // nature of BPF map iterators, on Linux kernels older than 4.12, Iterate 186 | // requires a non-nil startHint. On Linux >= 4.12, startHint may be nil, but 187 | // it is recommended to pass a valid one nevertheless. 188 | func (m *Map) Iter(fn MapIterFunc, startHint []byte) error { 189 | if m.fd == nil { 190 | return errUninitializedMap 191 | } 192 | return wrapMapOpError("iter", m.fd.Iter(fn, startHint)) 193 | } 194 | 195 | // Close destroys the map and releases the associated file descriptor. After a call 196 | // to Close, future method calls on the Map will return errors. 197 | func (m *Map) Close() error { 198 | if m.fd == nil { 199 | return errUninitializedMap 200 | } 201 | return m.fd.Close() 202 | } 203 | 204 | // readFD stores the underlying file descriptor into fd. 205 | func (m *Map) readFD() (int, error) { 206 | if m.fd == nil { 207 | return -1, errUninitializedMap 208 | } 209 | return m.fd.RawFD() 210 | } 211 | 212 | // mapFD is a low level wrapper around a bpf map file descriptor. 213 | type mapFD struct { 214 | bfd bpfFD 215 | keySize int 216 | valueSize int 217 | maxEntries uint32 218 | } 219 | 220 | func (m *mapFD) Init(cfg *mapAttr) error { 221 | rawfd, err := createMap(cfg) 222 | if err != nil { 223 | return wrapCmdError(cmdMapCreate, err) 224 | } 225 | if err := m.bfd.Init(rawfd, unix.Close); err != nil { 226 | return err 227 | } 228 | m.keySize = int(cfg.KeySize) 229 | m.valueSize = int(cfg.ValueSize) 230 | m.maxEntries = cfg.MaxEntries 231 | return nil 232 | } 233 | 234 | func (m *mapFD) Lookup(k, v []byte) error { 235 | if err := m.ensureCorrectKeyValueSize(k, v); err != nil { 236 | return err 237 | } 238 | return m.bfd.MapLookup(k, v) 239 | 240 | } 241 | 242 | // Flags for map update operations. 243 | const ( 244 | mapUpdateAny = iota // BPF_UPDATE_ANY 245 | mapUpdateNoexist // BPF_UPDATE_NOEXIST 246 | mapUpdateExist // BPF_UPDATE_EXIST 247 | ) 248 | 249 | func (m *mapFD) Update(k, v []byte, flag uint64) error { 250 | if err := m.ensureCorrectKeyValueSize(k, v); err != nil { 251 | return err 252 | } 253 | return m.bfd.MapUpdate(k, v, flag) 254 | } 255 | 256 | func (m *mapFD) DeleteElem(k []byte) error { 257 | if err := m.ensureCorrectKeySize(k); err != nil { 258 | return err 259 | } 260 | return m.bfd.MapDeleteElem(k) 261 | } 262 | 263 | func (m *mapFD) Iter(fn MapIterFunc, startHint []byte) error { 264 | key := make([]byte, m.keySize) 265 | if err := m.bfd.FindFirstMapKey(startHint, key); err != nil { 266 | return err 267 | } 268 | nextKey := make([]byte, m.keySize) 269 | value := make([]byte, m.valueSize) 270 | for { 271 | if err := m.bfd.MapLookup(key, value); err != nil { 272 | return err 273 | } 274 | if stop := fn(key, value); stop { 275 | return nil 276 | } 277 | if err := m.bfd.MapGetNextKeyNoWrap(key, nextKey); err != nil { 278 | if err == unix.ENOENT { 279 | // No more entries. Clean end. 280 | return nil 281 | } 282 | return wrapCmdError(cmdMapGetNextKey, err) 283 | } 284 | copy(key, nextKey) 285 | } 286 | } 287 | 288 | func (m *mapFD) RawFD() (int, error) { 289 | return m.bfd.RawFD() 290 | } 291 | 292 | func (m *mapFD) Close() error { 293 | return m.bfd.Close() 294 | } 295 | 296 | // argumentSizeError records an error a mismatch between the size of a key 297 | // or value argument to a map operation, and the key or value size the map 298 | // was configured with. 299 | // 300 | // argumentSizeError is a programmer error, but it exists as an explicit 301 | // type because showing the caller an EINVAL that came from the kernel 302 | // might not be illuminating enough. The type is nevertheless unexported, 303 | // because this error is not actionable for callers. 304 | type argumentSizeError struct { 305 | arg string 306 | got int 307 | want int 308 | } 309 | 310 | func (e *argumentSizeError) Error() string { 311 | return fmt.Sprintf("%s size is %d, want %d", e.arg, e.got, e.want) 312 | } 313 | 314 | func (m *mapFD) ensureCorrectKeyValueSize(k, v []byte) error { 315 | if err := m.ensureCorrectKeySize(k); err != nil { 316 | return err 317 | } 318 | if len(v) != m.valueSize { 319 | return &argumentSizeError{ 320 | arg: "value", 321 | got: len(v), 322 | want: m.valueSize, 323 | } 324 | } 325 | return nil 326 | } 327 | 328 | func (m *mapFD) ensureCorrectKeySize(k []byte) error { 329 | if len(k) != m.keySize { 330 | return &argumentSizeError{ 331 | arg: "key", 332 | got: len(k), 333 | want: m.keySize, 334 | } 335 | } 336 | return nil 337 | } 338 | 339 | // createMap creates a new BPF map. 340 | func createMap(attr *mapAttr) (rawfd int, err error) { 341 | return bpf(cmdMapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 342 | } 343 | 344 | // mapAttr holds attributes which configure a map, for a BPF_MAP_CREATE 345 | // operation. 346 | type mapAttr struct { 347 | Type MapType 348 | KeySize uint32 349 | ValueSize uint32 350 | MaxEntries uint32 351 | Flags uint32 352 | InnerMapFD uint32 353 | NUMANode uint32 354 | Name objectName 355 | InterfaceIndex uint32 356 | BTFFD uint32 357 | BTFKeyTypeID uint32 358 | BTFValueTypeID uint32 359 | } 360 | 361 | // MapOpError records an error caused by a map operation. 362 | // 363 | // Op is the high level operation performed. 364 | // 365 | // In some cases, Err is of type SyscallError. 366 | type MapOpError struct { 367 | Op string 368 | Err error 369 | } 370 | 371 | func (e *MapOpError) Error() string { 372 | return fmt.Sprintf("ebpf: map %s: %v", e.Op, e.Err) 373 | } 374 | 375 | // Unwrap returns e.Err. 376 | func (e *MapOpError) Unwrap() error { 377 | return e.Err 378 | } 379 | 380 | // wrapMapOpError wraps err in a *MapOpError. Returns nil if err == nil. 381 | func wrapMapOpError(op string, err error) error { 382 | if err == nil { 383 | return nil 384 | } 385 | return &MapOpError{Op: op, Err: err} 386 | } 387 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | "unsafe" 21 | 22 | "acln.ro/rc/v2" 23 | ) 24 | 25 | func TestHashmap(t *testing.T) { 26 | check := trackFDs(t) 27 | defer check() 28 | 29 | t.Run("Lookup", testHashmapLookup) 30 | t.Run("Set", testHashmapSet) 31 | t.Run("Update", testHashmapUpdate) 32 | t.Run("Create", testHashmapCreate) 33 | t.Run("Iterate", testHashmapIterate) 34 | t.Run("Delete", testHashmapDelete) 35 | t.Run("E2BIG", testHashmapE2BIG) 36 | } 37 | 38 | func testHashmapLookup(t *testing.T) { 39 | h := newUint64Hashmap(t, 4) 40 | defer h.Close() 41 | 42 | k, v := uint64(4), uint64(8) 43 | if err := h.Set(k, v); err != nil { 44 | t.Fatal(err) 45 | } 46 | got, err := h.Lookup(k) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | if got != v { 51 | t.Fatalf("Lookup(%d): got %d, want %d", k, got, v) 52 | } 53 | k = uint64(123) 54 | got, err = h.Lookup(k) 55 | if err == nil { 56 | t.Fatalf("Lookup(%d) succeeded: got %x", k, got) 57 | } 58 | if !IsNotExist(err) { 59 | t.Fatalf("IsNotExist(%#v (%q)) == false, want true", err, err.Error()) 60 | } 61 | } 62 | 63 | func testHashmapSet(t *testing.T) { 64 | h := newUint64Hashmap(t, 4) 65 | defer h.Close() 66 | 67 | k, v := uint64(15), uint64(16) 68 | if err := h.Set(k, v); err != nil { 69 | t.Fatal(err) 70 | } 71 | got, err := h.Lookup(k) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | if got != v { 76 | t.Fatalf("Lookup(%d): got %d, want %d", k, got, v) 77 | } 78 | v = uint64(23) 79 | if err := h.Set(k, v); err != nil { 80 | t.Fatal(err) 81 | } 82 | got, err = h.Lookup(k) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | if got != v { 87 | t.Fatalf("Lookup(%d): got %d, want %d", k, got, v) 88 | } 89 | } 90 | 91 | func testHashmapUpdate(t *testing.T) { 92 | h := newUint64Hashmap(t, 4) 93 | defer h.Close() 94 | 95 | k, v := uint64(3), uint64(50) 96 | if err := h.Set(k, v); err != nil { 97 | t.Fatal(err) 98 | } 99 | v = uint64(51) 100 | if err := h.Update(k, v); err != nil { 101 | t.Fatal(err) 102 | } 103 | got, err := h.Lookup(k) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | if got != v { 108 | t.Fatalf("Lookup(%d): got %d, want %d", k, got, v) 109 | } 110 | k = uint64(5) 111 | err = h.Update(k, v) 112 | if err == nil { 113 | t.Fatalf("succeeded for non-existent key") 114 | } 115 | if !IsNotExist(err) { 116 | t.Fatalf("IsNotExist(%#v (%q)) == false, want true", err, err.Error()) 117 | } 118 | } 119 | 120 | func testHashmapCreate(t *testing.T) { 121 | h := newUint64Hashmap(t, 4) 122 | defer h.Close() 123 | 124 | k, v := uint64(23), uint64(42) 125 | if err := h.Create(k, v); err != nil { 126 | t.Fatal(err) 127 | } 128 | v = uint64(59) 129 | err := h.Create(k, v) 130 | if err == nil { 131 | t.Fatalf("succeeded for existing key") 132 | } 133 | if !IsExist(err) { 134 | t.Fatalf("IsExist(%#v (%q)) == false, want true", err, err.Error()) 135 | } 136 | } 137 | 138 | func testHashmapIterate(t *testing.T) { 139 | h := newUint64Hashmap(t, 8) 140 | defer h.Close() 141 | 142 | pairs := map[uint64]uint64{ 143 | 4: 8, 144 | 15: 16, 145 | 23: 42, 146 | } 147 | for k, v := range pairs { 148 | if err := h.Set(k, v); err != nil { 149 | t.Fatal(err) 150 | } 151 | } 152 | hint := ^uint64(0) 153 | seen := map[uint64]uint64{} 154 | fn := func(k, v uint64) bool { 155 | seen[k] = v 156 | return false 157 | } 158 | if err := h.Iterate(fn, hint); err != nil { 159 | t.Fatal(err) 160 | } 161 | if !reflect.DeepEqual(pairs, seen) { 162 | t.Fatalf("inserted %v, saw %v", pairs, seen) 163 | } 164 | } 165 | 166 | func testHashmapDelete(t *testing.T) { 167 | h := newUint64Hashmap(t, 4) 168 | defer h.Close() 169 | 170 | k, v := uint64(10), uint64(20) 171 | if err := h.Create(k, v); err != nil { 172 | t.Fatal(err) 173 | } 174 | if err := h.DeleteElem(k); err != nil { 175 | t.Fatal(err) 176 | } 177 | got, err := h.Lookup(k) 178 | if err == nil { 179 | t.Fatalf("Lookup(%d) succeeded after Delete(%d): got %d", k, k, got) 180 | } 181 | err = h.DeleteElem(k) 182 | if err == nil { 183 | t.Fatalf("succeeded for non-existent key") 184 | } 185 | if !IsNotExist(err) { 186 | t.Fatalf("IsNotExist(%#v (%q)) == false, want true", err, err.Error()) 187 | } 188 | } 189 | 190 | func testHashmapE2BIG(t *testing.T) { 191 | const size = 16 192 | h := newUint64Hashmap(t, size) 193 | defer h.Close() 194 | 195 | v := uint64(100) 196 | for i := 0; i < size; i++ { 197 | k := uint64(i) 198 | if err := h.Create(k, v); err != nil { 199 | t.Fatal(err) 200 | } 201 | } 202 | err := h.Create(uint64(size), v) 203 | if err == nil { 204 | t.Fatalf("Create succeeded on map at size limit") 205 | } 206 | if !IsTooBig(err) { 207 | t.Fatalf("IsTooBig(%#v (%q)) == false, want true", err, err.Error()) 208 | } 209 | } 210 | 211 | func newUint64Hashmap(t *testing.T, maxEntries uint32) *uint64Hashmap { 212 | t.Helper() 213 | m := &Map{ 214 | Type: MapHash, 215 | KeySize: uint32(unsafe.Sizeof(uint64(0))), 216 | ValueSize: uint32(unsafe.Sizeof(uint64(0))), 217 | MaxEntries: maxEntries, 218 | ObjectName: "test_map", 219 | } 220 | if err := m.Init(); err != nil { 221 | t.Fatal(err) 222 | } 223 | return &uint64Hashmap{m: m} 224 | } 225 | 226 | type uint64Hashmap struct { 227 | m *Map 228 | } 229 | 230 | func (h *uint64Hashmap) Lookup(k uint64) (v uint64, err error) { 231 | err = h.m.Lookup(uint64b(&k), uint64b(&v)) 232 | return v, err 233 | } 234 | 235 | func (h *uint64Hashmap) Set(k, v uint64) error { 236 | return h.m.Set(uint64b(&k), uint64b(&v)) 237 | } 238 | 239 | func (h *uint64Hashmap) Create(k, v uint64) error { 240 | return h.m.Create(uint64b(&k), uint64b(&v)) 241 | } 242 | 243 | func (h *uint64Hashmap) Update(k, v uint64) error { 244 | return h.m.Update(uint64b(&k), uint64b(&v)) 245 | } 246 | 247 | func (h *uint64Hashmap) Iterate(fn func(k, v uint64) bool, hint uint64) error { 248 | bfn := func(kb, vb []byte) bool { 249 | kp, vp := uint64ptr(kb), uint64ptr(vb) 250 | return fn(*kp, *vp) 251 | } 252 | return h.m.Iter(bfn, uint64b(&hint)) 253 | } 254 | 255 | func (h *uint64Hashmap) DeleteElem(k uint64) error { 256 | return h.m.DeleteElem(uint64b(&k)) 257 | } 258 | 259 | func (h *uint64Hashmap) Close() error { 260 | return h.m.Close() 261 | } 262 | 263 | func uint32b(v *uint32) []byte { 264 | const size = unsafe.Sizeof(*v) 265 | return (*[size]byte)(unsafe.Pointer(v))[:] 266 | } 267 | 268 | func uint64b(v *uint64) []byte { 269 | const size = unsafe.Sizeof(*v) 270 | return (*[size]byte)(unsafe.Pointer(v))[:] 271 | } 272 | 273 | func uint64ptr(b []byte) *uint64 { 274 | return (*uint64)(unsafe.Pointer(&b[0])) 275 | } 276 | 277 | func TestArray(t *testing.T) { 278 | check := trackFDs(t) 279 | defer check() 280 | 281 | t.Run("Lookup", testArrayLookup) 282 | t.Run("OOBAccess", testArrayOOBAccess) 283 | } 284 | 285 | func testArrayLookup(t *testing.T) { 286 | a := newUint64Array(t, 8) 287 | defer a.Close() 288 | 289 | values := []uint64{4, 8, 15, 16, 23, 42} 290 | for i, v := range values { 291 | if err := a.Set(uint32(i), v); err != nil { 292 | t.Fatal(err) 293 | } 294 | } 295 | for i, v := range values { 296 | got, err := a.Lookup(uint32(i)) 297 | if err != nil { 298 | t.Fatal(err) 299 | } 300 | if got != v { 301 | t.Fatalf("Lookup(%d): got %d, want %d", i, got, v) 302 | } 303 | } 304 | } 305 | 306 | func testArrayOOBAccess(t *testing.T) { 307 | const size = 4 308 | a := newUint64Array(t, size) 309 | defer a.Close() 310 | 311 | index := uint32(size) 312 | v := uint64(5) 313 | err := a.Set(index, 0) 314 | if err == nil { 315 | t.Fatalf("Set(%d, %d) succeeded on array of size %d", index, v, size) 316 | } 317 | got, err := a.Lookup(index) 318 | if err == nil { 319 | t.Fatalf("Lookup(%d) succeeded on array of size %d: got %d", index, size, got) 320 | } 321 | } 322 | 323 | type uint64Array struct { 324 | m *Map 325 | } 326 | 327 | func newUint64Array(t *testing.T, numElements int) *uint64Array { 328 | t.Helper() 329 | m := &Map{ 330 | Type: MapArray, 331 | KeySize: uint32(unsafe.Sizeof(uint32(0))), 332 | ValueSize: uint32(unsafe.Sizeof(uint64(0))), 333 | MaxEntries: uint32(numElements), 334 | ObjectName: "test_array", 335 | } 336 | if err := m.Init(); err != nil { 337 | t.Fatal(err) 338 | } 339 | return &uint64Array{m: m} 340 | } 341 | 342 | func (arr *uint64Array) Set(index uint32, value uint64) error { 343 | return arr.m.Set(uint32b(&index), uint64b(&value)) 344 | } 345 | 346 | func (arr *uint64Array) Lookup(index uint32) (uint64, error) { 347 | var value uint64 348 | err := arr.m.Lookup(uint32b(&index), uint64b(&value)) 349 | return value, err 350 | } 351 | 352 | func (arr *uint64Array) Close() error { 353 | return arr.m.Close() 354 | } 355 | 356 | func trackFDs(t *testing.T) (check func()) { 357 | fdLifetimeRegistry = new(rc.LifetimeRegistry) 358 | 359 | return func() { 360 | t.Helper() 361 | 362 | defer func() { fdLifetimeRegistry = nil }() 363 | 364 | stats := fdLifetimeRegistry.FDStats() 365 | if report := stats.Report(); report != "" { 366 | t.Error(report) 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /prog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf 16 | 17 | import ( 18 | "errors" 19 | "unsafe" 20 | 21 | "golang.org/x/sys/unix" 22 | ) 23 | 24 | // ProgType is the type of an eBPF program. 25 | type ProgType uint32 26 | 27 | // Valid eBPF program types. 28 | const ( 29 | ProgTypeUnspec ProgType = iota 30 | ProgTypeSocketFilter 31 | ProgTypeKProbe 32 | ProgTypeSchedCLS 33 | ProgTypeSchedACT 34 | ProgTypeTracepoint 35 | ProgTypeXDP 36 | ProgTypePerfEvent 37 | ProgTypeCGroupSKB 38 | ProgTypeCGroupSock 39 | ProgTypeLWTIn 40 | ProgTypeLWTOut 41 | ProgTypeLWTXMit 42 | ProgTypeSockOps 43 | ProgTypeSKSKB 44 | ProgTypeCGroupDevice 45 | ProgTypeSKMsg 46 | ProgTypeRawTracepoint 47 | ProgTypeCGroupSockAddr 48 | ProgTypeLWTSeg6Local 49 | ProgTypeLIRCMode2 50 | ProgTypeSKReusePort 51 | ) 52 | 53 | // AttachType describes the attach type of an eBPF program. 54 | type AttachType uint32 55 | 56 | // Valid program attach types. 57 | const ( 58 | AttachTypeCGroupInetIngress AttachType = iota 59 | AttachTypeCGroupInetEgress 60 | AttachTypeCGroupInetSockCreate 61 | AttachTypeCGroupSockOps 62 | AttachTypeSKSKBStreamParser 63 | AttachTypeSKSKBStreamVerdict 64 | AttachTypeCGroupDevice 65 | AttachTypeSKMsgVerdict 66 | AttachTypeCGroupInet4Bind 67 | AttachTypeCGroupInet6Bind 68 | AttachTypeCGroupInet4Connect 69 | AttachTypeCGroupInet6Connect 70 | AttachTypeCGroupInet4PostBind 71 | AttachTypeCGroupInet6PostBind 72 | AttachTypeCGroupUDP4SendMsg 73 | AttachTypeCGroupUDP6SendMsg 74 | AttachTypeLIRCMode2 75 | ) 76 | 77 | // Prog configures an eBPF program. 78 | type Prog struct { 79 | Type ProgType 80 | License string 81 | KernelVersion uint32 82 | StrictAlignment bool 83 | ObjectName string 84 | IfIndex uint32 85 | ExpectedAttachType AttachType 86 | 87 | pfd *progFD 88 | } 89 | 90 | // defaultLogBufSize is the log buffer size used by the Linux tools. 91 | // See tools/lib/bpf.h in the Linux kernel source tree. We use it as-is. 92 | // Perhaps it will be configurable one day. 93 | // 94 | // TODO(acln): configurable? 95 | const defaultLogBufSize = 256 * 1024 96 | 97 | // BPF_PROG_LOAD flags. 98 | const loadStrictAlignment = 1 << 0 99 | 100 | // CGroupAttachFlag is a flag for an AttachCGroup operation. 101 | type CGroupAttachFlag uint32 102 | 103 | // cgroup attach flags. 104 | const ( 105 | // CGroupAttachAllowNone allows no further bpf programs in the target 106 | // cgroup sub-tree. 107 | CGroupAttachAllowNone CGroupAttachFlag = 0 108 | 109 | // CGroupAttachAllowOverride arranges for the program in this cgroup 110 | // to yield to programs installed by sub-cgroups. 111 | CGroupAttachAllowOverride CGroupAttachFlag = 1 << 0 112 | 113 | // CGroupAttachAllowMulti arranges for the program in this cgroup 114 | // to run in addition to programs installed by sub-cgroups. 115 | CGroupAttachAllowMulti CGroupAttachFlag = 1 << 1 116 | ) 117 | 118 | // Load attaches the specified InstructionStream to the Prog 119 | // and loads the program into the kernel. 120 | // 121 | // If the specified InstructionStream uses symbols, all symbols must 122 | // be resolved before calling Load. 123 | // 124 | // If loading the program produces output from the eBPF kernel verifier, 125 | // the output is returned in the log string. 126 | func (p *Prog) Load(s *InstructionStream) (log string, err error) { 127 | if s.empty() { 128 | return "", errors.New("ebpf: empty instruction stream") 129 | } 130 | if s.hasUnresolvedSymbols() { 131 | return "", errors.New("ebpf: unresolved symbols in instruction stream") 132 | } 133 | insns := s.instructions() 134 | logbuf := make([]byte, defaultLogBufSize) 135 | attr := progLoadAttr{ 136 | Type: p.Type, 137 | InstructionCount: uint32(len(insns)), 138 | Instructions: iptr(insns), 139 | License: bptr(nullTerminatedString(p.License)), 140 | LogLevel: 1, 141 | LogBufSize: uint32(len(logbuf)), 142 | LogBuf: bptr(logbuf), 143 | KernelVersion: p.KernelVersion, 144 | Name: newObjectName(p.ObjectName), 145 | IfIndex: p.IfIndex, 146 | ExpectedAttachType: p.ExpectedAttachType, 147 | } 148 | if p.StrictAlignment { 149 | attr.Flags = loadStrictAlignment 150 | } 151 | pfd := new(progFD) 152 | err = pfd.Init(&attr) 153 | for i := 0; i < len(logbuf); i++ { 154 | if logbuf[i] == 0 { 155 | log = string(logbuf[:i]) 156 | break 157 | } 158 | } 159 | if err != nil { 160 | return log, err 161 | } 162 | p.pfd = pfd 163 | return log, nil 164 | } 165 | 166 | // Socket represents a socket an eBPF program can be attached to. 167 | // 168 | // Note that implementations of syscall.RawConn also satisfy Socket. 169 | type Socket interface { 170 | Control(fn func(fd uintptr)) error 171 | } 172 | 173 | // RawSocketFD is an implementation of Socket that uses a raw file descriptor. 174 | type RawSocketFD int 175 | 176 | // Control calls fn on raw. It always returns nil. 177 | func (raw RawSocketFD) Control(fn func(fd uintptr)) error { 178 | fn(uintptr(raw)) 179 | return nil 180 | } 181 | 182 | var errProgNotLoaded = errors.New("ebpf: program not loaded") 183 | 184 | // AttachToSocket attaches the program to a socket. 185 | // 186 | // It sets the SO_ATTACH_BPF option, at the SOL_SOCKET level. 187 | func (p *Prog) AttachToSocket(sock Socket) error { 188 | if p.pfd == nil { 189 | return errProgNotLoaded 190 | } 191 | var err error 192 | cerr := sock.Control(func(fd uintptr) { 193 | err = p.pfd.AttachToSocket(int(fd)) 194 | }) 195 | if cerr != nil { 196 | return cerr 197 | } 198 | return err 199 | } 200 | 201 | // AttachToCGroup attaches the program to a control group. 202 | // 203 | // TODO(acln): implement this 204 | func (p *Prog) AttachToCGroup(fd int, typ AttachType, flag CGroupAttachFlag) error { 205 | return errNotImplemented 206 | } 207 | 208 | // DetachFromSocket detaches the program from the specified socket. 209 | func (p *Prog) DetachFromSocket(sock Socket) error { 210 | if p.pfd == nil { 211 | return errProgNotLoaded 212 | } 213 | var err error 214 | cerr := sock.Control(func(fd uintptr) { 215 | err = p.pfd.DetachFromSocket(int(fd)) 216 | }) 217 | if cerr != nil { 218 | return cerr 219 | } 220 | return err 221 | } 222 | 223 | // TestRun specifies a test run for an eBPF program. 224 | type TestRun struct { 225 | // Input contains the input for the eBPF program. 226 | Input []byte 227 | 228 | // Output is the memory area where the output of the 229 | // program will be stored. 230 | // 231 | // TODO(acln): document the ENOSPC 232 | Output []byte 233 | 234 | // Repeat configures the number of times the program is to be 235 | // executed. The default value of 0 means one execution. 236 | Repeat uint32 237 | } 238 | 239 | // TestResults holds the results of a test run. 240 | type TestResults struct { 241 | // ReturnValue is the return value of the eBPF program. 242 | ReturnValue uint32 243 | 244 | // Duration is the total execution time, in nanoseconds. 245 | Duration uint32 246 | 247 | // Output is the output slice. It aliases TestRun.Output, but its 248 | // length is set to the length returned by the kernel. 249 | Output []byte 250 | 251 | // TestRun is the associated test run configuration. 252 | TestRun TestRun 253 | } 254 | 255 | // DoTestRun executes a test run of the program. 256 | func (p *Prog) DoTestRun(tr TestRun) (*TestResults, error) { 257 | if p.pfd == nil { 258 | return nil, errProgNotLoaded 259 | } 260 | return p.pfd.DoTestRun(tr) 261 | } 262 | 263 | // Unload unloads the program from the kernel and releases the associated 264 | // file descriptor. 265 | func (p *Prog) Unload() error { 266 | if p.pfd == nil { 267 | return errProgNotLoaded 268 | } 269 | return p.pfd.Close() 270 | } 271 | 272 | // progFD is a low level wrapper around a bpf program file descriptor. 273 | type progFD struct { 274 | bfd bpfFD 275 | } 276 | 277 | func (pfd *progFD) Init(attr *progLoadAttr) error { 278 | rawfd, err := loadProg(attr) 279 | if err != nil { 280 | return wrapCmdError(cmdProgLoad, err) 281 | } 282 | if err := pfd.bfd.Init(rawfd, unix.Close); err != nil { 283 | return err 284 | } 285 | // TODO(acln): what do we do about the attach type? 286 | return nil 287 | } 288 | 289 | func (pfd *progFD) AttachToSocket(sockfd int) error { 290 | return pfd.bfd.ProgAttach(sockfd, unix.SOL_SOCKET) 291 | } 292 | 293 | func (pfd *progFD) DetachFromSocket(sockfd int) error { 294 | return pfd.bfd.ProgDetach(sockfd, unix.SOL_SOCKET) 295 | } 296 | 297 | func (pfd *progFD) DoTestRun(tr TestRun) (*TestResults, error) { 298 | return pfd.bfd.ProgTestRun(tr) 299 | } 300 | 301 | func (pfd *progFD) Close() error { 302 | return pfd.bfd.Close() 303 | } 304 | 305 | type progLoadAttr struct { 306 | Type ProgType 307 | InstructionCount uint32 308 | Instructions u64ptr 309 | License u64ptr // pointer to null-terminated string 310 | LogLevel uint32 311 | LogBufSize uint32 312 | LogBuf u64ptr 313 | KernelVersion uint32 314 | Flags uint32 315 | Name objectName 316 | IfIndex uint32 317 | ExpectedAttachType AttachType 318 | } 319 | 320 | func loadProg(attr *progLoadAttr) (int, error) { 321 | return bpf(cmdProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 322 | } 323 | -------------------------------------------------------------------------------- /prog_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package ebpf_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "acln.ro/ebpf" 21 | 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | func TestProgTestRun(t *testing.T) { 26 | const arrName = "snoop_counters" 27 | packetsByProto := &ebpf.Map{ 28 | Type: ebpf.MapArray, 29 | KeySize: 4, 30 | ValueSize: 8, 31 | MaxEntries: 256, 32 | ObjectName: arrName, 33 | } 34 | if err := packetsByProto.Init(); err != nil { 35 | t.Fatalf("failed to initialize array: %v", err) 36 | } 37 | const retval = 0 38 | var s ebpf.InstructionStream 39 | s.Mov64Reg(ebpf.R6, ebpf.R1) // r6 = r1 40 | s.LoadAbs(ebpf.B, offsetofIPHeader) // r0 = iphdr->protocol 41 | s.MemStoreReg(ebpf.W, ebpf.FP, ebpf.R0, -4) // store r0 to a slot on the stack 42 | s.Mov64Reg(ebpf.R2, ebpf.FP) // r2 = fp 43 | s.ALU64Imm(ebpf.ADD, ebpf.R2, -4) // r2 -= 4 44 | s.LoadMapFD(ebpf.R1, arrName) // load pointer to map in r1 45 | s.Call(ebpf.MapLookupElem) // call bpf_map_lookup_elem 46 | s.JumpImm(ebpf.JEQ, ebpf.R0, 0, 2) // if r0 == 0, pc += 2 47 | s.Mov64Imm(ebpf.R1, 1) // r1 = 1 48 | s.AtomicAdd64(ebpf.R0, ebpf.R1, 0) // xadd r0 += R1 49 | s.Mov64Imm(ebpf.R0, retval) // r0 = retval 50 | s.Exit() // return 51 | symtab := &ebpf.SymbolTable{ 52 | Maps: []*ebpf.Map{packetsByProto}, 53 | } 54 | if err := s.Resolve(symtab); err != nil { 55 | t.Fatalf("failed to resolve symbol table: %v", err) 56 | } 57 | prog := &ebpf.Prog{ 58 | Type: ebpf.ProgTypeSocketFilter, 59 | License: "ISC", 60 | StrictAlignment: true, 61 | ObjectName: "snoop_prog", 62 | } 63 | loadLog, err := prog.Load(&s) 64 | if testing.Verbose() { 65 | t.Logf("program load log: %s", loadLog) 66 | } 67 | if err != nil { 68 | t.Fatalf("failed to load program: %v", err) 69 | } 70 | input := make([]byte, offsetofIPHeader-1) 71 | input = append(input, unix.IPPROTO_TCP) 72 | output := make([]byte, 1024) 73 | tr := ebpf.TestRun{ 74 | Input: input, 75 | Output: output, 76 | Repeat: 1, 77 | } 78 | res, err := prog.DoTestRun(tr) 79 | if err != nil { 80 | if ebpf.IsPerm(err) { 81 | t.Skip("no permission to execute test runs") 82 | } 83 | t.Fatalf("test run failed: %v", err) 84 | } 85 | if testing.Verbose() { 86 | t.Logf("length of output = %d", len(res.Output)) 87 | t.Logf("duration = %dns", res.Duration) 88 | } 89 | if res.ReturnValue != retval { 90 | t.Errorf("got return value %d, want %d", res.ReturnValue, retval) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /u64ptr_32.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // +build 386 16 | 17 | // TODO(acln): add the complete set of 32 bit GOARCHes for GOOS=linux 18 | 19 | // TODO(acln): investigate what happens to this code on big endian CPUs 20 | 21 | package ebpf 22 | 23 | import "unsafe" 24 | 25 | type u64ptr struct { 26 | p unsafe.Pointer 27 | _ uint32 28 | } 29 | -------------------------------------------------------------------------------- /u64ptr_64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Andrei Tudor Călin 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // +build amd64 16 | 17 | // TODO(acln): add the complete set of 64 bit GOARCHes for GOOS=linux 18 | 19 | package ebpf 20 | 21 | import "unsafe" 22 | 23 | type u64ptr struct { 24 | p unsafe.Pointer 25 | } 26 | --------------------------------------------------------------------------------