mimic

package module
v0.0.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2022 License: MIT Imports: 30 Imported by: 3

README

Mimic (eBPF userspace emulator)

Go Reference

Mimic is a eBPF virtual machine and emulator which runs in userspace. Mimic attempts to 'mimic' the eBPF machinery we find in the Linux kernel, as well as other possible implementation/environments.

Goals / use cases

Mimics main purpose is to serve as a backend for the edb eBPF debugger, which is needed since we can't interrupt the kernel and "step" through its execution like we can in userspace.

Secondly, running eBPF programs typically requires Linux, so developing on a machine which itself doesn't have eBPF support makes testing difficult. Since Mimic is written in Go, developers should be able to run eBPF programs on their own machine.

Third, Mimic might be useful for embedding external/untrusted programs in Go applications, or as a plugin system. This is what eBPF is designed to do after all, except in the Linux kernel. It should be noted that this is not a JIT-ed implementation, so it is not as fast as native code. But perhaps it can compete with WASM, JS and LUA

Getting started

Just like running eBPF in a Linux kernel, we need to do a few steps to setup, most of which will be familiar.

// Create a new Linux emulator, which handles some Linux specific eBPF features like maps.
emu := &mimic.LinuxEmulator{}
// Create a VM, which can spawn multiple processes, we pass the emulator to the VM, the VM will call the emulator
// at certain points, namely when loading programs and for helper functions.
vm := mimic.NewVM(mimic.VMOptEmulator(emu))

// Parse an ELF file containing programs and maps
coll, err := ebpf.LoadCollectionSpec("./example-ebpf-elf-file")
if err != nil {
    panic(err)
}

// Get a map specification from the collection(this is just information about the map type)
exampleMapSpec := coll.Maps["example_map"]
// Get an emulated map based on the spec(which can actually store data)
exampleMap, err := mimic.MapSpecToLinuxMap(exampleMapSpec)
if err != nil {
    panic(err)
}

// Register the emulated map with the emulator.
err = emu.AddMap(exampleMap)
if err != nil {
    panic(err)
}

// Note: After registering the map, it will be initialized, you can now modify it.

// Get and load the program specification.
prog := coll.Programs["example_prog"]
progID, err := vm.AddProgram(prog)
if err != nil {
    panic(err)
}

// Open the program context file.
f, err := os.Open("./example.ctx.json")
if err != nil {
    panic(err)
}

// Unmarshal the JSON into an actual context object, more about what a context is and its JSON format in the
// "Context" readme section.
ctx, err := mimic.UnmarshalContextJSON(f)
if err != nil {
    panic(err)
}

// Start a new process by providing the unique ID of the loaded program and a context. This links `ctx` to the process
// until p is cleaned up and can't be used by other processes.
p, err := vm.NewProcess(progID, ctx)
if err != nil {
    panic(err)
}

// Run the instance of the program. Alternatively, one can manually execute each program instruction via the p.Step()
// function.
err = p.Run(context.Background())

// Note: after running a process, one can inspect maps, memory, registers and the context to get the program results

// Cleanup the process, this frees memory associated with the process, p should not be stored or used after calling
// cleanup. The `ctx` that was attached is also cleaned up and can now be re-used.
err = p.Cleanup()

VM vs Emulator

eBPF is no longer Linux specific(eBPF for windows). Now the eBPF instruction set is well defined as must be constant across all platforms(perhaps with the exception of legacy cBPF instructions for packet access). However, the Linux eBPF subsystem makes use of a lot of maps, some if which are generic, but most of which are very specialized and aimed at Linux specific features like cGroups or LSM.

To be able to support all use cases and emulator multiple platforms/environments, Mimic splits functionality into two parts(reals). The "Virtual Machine" which is responsible for all generic features(Instructions, Registers, Memory, Processes). The "Emulator" is interchangeable and provides platform/environment specific features(Helper functions/Maps/Some other construct), besides a Linux emulator we can have a Windows emulator and users of Mimic might even implement their own emulator with custom helper functions specific to their needs.

Thread safety / concurrent execution

Mimic considers two different levels of safety and race-conditions. The first is race-conditions in Go-land, meaning in the VM or emulator itself, cases like concurrent access to maps. The second level is in eBPF-land, which would be concurrent reads and writes to the same map key. The emulator allocates memory in the form of []byte which will not change in size during their lifetime, therefor race-conditions in the contents of these allocations are considered "safe" from a emulator perspective. This is very much like the behavior of the Linux kernel itself.

So the vm/emulator is thread safe, but the eBPF programs not necessary, running multiple processes concurrently might give cause race-conditions in the eBPF programs, eBPF programs are responsible for their own race-conditions and must guard against them using eBPF mechanism like per-CPU maps and emulated spin locks.

Contexts

When the Linux kernel invokes a eBPF program it will pass it arguments also called a context. The type of the context depends on the program type and even where it is attached. An XDP program will get a *xdp_md passed while a socket program will get a *sk_buff. So when we start a eBPF program we also need to pass in a context.

Just passing in some arguments is enough for some programs, but more complex programs often make extensive use of helper functions such as bpf_get_current_pid_tgid or bpf_probe_read which return memory from the kernel. Therefor the context also contains data which will be used to return environment data to the eBPF calls.

Any Go struct which implements mimic.Context can be used as a context. The mimic.UnmarshalContextJSON function can be used to unmarshal JSON files into the contexts types provided by mimic. The mimic.RegisterContextUnmarshaller function can be used to register additional context types, so all context parsing can work via a single function.

Generic context

The goal of the generic context type is to provide a context which should cover all situations. It enabled users to craft any data structure. This makes it also very verbose, which is why for some program types, more specialized contexts types are implemented.

{
    "name": "{name of the context}",
    "type": "generic",
    "ctx": {
        "registers": {
            "r1": "{name of a memory section}",
            "r2": "{name of a memory section}",
            "r3": "{name of a memory section}",
            "r4": "{name of a memory section}",
            "r5": "{name of a memory section}"
        },
        "memory": [
            {
                "name": "{Name of this piece of memory}",
                "type": "{block|ptr|struct|int}",
                "value": "{type dependant value}"
            },
            {
                "name": "{block of memory is just a collection of bytes}",
                "type": "block",
                "value": {
                    "value": "{base64 encoded string}",
                    "byteorder": "{optional byte order(big-endian|be|little-endian|le)}"
                }
            },
            {
                "name": "{pointer points at an offset within another memory section}",
                "type": "ptr",
                "value": {
                    "memory": "{name of the memory block we point to}",
                    "offset": 20,
                    "size": 32
                }
            },
            {
                "name": "{struct is a memory structure}",
                "type": "struct",
                "value": [
                    {
                        "name": "{field name}",
                        "memory": "{name of other memory}"
                    },
                    {
                        "name": "{field name}",
                        "memory": "{name of other memory}"
                    }
                ]
            },
            {
                "name": "{Name of this integer}",
                "type": "int",
                "value": {
                    "value": 123,
                    "size": 8
                }
            }
        ],
        "emulator": {
            "key": "this can be anything. The schema will be determined by the specific emulator",
            "other-key": "these k/v pairs are not loaded into the VM's memory, but can reference it"
        }
    }
}
XDP_MD
sk_buff
Captured context

A captured context is a wrapper around another context type to add "captured" helper data to it. The idea being that it is impossible to emulate a number of helper calls. Take bpf_get_current_comm for example, which contains the name of the current executable which would have to be supplied by the user, it is not something we can emulate. The captured context allows users to supply the result of helper calls via the context. The primary use case for this is to reply captured helper call responses from actual transactions.

This is an example of a captured context:

{
    "name": "0 (2022-03-15 21:08:31.25219138 +0100 CET m=+7.724144727)",
    "type": "captured",
    "ctx": {
      "subContext": {
        "name": "0 (2022-03-15 21:08:31.25219138 +0100 CET m=+7.724144727)",
        "type": "generic",
        "ctx": {
          "registers": {},
          "memory": null,
          "emulator": {}
        }
      },
      "helperCalls": {
        "16": [
          {
            "helperFn": 16,
            "params": [
              {
                "reg": 1,
                "value": 18446656970732715112
              },
              {
                "reg": 2,
                "value": 16
              }
            ],
            "results": [
              {
                "reg": 0,
                "value": 0
              },
              {
                "reg": 1,
                "value": "cGhwLWZwbQAAAAAAAAAAAA=="
              }
            ]
          }
        ],
        "6": [
          {
            "helperFn": 6,
            "params": [
              {
                "reg": 1,
                "value": 18446613223656132368
              },
              {
                "reg": 2,
                "value": 3
              },
              {
                "reg": 3,
                "value": 18446656970732715112
              }
            ],
            "results": [
              {
                "reg": 0,
                "value": 7
              }
            ]
          }
        ]
      }
    }
  }

Features / TODO

eBPF is complex, the Linux kernel is complex, thus so is an emulator. This is a list of features which we might want to add but haven't yet. Contributions are welcome.

Note: Marked list items were on the list, but have since been implemented.

Project quality

  • Linting
  • CI
  • Decent unit test coverage
  • Comments on all exported structs for GoDoc generation
  • Usage examples
  • Linux selftest mirroring (if we execute the linux eBPF selftests and get a positive result, can can conclude that the implementation matches)
  • Benchmarking

Generic features

  • Process schedulers
    • Per-CPU scheduler. Running eBPF processes on a worker pool no larger than the amount of logical CPU's. Theoretically the fastest for CPU heavy workloads.
    • Naive scheduling. Running each process in a Goroutine, mostly useful for programs which don't rely on Per-CPU maps and are I/O heavy or blocking.
  • sk_buff context. The Linux sk_buff structure is very complex, and would be hard to craft with the generic context. Making a purpose built context type for it seems logical.

eBPF Instructions

  • Atomic add
  • Atomic and
  • Atomic or
  • Atomic xor
  • Atomic exchange
  • Atomic compare and exchange
  • Packet access

Mechanisms

  • BPF-to-BPF function calls
  • Tailcalling (Switching of the current program)

Linux helper functions

helper Emulated Captured context replay commend
bpf_lookup_element
bpf_lookup_element
bpf_update_element
bpf_delete_element
bpf_probe_read N/A
bpf_ktime_get_ns
bpf_trace_printk TODO add trace output
bpf_get_prandom_u32
bpf_get_smp_processor_id
bpf_skb_store_bytes
bpf_l3_csum_replace Replay returns status code, but doesn't modify sk_buff
bpf_l4_csum_replace Replay returns status code, but doesn't modify sk_buff
bpf_tail_call N/A
bpf_clone_redirect Replay returns status code, but doesn't store redirect info
bpf_get_current_pid_tgid N/A
bpf_get_current_uid_gid N/A
bpf_get_current_comm N/A
bpf_get_cgroup_classid N/A
bpf_skb_vlan_push
bpf_skb_vlan_pop
bpf_skb_get_tunnel_key N/A
bpf_skb_set_tunnel_key N/A
bpf_perf_event_read N/A
bpf_redirect Replay returns status code, but doesn't store redirect info
bpf_get_route_realm N/A
bpf_perf_event_output N/A
bpf_skb_load_bytes
bpf_get_stackid
bpf_csum_diff
bpf_skb_get_tunnel_opt
bpf_skb_set_tunnel_opt Replay returns status code, but doesn't modify sk_buff
bpf_skb_change_proto N/A
bpf_skb_change_type N/A
bpf_skb_under_cgroup N/A
bpf_get_hash_recalc
bpf_get_current_task
bpf_probe_write_user
bpf_current_task_under_cgroup N/A
bpf_skb_change_tail N/A
bpf_skb_pull_data N/A
bpf_csum_update N/A
bpf_set_hash_invalid N/A
bpf_get_numa_node_id N/A
bpf_skb_change_head N/A
bpf_xdp_adjust_head N/A
bpf_probe_read_str N/A
bpf_get_socket_cookie N/A
bpf_get_socket_uid N/A
bpf_set_hash N/A
bpf_setsockopt N/A
bpf_skb_adjust_room N/A
bpf_redirect_map Replay returns status code, but doesn't store redirect info
bpf_sk_redirect_map Replay returns status code, but doesn't store redirect info
bpf_sock_map_update N/A
bpf_xdp_adjust_meta N/A
bpf_perf_event_read_value N/A
bpf_perf_prog_read_value N/A
bpf_getsockopt
bpf_override_return
bpf_sock_ops_cb_flags_set N/A
bpf_msg_redirect_map Replay returns status code, but doesn't store redirect info
bpf_msg_apply_bytes N/A
bpf_msg_cork_bytes N/A
bpf_msg_pull_data N/A
bpf_bind N/A
bpf_xdp_adjust_tail N/A
bpf_skb_get_xfrm_state N/A
bpf_get_stack N/A
bpf_skb_load_bytes_relative
bpf_fib_lookup
bpf_sock_hash_update N/A
bpf_msg_redirect_hash Replay returns status code, but doesn't store redirect info
bpf_sk_redirect_hash Replay returns status code, but doesn't store redirect info
bpf_lwt_push_encap N/A
bpf_lwt_seg6_store_bytes N/A
bpf_lwt_seg6_adjust_srh N/A
bpf_lwt_seg6_action N/A
bpf_rc_repeat
bpf_rc_keydown
bpf_skb_cgroup_id N/A
bpf_get_current_cgroup_id N/A
bpf_get_local_storage N/A
bpf_sk_select_reuseport N/A
bpf_skb_ancestor_cgroup_id
bpf_sk_lookup_tcp
bpf_sk_lookup_udp
bpf_sk_release
bpf_map_push_elem
bpf_map_pop_elem
bpf_map_peek_elem
bpf_msg_push_data
bpf_msg_pop_data
bpf_rc_pointer_rel
bpf_spin_lock N/A
bpf_spin_unlock N/A
bpf_sk_fullsock
bpf_tcp_sock
bpf_skb_ecn_set_ce
bpf_get_listener_sock
bpf_skc_lookup_tcp
bpf_tcp_check_syncookie
bpf_sysctl_get_name
bpf_sysctl_get_current_value
bpf_sysctl_get_new_value
bpf_sysctl_set_new_value Replay returns status code, but doesn't update sysctl
bpf_strtol
bpf_strtoul
bpf_sk_storage_get N/A
bpf_sk_storage_delete N/A
bpf_send_signal
bpf_tcp_gen_syncookie N/A
bpf_skb_output N/A
bpf_probe_read_user N/A
bpf_probe_read_kernel N/A
bpf_probe_read_user_str N/A
bpf_probe_read_kernel_str N/A
bpf_tcp_send_ack
bpf_send_signal_thread
bpf_jiffies64
bpf_read_branch_records N/A
bpf_get_ns_current_pid_tgid N/A
bpf_xdp_output
bpf_get_netns_cookie N/A
bpf_get_current_ancestor_cgroup_id N/A
bpf_sk_assign N/A
bpf_ktime_get_boot_ns
bpf_seq_printf
bpf_seq_write
bpf_sk_cgroup_id N/A
bpf_sk_ancestor_cgroup_id N/A
bpf_ringbuf_output N/A
bpf_ringbuf_reserve N/A
bpf_ringbuf_submit N/A
bpf_ringbuf_discard N/A
bpf_ringbuf_query N/A
bpf_csum_level N/A
bpf_skc_to_tcp6_sock
bpf_skc_to_tcp_sock
bpf_skc_to_tcp_timewait_sock
bpf_skc_to_tcp_request_sock
bpf_skc_to_udp6_sock
bpf_get_task_stack
bpf_load_hdr_opt
bpf_store_hdr_opt Replay returns status code, but doesn't sk_buff
bpf_reserve_hdr_opt
bpf_inode_storage_get N/A
bpf_inode_storage_delete N/A
bpf_d_path
bpf_copy_from_user
bpf_snprintf_btf
bpf_seq_printf_btf
bpf_skb_cgroup_classid N/A
bpf_redirect_neigh N/A Replay returns status code, but doesn't store redirect info
bpf_per_cpu_ptr
bpf_this_cpu_ptr
bpf_redirect_peer eplay returns status code, but doesn't store redirect info
bpf_task_storage_get N/A
bpf_task_storage_delete N/A
bpf_get_current_task_btf
bpf_bprm_opts_set
bpf_ktime_get_coarse_ns
bpf_ima_inode_hash
bpf_sock_from_file
bpf_check_mtu
bpf_for_each_map_elem N/A
bpf_snprintf
bpf_sys_bpf
bpf_btf_find_by_name_kind
bpf_sys_close
bpf_timer_init N/A
bpf_timer_set_callback N/A
bpf_timer_start N/A
bpf_timer_cancel N/A
bpf_get_func_ip
bpf_get_attach_cookie
bpf_task_pt_regs
bpf_get_branch_snapshot
bpf_trace_vprintk
bpf_skc_to_unix_sock
bpf_kallsyms_lookup_name
bpf_find_vma
bpf_loop
bpf_strncmp
bpf_get_func_arg
bpf_get_func_ret
bpf_get_func_arg_cnt
bpf_get_retval
bpf_set_retval

Linux maps

  • BPF_MAP_TYPE_HASH
  • BPF_MAP_TYPE_ARRAY
  • BPF_MAP_TYPE_PROG_ARRAY (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_PERF_EVENT_ARRAY
  • BPF_MAP_TYPE_PERCPU_HASH
  • BPF_MAP_TYPE_PERCPU_ARRAY
  • BPF_MAP_TYPE_STACK_TRACE
  • BPF_MAP_TYPE_CGROUP_ARRAY (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_LRU_HASH
  • BPF_MAP_TYPE_LRU_PERCPU_HASH
  • BPF_MAP_TYPE_LPM_TRIE
  • BPF_MAP_TYPE_ARRAY_OF_MAPS (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_HASH_OF_MAPS (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_DEVMAP (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_SOCKMAP (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_CPUMAP (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_XSKMAP (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_SOCKHASH (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_CGROUP_STORAGE (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_REUSEPORT_SOCKARRAY (covered by BPF_MAP_TYPE_ARRAY)
  • BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE (covered by BPF_MAP_TYPE_PERCPU_HASH)
  • BPF_MAP_TYPE_QUEUE
  • BPF_MAP_TYPE_STACK
  • BPF_MAP_TYPE_SK_STORAGE (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_DEVMAP_HASH (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_STRUCT_OPS (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_RINGBUF
  • BPF_MAP_TYPE_INODE_STORAGE (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_TASK_STORAGE (covered by BPF_MAP_TYPE_HASH)
  • BPF_MAP_TYPE_BLOOM_FILTER

Misc

  • (optional) real map backing. The idea being that instead of emulating a map, we can use actual BPF maps of the host. It may not be able to simulate all features, disabling syscall writes on a map would also block access for the eBPF program in this case. But baring some limitations, this could allow someone to share a map between the emulator and a loader program, or a real and emulated program.
  • emulator helper customization. Allowing users to change or extend the existing Linux emulator. For example to replace the bpf_probe_read callback so the Host program can return its own custom memory objects, thus being able integrate the VM/Emulator with the rest of the host application.
  • high-security settings on the vm. There are moments when we reuse memory(deleting map keys, tailcalls, bpf-to-bpf calls), but don't zero it out before reuse. There might be use cases, especially when running foreign code in a multi tenant environment where this might lead to security issues. Zeroing out memory can be costly, especially if the security use-case doesn't apply, so being able to enable or disable this feature with a VM setting would be nice. (Note: we currently don't zero-memory at all). (Note2: zeroing out memory should also enforce correctness for programs)

Documentation

Index

Constants

View Source
const (
	AF_UNSPEC     = 0x0
	BPF_TCP_CLOSE = 0x7
	AF_INET       = 0x2
	AF_INET6      = 0xa
)

Define unix constants here, since we can't import sys/unix on windows

Variables

This section is empty.

Functions

func GetNativeEndianness

func GetNativeEndianness() binary.ByteOrder

GetNativeEndianness returns the binary.ByteOrder matching the endianess of the current machine

func RegisterContextUnmarshaller

func RegisterContextUnmarshaller(ctxType string, fn ContextUnmarshaller)

RegisterContextUnmarshaller is used to register custom context type unmarshallers which will be invoked by UnmarshalContextJSON if any context is passed in with "type" set to `ctxType`.

Types

type CapturedContext added in v0.0.6

type CapturedContext struct {
	Sub         Context                                `json:"-"`
	RawSub      json.RawMessage                        `json:"subContext"`
	HelperCalls map[string][]CapturedContextHelperCall `json:"helperCalls"`
}

CapturedContext wraps another context and adds captured helper call data to it which the LinuxEmulator can use to replay helper calls instead of actually emulating them, or doing both in some cases.

func (*CapturedContext) Cleanup added in v0.0.6

func (cc *CapturedContext) Cleanup(process *Process) error

Cleanup cleans up the allocated memory of the sub-context

func (*CapturedContext) GetName added in v0.0.6

func (cc *CapturedContext) GetName() string

GetName returns the name of the context

func (*CapturedContext) Load added in v0.0.6

func (cc *CapturedContext) Load(process *Process) error

Load loads the sub-context into memory

func (*CapturedContext) MarshalJSON added in v0.0.6

func (cc *CapturedContext) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler

func (*CapturedContext) SetName added in v0.0.6

func (cc *CapturedContext) SetName(name string)

SetName sets the name of the context

type CapturedContextHelperCall added in v0.0.6

type CapturedContextHelperCall struct {
	HelperFn asm.BuiltinFunc               `json:"helperFn"`
	Params   []CapturedContextRegisterData `json:"params"`
	Result   []CapturedContextRegisterData `json:"results"`
}

CapturedContextHelperCall describes a single call to a helper function

type CapturedContextRegisterData added in v0.0.6

type CapturedContextRegisterData struct {
	Reg    asm.Register    `json:"reg"`
	Value  json.RawMessage `json:"value"`
	Scalar uint64          `json:"-"`
	Data   []byte          `json:"-"`
}

CapturedContextRegisterData describes the contexts of a register

func (*CapturedContextRegisterData) MarshalJSON added in v0.0.6

func (crd *CapturedContextRegisterData) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler

func (*CapturedContextRegisterData) UnmarshalJSON added in v0.0.6

func (crd *CapturedContextRegisterData) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler

type Context

type Context interface {
	SetName(string)
	GetName() string
	// Load is called when a new process is started, the context is expected to construct a memory object to be passed
	// to the program, register it in the memory controller and set initial argument registers(R1-R5)
	Load(process *Process) error
	// Cleanup is called when a process exists, the Context is expected to delete all its memory from the memory
	// controller.
	Cleanup(process *Process) error
}

Context describes a object which provides all information needed to emulate a specific eBPF environment. A context contains both the initial arguments(R1-R5) of a eBPF program as well as data which can be used by the emulator in helper functions.

func UnmarshalContextJSON

func UnmarshalContextJSON(r io.Reader) (Context, error)

UnmarshalContextJSON attempts to unmarshal json into a Context. This function expects the top level element to be a object with "name" and "type" string fields where "type" is a unique name of the context type. The "ctx" field should contain the context type specific object.

Custom context types can also be decoded, additional unmarshallers can be added with the RegisterContextUnmarshaller function.

type ContextUnmarshaller

type ContextUnmarshaller func(name string, ctx json.RawMessage) (Context, error)

ContextUnmarshaller unmarshals JSON into a specific context

type Emulator

type Emulator interface {
	// SetVM is called when an emulator is linked to a VM, this allows the emulator to save a reference
	// so it can access information about the VM like the memory controller.
	SetVM(vm *VM)
	// CallHelperFunction is called when a process executes a call to a helper function. The emulator must make sure
	// that this call is thread-safe in go-land, meaning that we should not allow raceconditions in the vm/emulator.
	// However, eBPF programs are themselves responsible for race-conditions in VM memory.
	//
	// If this function returns an error, a un-graceful error is assumed which will abort further execution of the
	// program and forwards the error to the process callee. Helper functions can also define graceful errors, which
	// can be returned to the calling program by setting the R0 register for example.
	CallHelperFunction(helperNr int32, p *Process) error
	// CustomInstruction is called when a process encounters an instruction that is not implemented by the VM. This
	// allows the emulator to implement custom eBPF CPU instructions as long as the opcode is not already in use.
	CustomInstruction(inst asm.Instruction, process *Process) error
	// RewriteProgram is called when a program is loaded into the VM. At this point the emulator may rewrite parts
	// of the program with emulator specific references, map addresses for example. If an error is returned the program
	// loading is halted.
	RewriteProgram(program *ebpf.ProgramSpec) error
}

Emulator describes a struct which implements eBPF features which are specific to a certain environment.

type FlowKeys added in v0.0.5

type FlowKeys struct {
	Nhoff uint16 `json:"nhoff"`
	Thoff uint16 `json:"thoff"`
	// ETH_P_* of valid addrs
	AddrProto     uint16 `json:"addrProto"`
	IsFrag        uint8  `json:"isFrag"`
	IsFirstFrag   uint8  `json:"isFirstFrag"`
	IsEncap       uint8  `json:"isEncap"`
	IPProto       uint8  `json:"ipProto"`
	NProto        uint16 `json:"nProto"`
	Sport         uint16 `json:"sport"`
	Dport         uint16 `json:"dport"`
	SrcIPv6orIPv4 net.IP `json:"ip"`
	Flags         uint32 `json:"flags"`
	FlowLabel     uint32 `json:"flowLabel"`
}

FlowKeys describe the flow information of a sk_buff

func (*FlowKeys) Load added in v0.0.5

func (fk *FlowKeys) Load(offset uint32, size asm.Size) (uint64, error)

Load reads a single integer value of 1, 2, 4 or 8 bytes at a specific offset

func (*FlowKeys) Read added in v0.0.5

func (fk *FlowKeys) Read(offset uint32, b []byte) error

Read reads a byte slice of arbitrary size, the length of 'b' is used to determine the requested size

func (*FlowKeys) Size added in v0.0.5

func (fk *FlowKeys) Size() int

Size returns the size of a flowKeys struct in bytes

func (*FlowKeys) Store added in v0.0.5

func (fk *FlowKeys) Store(offset uint32, value uint64, size asm.Size) error

Store write a single interger value of 1, 2, 4 or 8 bytes to a specific offset

func (*FlowKeys) Write added in v0.0.5

func (fk *FlowKeys) Write(offset uint32, b []byte) error

Write write a byte slice of arbitrary size to the memory

type GenericContext

type GenericContext struct {
	Name        string                     `json:"-"`
	Registers   GenericContextRegisters    `json:"registers"`
	Memory      []GenericContextMemory     `json:"memory"`
	EmulatorRaw map[string]json.RawMessage `json:"emulator"`
	Emulator    map[string]interface{}     `json:"-"`
	// contains filtered or unexported fields
}

GenericContext implements mimic.Context. The goal of GenericContext and its GenericContext... struct types is to provide a method to construct any type of eBPF context. Being generic also means we have to be very verbose which is not always desirable, it is recommended to use specific contexts whenever possible, but this type can always be used as a fallback in situations where no specific context type exists.

func (*GenericContext) Cleanup

func (c *GenericContext) Cleanup(process *Process) error

Cleanup cleans up the context, the process call this function the Process.Cleanup is called, users should not have to manually call this function. Cleaning up the context will remove the associated memory from the processes memory controller and make the context ready to be re-used/re-loaded.

func (*GenericContext) GetName

func (c *GenericContext) GetName() string

GetName returns the name of the context

func (*GenericContext) Load

func (c *GenericContext) Load(process *Process) error

Load loads the context into a process. Load is called by the VM when creating a new process, users don't have to call this function manually. Loading a context into a process will register memory for the context at the memory controller of the VM.

func (*GenericContext) MarshalJSON

func (c *GenericContext) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler

func (*GenericContext) SetName added in v0.0.6

func (c *GenericContext) SetName(name string)

SetName sets the name of the context

type GenericContextInt

type GenericContextInt struct {
	Value int64 `json:"value"`
	Size  int   `json:"size"`
}

GenericContextInt is a memory object describing a interger value of a specific bit size(8, 16, 32, or 64).

func (*GenericContextInt) GetValue

func (i *GenericContextInt) GetValue() ([]byte, error)

GetValue returns the bytes for the given integer in the native byte order.

type GenericContextMemory

type GenericContextMemory struct {
	Name     string          `json:"name"`
	Type     string          `json:"type"`
	RawValue json.RawMessage `json:"value"`

	Block   *GenericContextMemoryBlock `json:"-"`
	Pointer *GenericContextPointer     `json:"-"`
	Struct  *GenericContextStruct      `json:"-"`
	Int     *GenericContextInt         `json:"-"`
}

GenericContextMemory represents a named memory object in the generic context, which can be one of multiple actual different memory types. The name for each type within a generic context should be unique.

func (*GenericContextMemory) MarshalJSON

func (m *GenericContextMemory) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler

func (*GenericContextMemory) UnmarshalJSON

func (m *GenericContextMemory) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler

type GenericContextMemoryBlock

type GenericContextMemoryBlock struct {
	Value     []byte
	ByteOrder binary.ByteOrder
	// contains filtered or unexported fields
}

GenericContextMemoryBlock is a block of memory, the `Value` will be loaded into the memory controller as a PlainMemory object.

func (*GenericContextMemoryBlock) Cleanup

func (m *GenericContextMemoryBlock) Cleanup(p *Process) error

Cleanup will remove the memory block from the memory controller of the given process.

func (*GenericContextMemoryBlock) GetAddr

GetAddr returns the virtual address of block of memory within the process. If the block is not yet loaded, calling this function will cause the load. If the block was already loaded, the existing memory address is returned.

func (*GenericContextMemoryBlock) MarshalJSON

func (m *GenericContextMemoryBlock) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler

func (*GenericContextMemoryBlock) UnmarshalJSON

func (m *GenericContextMemoryBlock) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler

type GenericContextPointer

type GenericContextPointer struct {
	Memory string `json:"memory"`
	Offset int    `json:"offset"`
	Size   int    `json:"size"`
}

GenericContextPointer is a pointer to other memory defined in GenericContext.Memory. Offset is the offset from the start of the memory block in bytes and Size is the size of the pointer in bits(32 or 64). Pointers can only point to "block" and "struct" memory objects.

func (*GenericContextPointer) GetValue

func (ptr *GenericContextPointer) GetValue(p *Process, g *GenericContext) (uint32, error)

GetValue returns the value of the pointer(the address of the memory we point to plus the allocation)

type GenericContextRegisters

type GenericContextRegisters struct {
	R1 string `json:"r1,omitempty"`
	R2 string `json:"r2,omitempty"`
	R3 string `json:"r3,omitempty"`
	R4 string `json:"r4,omitempty"`
	R5 string `json:"r5,omitempty"`
}

GenericContextRegisters are registers which the generic context can set when loading

type GenericContextStruct

type GenericContextStruct struct {
	Fields []GenericContextStructField
	// contains filtered or unexported fields
}

GenericContextStruct is a memory object which describes a memory structure which can be translated into a PlainMemory object and loaded into the memory controller of a process.

func (*GenericContextStruct) Cleanup

func (s *GenericContextStruct) Cleanup(p *Process) error

Cleanup removes the structure from the processes memory controller

func (*GenericContextStruct) GetAddr

GetAddr returns the virtual address of the structure. The call to this function will cause the struct to be registered with the memory controller of the process, subsequent calls will return the same address.

func (*GenericContextStruct) MarshalJSON

func (s *GenericContextStruct) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler

func (*GenericContextStruct) UnmarshalJSON

func (s *GenericContextStruct) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler

type GenericContextStructField

type GenericContextStructField struct {
	Name   string `json:"name"`
	Memory string `json:"memory"`
}

GenericContextStructField describes a field of GenericContextStruct

type HelperFunction

type HelperFunction func(p *Process) error

HelperFunction is a function defined in Go which can be invoked by the eBPF VM via the Call instruction. The emulator has a lookup map which maps a particular number to a helper function. Helper functions are used as a interface between the sandboxed eBPF VM and the Emulator/Host/Outside world. Helper functions are responsible for security checks and should implement policies or other forms of limitations to guarantee that eBPF code can't be used for malicious purposes.

Helper functions are called with eBPF calling convention, meaning R1-R5 are arguments, and R0 is the return value. Errors returned by the helper are considered fatal for the process, are passed to the process and will result in it aborting. Recoverable errors/graceful errors should be returned to the VM via the R0 register and a helper func specific contract.

type LinuxArrayMap

type LinuxArrayMap struct {
	Spec *ebpf.MapSpec
	// contains filtered or unexported fields
}

LinuxArrayMap is the emulated version of ebpf.Array / BPF_MAP_TYPE_ARRAY. Array maps have 4 byte integer keys from 0 to Spec.MaxEntries with arbitrary values

func (*LinuxArrayMap) GetSpec

func (m *LinuxArrayMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxArrayMap) Indices added in v0.0.3

func (m *LinuxArrayMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxArrayMap) Init

func (m *LinuxArrayMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxArrayMap) Keys

func (m *LinuxArrayMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxArrayMap) Load added in v0.0.10

func (m *LinuxArrayMap) Load(offset uint32, size asm.Size) (uint64, error)

Load reads a single integer value of 1, 2, 4 or 8 bytes at a specific offset

func (*LinuxArrayMap) Lookup

func (m *LinuxArrayMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxArrayMap) Read added in v0.0.10

func (m *LinuxArrayMap) Read(offset uint32, b []byte) error

Read reads a byte slice of arbitrary size, the length of 'b' is used to determine the requested size

func (*LinuxArrayMap) Store added in v0.0.10

func (m *LinuxArrayMap) Store(offset uint32, value uint64, size asm.Size) error

Store write a single interger value of 1, 2, 4 or 8 bytes to a specific offset

func (*LinuxArrayMap) Update

func (m *LinuxArrayMap) Update(key []byte, value []byte, flags uint32, cpuid int) error

Update updates an existing value in the map, or add a new value if it didn't exist before.

func (*LinuxArrayMap) UpdateObject added in v0.0.3

func (m *LinuxArrayMap) UpdateObject(key []byte, value LinuxMap, flags uint32) error

UpdateObject is similar to Update, accept it take a arbitrary go interface{} as value. The virtual address of the object will be stored in the map. The map must have a value size of 4 bytes and the object must already be registered with the VMs memory controller.

func (*LinuxArrayMap) Write added in v0.0.10

func (m *LinuxArrayMap) Write(offset uint32, b []byte) error

Write write a byte slice of arbitrary size to the memory

type LinuxContextSKBuff added in v0.0.5

type LinuxContextSKBuff struct {
	Name string `json:"-"`

	Packet   []byte    `json:"packet"`
	SK       *SK       `json:"sock"`
	Dev      *NetDev   `json:"dev"`
	FlowKeys *FlowKeys `json:"flowKeys"`
	// contains filtered or unexported fields
}

LinuxContextSKBuff is a context for skb programs

func (*LinuxContextSKBuff) Cleanup added in v0.0.5

func (c *LinuxContextSKBuff) Cleanup(process *Process) error

Cleanup removes the context from the processes memory and makes the context ready to be re-used/re-loaded.

func (*LinuxContextSKBuff) GetName added in v0.0.5

func (c *LinuxContextSKBuff) GetName() string

GetName return the context name

func (*LinuxContextSKBuff) Load added in v0.0.5

func (c *LinuxContextSKBuff) Load(process *Process) error

Load load the context into the memory of the process

func (*LinuxContextSKBuff) SetName added in v0.0.6

func (c *LinuxContextSKBuff) SetName(name string)

SetName sets the name of the context

type LinuxContextXDP

type LinuxContextXDP struct {
	Name string `json:"-"`

	Headroom      int    `json:"headroom"`
	Tailroom      int    `json:"tailroom"`
	Packet        []byte `json:"packet"`
	IngessIfIndex int32  `json:"ingress_ifidx"`
	RxQueueIndex  int32  `json:"rx_queue_idx"`
	EgressIfIndex int32  `json:"egress_ifidx"`
	// contains filtered or unexported fields
}

LinuxContextXDP is a specialized context type for XDP programs.

func (*LinuxContextXDP) Cleanup

func (c *LinuxContextXDP) Cleanup(process *Process) error

Cleanup removes the context from the processes memory and makes the context ready to be re-used/re-loaded.

func (*LinuxContextXDP) GetName

func (c *LinuxContextXDP) GetName() string

GetName return the context name

func (*LinuxContextXDP) Load

func (c *LinuxContextXDP) Load(process *Process) error

Load load the context into the memory of the process

func (*LinuxContextXDP) SetName added in v0.0.6

func (c *LinuxContextXDP) SetName(name string)

SetName set the name of the context

type LinuxEmuProcValKey added in v0.0.2

type LinuxEmuProcValKey int

LinuxEmuProcValKey is a enum values which is used as the key to the Process.EmulatorValues maps when a LinuxEmulator is used.

const (
	// LinuxEmuProcValKeyTailcalls tracks the amount of tailcalls which a process has made.
	LinuxEmuProcValKeyTailcalls LinuxEmuProcValKey = iota
)

func (LinuxEmuProcValKey) String added in v0.0.2

func (v LinuxEmuProcValKey) String() string

type LinuxEmulator

type LinuxEmulator struct {
	Maps map[string]LinuxMap
	// contains filtered or unexported fields
}

LinuxEmulator implements Emulator, and attempts to emulate all Linux specific eBPF features.

func NewLinuxEmulator added in v0.0.2

func NewLinuxEmulator(opts ...LinuxEmulatorOpts) *LinuxEmulator

NewLinuxEmulator create a new LinuxEmulator from the given options.

func (*LinuxEmulator) AddMap

func (le *LinuxEmulator) AddMap(name string, m LinuxMap) error

AddMap adds a map to the emulator.

func (*LinuxEmulator) CallHelperFunction

func (le *LinuxEmulator) CallHelperFunction(helperNr int32, p *Process) error

CallHelperFunction is called by the VM when it wants to execute a helper function.

func (*LinuxEmulator) CustomInstruction added in v0.0.5

func (le *LinuxEmulator) CustomInstruction(inst asm.Instruction, process *Process) error

CustomInstruction is called by the VM when it encounters a unimplemented CPU instruction, giving the emulator a chance to provide an implementation.

func (*LinuxEmulator) RewriteProgram

func (le *LinuxEmulator) RewriteProgram(program *ebpf.ProgramSpec) error

RewriteProgram is called by the VM when adding a program to it. It allows us to rewrite program instructions. In this case we rewrite map load instructions to have the virtual addresess of the map to which the refer.

func (*LinuxEmulator) SetVM

func (le *LinuxEmulator) SetVM(vm *VM)

SetVM is called by VM when attaching the emulator to the VM, it allows the emulator to store a reference to the VM to which is is attached.

type LinuxEmulatorOpts added in v0.0.2

type LinuxEmulatorOpts func(*LinuxEmulatorSettings)

LinuxEmulatorOpts are options which can be passed to NewLinuxEmulator to modify the default settings.

func OptMaxTailCalls added in v0.0.2

func OptMaxTailCalls(max int) LinuxEmulatorOpts

OptMaxTailCalls is a option to change the max amount of tailcalls a process is allowed to make

func OptRngSeed added in v0.0.3

func OptRngSeed(seed int64) LinuxEmulatorOpts

OptRngSeed sets the seed for the random number generator used for bpf_get_prandom_u32.

type LinuxEmulatorSettings added in v0.0.2

type LinuxEmulatorSettings struct {
	// The maximum amount of tailcalls a process can make, a security feature in the Linux kernel to avoid
	// infinite tailcall loops.
	MaxTailCalls int

	// The seed used by the pseudo random number generator for the bpf_get_prandom_u32 function.
	// Is set to the current nanoseconds since system boot, but can be set to a custom value to make the the emulator
	// predictable.
	RandomSeed int64

	// The boot time of the emulator, is the boot time of the host by default, can be set so behavior is predictable.
	// Value is used to calculate time since boot for bpf_ktime_get_ns helper function.
	TimeOfBoot time.Time

	// The size of the perf event ring buffer per cpu. When allocating memory for a MAP_TYPE_PERF_EVENT_ARRAY, this
	// is the number of bytes per cpu reserved. This value is always rounded up to the nearest multiple of the
	// systems page size.
	PerfEventBufferSize int
}

LinuxEmulatorSettings are settings used by LinuxEmulator, which can be updated by

type LinuxHashMap

type LinuxHashMap struct {
	Spec *ebpf.MapSpec

	KeyToIndex map[[sha256.Size]byte]int
	// contains filtered or unexported fields
}

LinuxHashMap is the emulated version of ebpf.Hash / BPF_MAP_TYPE_HASH. Hash maps have arbitrary keys and values.

func (*LinuxHashMap) Delete

func (m *LinuxHashMap) Delete(key []byte) error

Delete deletes a values from the map

func (*LinuxHashMap) GetSpec

func (m *LinuxHashMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxHashMap) Indices added in v0.0.3

func (m *LinuxHashMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxHashMap) Init

func (m *LinuxHashMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxHashMap) Keys

func (m *LinuxHashMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxHashMap) Lookup

func (m *LinuxHashMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxHashMap) Update

func (m *LinuxHashMap) Update(key []byte, value []byte, flags uint32, cpuid int) error

Update updates an existing value in the map, or add a new value if it didn't exist before.

func (*LinuxHashMap) UpdateObject added in v0.0.3

func (m *LinuxHashMap) UpdateObject(key []byte, value LinuxMap, flags uint32) error

UpdateObject is similar to Update, accept it take a arbitrary go interface{} as value. The virtual address of the object will be stored in the map. The map must have a value size of 4 bytes and the object must already be registered with the VMs memory controller.

type LinuxLRUHashMap

type LinuxLRUHashMap struct {
	Spec *ebpf.MapSpec
	// contains filtered or unexported fields
}

LinuxLRUHashMap is the emulated version of ebpf.LRUHash / BPF_MAP_TYPE_LRU_HASH. This map type is a normal hash map which also records which map values are the Least Recently Used. If the map is full and a new value is added, this map type will discard the Least Recently Used value from the map to make room for the new value instread of returning a "out of memory" error.

func (*LinuxLRUHashMap) Delete

func (m *LinuxLRUHashMap) Delete(key []byte) error

Delete deletes a key from the map.

func (*LinuxLRUHashMap) GetSpec

func (m *LinuxLRUHashMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxLRUHashMap) Indices added in v0.0.3

func (m *LinuxLRUHashMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxLRUHashMap) Init

func (m *LinuxLRUHashMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxLRUHashMap) Keys

func (m *LinuxLRUHashMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxLRUHashMap) Lookup

func (m *LinuxLRUHashMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxLRUHashMap) Update

func (m *LinuxLRUHashMap) Update(key []byte, value []byte, flags uint32, cpuid int) error

Update updates an existing value in the map, or add a new value if it didn't exist before.

type LinuxMap

type LinuxMap interface {
	Init(emulator *LinuxEmulator) error
	GetSpec() ebpf.MapSpec

	// Indices returns the amount of per-cpu indexes.
	Indices() int

	// Keys returns a byte slice containing all key values in their byte representation. The size is always a multiple
	// of the about of entries in the map and the key size.
	Keys(cpuid int) []byte

	// Lookup returns a pointer to a value matching the key, or NULL if no matching value can be found.
	// `key` must be the same length as defined in the map spec
	Lookup(key []byte, cpuid int) (uint32, error)
}

LinuxMap is an interface which describes the common functions each map for the LinuxEmulator has. LinuxMaps are initialized by the LinuxEmulator upon being added to the emulator and are expected to be ready to store data after that. Every map type is expected to be able to return a list of valid keys and for these keys to be retrieved via Lookup, this is so the Host can always inspect the map contents. Other "actions" like modifying the maps are optional and have their own interfaces.

func MapSpecToLinuxMap

func MapSpecToLinuxMap(spec *ebpf.MapSpec) (LinuxMap, error)

MapSpecToLinuxMap translates a map specification to a LinuxMap type which can be used in the LinuxEmulator.

type LinuxMapDeleter

type LinuxMapDeleter interface {
	// Delete takes a key, and removes it from the map
	Delete(key []byte) error
}

LinuxMapDeleter describes a LinuxMap which can delete any value as long as the key is known.

type LinuxMapPopper added in v0.0.3

type LinuxMapPopper interface {
	// Pops a key-less value from a map
	Pop(cpuid int) (uint32, error)
}

LinuxMapPopper describes a LinuxMap from which you can pop values without a key, deleting them from the map.

type LinuxMapPusher added in v0.0.3

type LinuxMapPusher interface {
	// Pushes a key-less value into a map
	Push(value []byte, cpuid int) error
}

LinuxMapPusher describes a LinuxMap into which you can push values without a key.

type LinuxMapUpdater

type LinuxMapUpdater interface {
	// Updates takes a key, value and flags, key and value slices must match the length of the key and value as defined
	// in the map spec. If successful nil is returned, graceful errors are of type syscall.Errno and can be forwarded
	// to the eBPF VM. Other error types are fatal.
	Update(key []byte, value []byte, flags uint32, cpuid int) error
}

LinuxMapUpdater describes a LinuxMap which can update any value as long as the key is known.

type LinuxPerCPUArrayMap added in v0.0.3

type LinuxPerCPUArrayMap struct {
	Spec *ebpf.MapSpec
	// contains filtered or unexported fields
}

LinuxPerCPUArrayMap is the emulated version of ebpf.PerCPUArray / BPF_MAP_TYPE_PERCPU_ARRAY. Array maps have 4 byte integer keys from 0 to Spec.MaxEntries, it value is the address of a map.

func (*LinuxPerCPUArrayMap) GetSpec added in v0.0.3

func (m *LinuxPerCPUArrayMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxPerCPUArrayMap) Indices added in v0.0.3

func (m *LinuxPerCPUArrayMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxPerCPUArrayMap) Init added in v0.0.3

func (m *LinuxPerCPUArrayMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxPerCPUArrayMap) Keys added in v0.0.3

func (m *LinuxPerCPUArrayMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxPerCPUArrayMap) Lookup added in v0.0.3

func (m *LinuxPerCPUArrayMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxPerCPUArrayMap) Update added in v0.0.3

func (m *LinuxPerCPUArrayMap) Update(key []byte, value []byte, flags uint32, cpuid int) error

Update updates an existing value in the map, or add a new value if it didn't exist before.

type LinuxPerCPUHashMap added in v0.0.3

type LinuxPerCPUHashMap struct {
	Spec *ebpf.MapSpec

	KeyToIndex map[[sha256.Size]byte]int
	// contains filtered or unexported fields
}

LinuxPerCPUHashMap is the emulated version of ebpf.PerCPUHash / BPF_MAP_TYPE_PERCPU_HASH. Hash maps have arbitrary keys and values.

func (*LinuxPerCPUHashMap) Delete added in v0.0.3

func (m *LinuxPerCPUHashMap) Delete(key []byte) error

Delete deletes a values from the map

func (*LinuxPerCPUHashMap) GetSpec added in v0.0.3

func (m *LinuxPerCPUHashMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxPerCPUHashMap) Indices added in v0.0.3

func (m *LinuxPerCPUHashMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxPerCPUHashMap) Init added in v0.0.3

func (m *LinuxPerCPUHashMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxPerCPUHashMap) Keys added in v0.0.3

func (m *LinuxPerCPUHashMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxPerCPUHashMap) Lookup added in v0.0.3

func (m *LinuxPerCPUHashMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxPerCPUHashMap) Update added in v0.0.3

func (m *LinuxPerCPUHashMap) Update(key []byte, value []byte, flags uint32, cpuid int) error

Update updates an existing value in the map, or add a new value if it didn't exist before.

func (*LinuxPerCPUHashMap) UpdateObject added in v0.0.3

func (m *LinuxPerCPUHashMap) UpdateObject(key []byte, value LinuxMap, flags uint32) error

UpdateObject is similar to Update, accept it take a arbitrary go interface{} as value. The virtual address of the object will be stored in the map. The map must have a value size of 4 bytes and the object must already be registered with the VMs memory controller.

type LinuxPerfEventArrayMap added in v0.0.3

type LinuxPerfEventArrayMap struct {
	Spec *ebpf.MapSpec
	// contains filtered or unexported fields
}

LinuxPerfEventArrayMap is the emulated version of ebpf.PerfEventArray / BPF_MAP_TYPE_PERF_EVENT_ARRAY. This map type has no keys, the map has no set value size, each element can be of a different size.

func (*LinuxPerfEventArrayMap) GetSpec added in v0.0.3

func (m *LinuxPerfEventArrayMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxPerfEventArrayMap) Indices added in v0.0.3

func (m *LinuxPerfEventArrayMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxPerfEventArrayMap) Init added in v0.0.3

func (m *LinuxPerfEventArrayMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxPerfEventArrayMap) Keys added in v0.0.3

func (m *LinuxPerfEventArrayMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxPerfEventArrayMap) Lookup added in v0.0.3

func (m *LinuxPerfEventArrayMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxPerfEventArrayMap) Pop added in v0.0.3

func (m *LinuxPerfEventArrayMap) Pop(cpuid int) ([]byte, error)

Pop pops a value from the perf event buffer

func (*LinuxPerfEventArrayMap) Push added in v0.0.3

func (m *LinuxPerfEventArrayMap) Push(value []byte, cpuid int) error

Push pushes a new value into the perf event buffer

type LinuxQueueMap added in v0.0.4

type LinuxQueueMap struct {
	Spec *ebpf.MapSpec
	// contains filtered or unexported fields
}

LinuxQueueMap is the emulated version of ebpf.Queue / BPF_MAP_TYPE_QUEUE. This map type has no keys, value sizes are fixed, but is configurable. This map type is a FIFO queue.

func (*LinuxQueueMap) GetSpec added in v0.0.4

func (m *LinuxQueueMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxQueueMap) Indices added in v0.0.4

func (m *LinuxQueueMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxQueueMap) Init added in v0.0.4

func (m *LinuxQueueMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxQueueMap) Keys added in v0.0.4

func (m *LinuxQueueMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxQueueMap) Lookup added in v0.0.4

func (m *LinuxQueueMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxQueueMap) Pop added in v0.0.4

func (m *LinuxQueueMap) Pop(cpuid int) (uint32, error)

Pop pops a value from the ring buffer

func (*LinuxQueueMap) Push added in v0.0.4

func (m *LinuxQueueMap) Push(value []byte, cpuid int) error

Push pushes a new value into the ring buffer

type LinuxStackMap added in v0.0.4

type LinuxStackMap struct {
	Spec *ebpf.MapSpec
	// contains filtered or unexported fields
}

LinuxStackMap is the emulated version of ebpf.PerfEventArray / BPF_MAP_TYPE_PERF_EVENT_ARRAY. Array maps have 4 byte integer keys from 0 to Spec.MaxEntries with arbitrary values

func (*LinuxStackMap) GetSpec added in v0.0.4

func (m *LinuxStackMap) GetSpec() ebpf.MapSpec

GetSpec returns the specification of the map, part of the LinuxMap implementation

func (*LinuxStackMap) Indices added in v0.0.4

func (m *LinuxStackMap) Indices() int

Indices returns the amount of per-cpu indexes.

func (*LinuxStackMap) Init added in v0.0.4

func (m *LinuxStackMap) Init(emulator *LinuxEmulator) error

Init initializes the map, part of the LinuxMap implementation

func (*LinuxStackMap) Keys added in v0.0.4

func (m *LinuxStackMap) Keys(cpuid int) []byte

Keys returns a byte slice which contains all keys in the map, keys are packed, the user is expected to calculate the proper window into the slice based on the size of m.Spec.KeySize.

func (*LinuxStackMap) Lookup added in v0.0.4

func (m *LinuxStackMap) Lookup(key []byte, cpuid int) (uint32, error)

Lookup returns the virtual memory offset to the map value or 0 if no value can be found for the given key.

func (*LinuxStackMap) Pop added in v0.0.4

func (m *LinuxStackMap) Pop(cpuid int) (uint32, error)

Pop pops a value from the ring buffer

func (*LinuxStackMap) Push added in v0.0.4

func (m *LinuxStackMap) Push(value []byte, cpuid int) error

Push pushes a new value into the ring buffer

type MemoryController

type MemoryController struct {
	// contains filtered or unexported fields
}

MemoryController is used to link virtual addresses(uint64 values) to Go objects.

func (*MemoryController) AddEntry

func (mc *MemoryController) AddEntry(obj interface{}, size uint32, name string) (MemoryEntry, error)

AddEntry adds a new memory entry to the controller.

func (*MemoryController) DelEntryByAddr

func (mc *MemoryController) DelEntryByAddr(addr uint32) error

DelEntryByAddr deletes a memory entry by virtual address `addr`, which might be any value between the entries Addr and Addr+Size.

func (*MemoryController) DelEntryByObj

func (mc *MemoryController) DelEntryByObj(obj interface{}) error

DelEntryByObj deletes the entry from the memory controller which resolves to the given object.

func (*MemoryController) GetAllEntries

func (mc *MemoryController) GetAllEntries() []MemoryEntry

GetAllEntries returns all memory entries, the returned slice are copies of the actual entries so they can't be used to modify internal state.

func (*MemoryController) GetEntry

func (mc *MemoryController) GetEntry(addr uint32) (MemoryEntry, uint32, bool)

GetEntry returns the memory entry matching the given virtual address. This method also returns the offset into the entry. An `addr` of 0x14 might for example resolve to an entry at 0x10 of size 8 and an offset of 0x04 into that entry. The last return value is a boolean which is true if an entry was found, and false if no entry was found.

func (*MemoryController) GetEntryByObject

func (mc *MemoryController) GetEntryByObject(obj interface{}) (MemoryEntry, bool)

GetEntryByObject return the memory entry which points to the given object. The boolean indicates if a entry was found.

func (*MemoryController) String

func (mc *MemoryController) String() string

String implements fmt.Stringer

type MemoryEntry

type MemoryEntry struct {
	Name   string
	Addr   uint32
	Size   uint32
	Object interface{}
}

MemoryEntry is returned by the MemoryController, each entry has a name `Which“ is used for debugging purposes. Virtual address `Addr`, allocation size `Size` and a `Object` which is what the `Addr` resolves to. Any virtual address between `Addr` and `Addr`+`Size` resolves to this entry.

func (MemoryEntry) Copy

func (me MemoryEntry) Copy() MemoryEntry

Copy makes a value copy of the MemoryEntry, the new entry will still point to the same Object

type NetDev added in v0.0.5

type NetDev struct {
	IFIndex uint32 `json:"ifIndex"`
}

NetDev represents a network device to which a socket / socket_buffer is connected

type PlainMemory

type PlainMemory struct {
	Backing   []byte
	ByteOrder binary.ByteOrder
}

PlainMemory is the simplest implementation of VMMem possible, it is just a []byte with no additional information about its contents. The ByteOrder is used when Load'in or Store'ing scalar values. If ByteOrder is not set the native endianness will be used.

func (*PlainMemory) Load

func (pm *PlainMemory) Load(offset uint32, size asm.Size) (uint64, error)

Load loads a scalar value of the given `size` and `offset` from the memory.

func (*PlainMemory) Read

func (pm *PlainMemory) Read(offset uint32, b []byte) error

Read reads a byte slice of arbitrary size, the length of 'b' is used to determine the requested size

func (*PlainMemory) Store

func (pm *PlainMemory) Store(offset uint32, value uint64, size asm.Size) error

Store stores a scalar value of the given `size` and `offset` from the memory.

func (*PlainMemory) Write

func (pm *PlainMemory) Write(offset uint32, b []byte) error

Write write a byte slice of arbitrary size to the memory

type Process

type Process struct {
	// The VM in which the process runs
	VM *VM
	// The current Program
	Program *ebpf.ProgramSpec
	// Stack of this process
	Stack PlainMemory
	// Context of the process
	Context Context
	// The registers of this process
	Registers Registers
	// A values which the the emulator can use to track process specific values
	EmulatorValues map[interface{}]interface{}
	// contains filtered or unexported fields
}

Process describes an instance of a program executing, each process has its own registers and stack

func (*Process) CPUID added in v0.0.3

func (p *Process) CPUID() int

CPUID returns the current CPU ID of the process

func (*Process) Cleanup

func (p *Process) Cleanup() error

Cleanup any memory allocations associated with this process

func (*Process) Run

func (p *Process) Run(ctx context.Context) error

Run runs the program until it exits, encounters a fatal error or the context is canceled/deadline expires

func (*Process) SetCPUID added in v0.0.3

func (p *Process) SetCPUID(id int) error

SetCPUID set the CPU ID of the process

func (*Process) Step

func (p *Process) Step() (exited bool, err error)

Step "steps" through one program instruction. If this function returns `exited` == true, it means that the program has stop execution, subsequent calls to Step will be ineffective. If `err` != nil, it means that a fatal error was encountered and that the process can't continue execution subsequent calls to Step will be ineffective.

type ProcessPool added in v0.0.3

type ProcessPool interface {
	Enqueue(job ProcessPoolJob, noblock bool) error
	Start(backlog int) error
	Stop()
}

ProcessPool is a worker pool for processes. Once Start'ed processes can be submitted which will be ran on the workers each worker has its own virtual CPU ID, thus emulating an actual CPU. The processPool guarantees that no two processes will run with the same CPU ID, making programs which rely on that property to guard against race-conditions save to run. Starting the worker pool with the exact amount of logical CPUs on the host (runtime.NumCPU()) is also the most performant way to run eBPF programs which are non-blocking.

type ProcessPoolJob added in v0.0.3

type ProcessPoolJob struct {
	// The process to be executed
	Process *Process
	// The context with which the process is to be ran. Which can be used to cancel a particular process or to set
	// a deadline to limit its resource usage. Optional, if nil context.Background() is used.
	Context context.Context
	// When the process exits or errors, instread of cleaning it up, it will be handed off to this callback which will
	// be started in its own goroutine. This can be used to process the results, but is also responsible for the process
	// cleanup. Optional, if nil, the process is cleaned up by the pool.
	Handoff func(p *Process, err error)
}

ProcessPoolJob is a job which can be scheduled with the process pool to be executed.

type Registers

type Registers struct {
	// PC is the program counter, it keeps track of the next instruction to be executed by the current program. It is
	// a offset within the instruction slice.
	PC int

	// R0 is used as return values from helper functions, BPF-to-BPF calls and eBPF programs
	R0 uint64

	// R1-R5 are used as arguments to a functions.
	R1 uint64
	R2 uint64
	R3 uint64
	R4 uint64
	R5 uint64

	// R6-R9 are callee saved registers, when calling a helper or BPF-to-BPF function the callee will save the current
	// values of these registers and upon returning will restore them.
	R6 uint64
	R7 uint64
	R8 uint64
	R9 uint64

	// R10 contains a pointer to the end of the current stack frame, it is read-only, programs are not allowed to write
	// to this register, only copy its value and modify its copy. The frame pointer will be changed when calling into
	// a BPF-to-BPF function. Callees can pass the current frame pointer to the next function to allow that function
	// to access the frame pointer of the callee.
	R10 uint64
}

Registers describe the CPU registers of the VM

func (*Registers) Get

func (r *Registers) Get(asmReg asm.Register) uint64

Get returns the value fo a given `asmReg`

func (*Registers) Set

func (r *Registers) Set(asmReg asm.Register, value uint64) error

Set sets the value of a given `asmReg` to `value`

type RingMemory added in v0.0.3

type RingMemory struct {
	Backing   []byte
	ByteOrder binary.ByteOrder
}

RingMemory is very similar to PlainMemory, except RingMemory will wrap around to the start when reading or writing out of bounds.

func (*RingMemory) Load added in v0.0.3

func (pm *RingMemory) Load(offset uint32, size asm.Size) (uint64, error)

Load loads a scalar value of the given `size` and `offset` from the memory.

func (*RingMemory) Read added in v0.0.3

func (pm *RingMemory) Read(offset uint32, b []byte) error

Read reads a byte slice of arbitrary size, the length of 'b' is used to determine the requested size

func (*RingMemory) Store added in v0.0.3

func (pm *RingMemory) Store(offset uint32, value uint64, size asm.Size) error

Store stores a scalar value of the given `size` and `offset` from the memory.

func (*RingMemory) Write added in v0.0.3

func (pm *RingMemory) Write(offset uint32, b []byte) error

Write write a byte slice of arbitrary size to the memory

type SK added in v0.0.5

type SK struct {
	BoundDevIF uint32 `json:"boundDevIF"`
	Family     uint32 `json:"family"`
	SockType   uint32 `json:"sockType"`
	Protocol   uint32 `json:"protocol"`
	Mark       uint32 `json:"mark"`
	Priority   uint32 `json:"priority"`

	SrcIP4 string `json:"srcIP4"`

	SrcIP6 string `json:"srcIP6"`

	SrcPort uint32 `json:"srcPort"` /* host byte order */
	DstPort uint32 `json:"dstPort"` /* network byte order */
	DstIP4  string `json:"dstIP4"`

	DstIP6 string `json:"dstIP6"`

	State          uint32 `json:"state"`
	RXQueueMapping int32  `json:"rxQueueMapping"`
	// contains filtered or unexported fields
}

SK https://elixir.bootlin.com/linux/v5.16.10/source/include/uapi/linux/bpf.h#L5406

func (*SK) Load added in v0.0.5

func (sk *SK) Load(offset uint32, size asm.Size) (uint64, error)

Load reads a single integer value of 1, 2, 4 or 8 bytes at a specific offset

func (*SK) Read added in v0.0.5

func (sk *SK) Read(offset uint32, b []byte) error

Read reads a byte slice of arbitrary size, the length of 'b' is used to determine the requested size

func (*SK) Size added in v0.0.5

func (sk *SK) Size() int

Size returns the size of the bpf_sk (not the actual sk, but its virtual address proxy range)

func (*SK) Store added in v0.0.5

func (sk *SK) Store(offset uint32, value uint64, size asm.Size) error

Store write a single interger value of 1, 2, 4 or 8 bytes to a specific offset

func (*SK) UnmarshalJSON added in v0.0.5

func (sk *SK) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler

func (*SK) Write added in v0.0.5

func (sk *SK) Write(offset uint32, b []byte) error

Write write a byte slice of arbitrary size to the memory

type SKBuff added in v0.0.5

type SKBuff struct {
	// contains filtered or unexported fields
}

SKBuff is an emulated version of the Linux socket buffer. A datastructure which Linux uses to keep track of packets received on a socket. A SKBuff is a metadata wrapper around the actual packet data, usually part of a linked list of other SKBuff objects. It contains a lot of pre-processes data which programs and pointers to the different network layers of the packet. https://elixir.bootlin.com/linux/v5.16.10/source/include/linux/skbuff.h#L731

eBPF programs can't directly access the sk_buff struct, rather the __sk_buff proxy is used. https://elixir.bootlin.com/linux/v5.16.10/source/include/uapi/linux/bpf.h#L5315 This object never actually exists it memory, just defined to provide the correct offset. When programs are loaded into the Linux kernel they are re-written to access the actual sk_buff, this provides API stability. https://elixir.bootlin.com/linux/v5.16.10/source/net/core/filter.c#L8548

This emulated version is not a perfect replica of the sk_buff, just aims to provide features required for eBPF emulation. Instread of re-writing the eBPF program, the SKBuff implements the VMMem interface, which internally will do the same conversion as the kernel does.

func SKBuffFromBytes added in v0.0.5

func SKBuffFromBytes(pktData []byte) (*SKBuff, error)

SKBuffFromBytes parses the packet and constructs a SKBuff from it.

Basically do the same as https://elixir.bootlin.com/linux/v5.16.10/source/net/bpf/test_run.c#L565

func (*SKBuff) Load added in v0.0.5

func (sk *SKBuff) Load(offset uint32, size asm.Size) (uint64, error)

Load reads a single integer value of 1, 2, 4 or 8 bytes at a specific offset

func (*SKBuff) Read added in v0.0.5

func (sk *SKBuff) Read(offset uint32, b []byte) error

Read reads a byte slice of arbitrary size, the length of 'b' is used to determine the requested size

func (*SKBuff) Size added in v0.0.5

func (sk *SKBuff) Size() int

Size returns the size of the __sk_buff (not the actual SKBuff, but its virtual address proxy range)

func (*SKBuff) Store added in v0.0.5

func (sk *SKBuff) Store(offset uint32, value uint64, size asm.Size) error

Store write a single interger value of 1, 2, 4 or 8 bytes to a specific offset

func (*SKBuff) Write added in v0.0.5

func (sk *SKBuff) Write(offset uint32, b []byte) error

Write write a byte slice of arbitrary size to the memory

type VM

type VM struct {
	MemoryController MemoryController
	// contains filtered or unexported fields
}

VM is the eBPF virtual machine

func NewVM

func NewVM(opts ...VMOpt) *VM

NewVM create a new eBPF virtual machine from the given options.

func (*VM) AddProgram

func (vm *VM) AddProgram(prog *ebpf.ProgramSpec) (int, error)

AddProgram adds a program to the VM. Doing so will cause the VM to rewrite the program to make it ready for execution. On success a unique identifier for the program is returned, which can be used in calls to NewProcess to specify the entrypoint program.

func (*VM) GetProcessPool added in v0.0.3

func (vm *VM) GetProcessPool() ProcessPool

GetProcessPool returns the process pool of the VM

func (*VM) GetPrograms

func (vm *VM) GetPrograms() []*ebpf.ProgramSpec

GetPrograms returns the loaded program specs

func (*VM) NewProcess

func (vm *VM) NewProcess(entrypoint int, ctx Context) (*Process, error)

NewProcess spawns a new process, `entrypoint` specifies the entrypoint program and `ctx` the context for the process which may be nil, if no context information is needed.

type VMMem

type VMMem interface {
	// Load reads a single integer value of 1, 2, 4 or 8 bytes at a specific offset
	Load(offset uint32, size asm.Size) (uint64, error)
	// Store write a single interger value of 1, 2, 4 or 8 bytes to a specific offset
	Store(offset uint32, value uint64, size asm.Size) error
	// Read reads a byte slice of arbitrary size, the length of 'b' is used to determine the requested size
	Read(offset uint32, b []byte) error
	// Write write a byte slice of arbitrary size to the memory
	Write(offset uint32, b []byte) error
}

VMMem is memory which which the VM can access(read and write)

type VMOpt

type VMOpt func(*VMSettings)

VMOpt is a option which can be used during the creation of a VM with the NewVM function

func VMOptEmulator

func VMOptEmulator(e Emulator) VMOpt

VMOptEmulator is used to assign an Emulator to a VM

func VMOptSetvCPUs added in v0.0.3

func VMOptSetvCPUs(vCPUs int) VMOpt

VMOptSetvCPUs explicitly sets the amount of virtual CPUs of the VM

type VMSettings

type VMSettings struct {
	Emulator Emulator
	// Size of the stack in bytes
	StackFrameSize int
	// Number of stack frames (max call depth of BPF-to-BPF calls)
	StackFrameCount int
	// Number of vCPU's, processes can't have a CPUID higher or equal to this number
	VirtualCPUs int
}

VMSettings are the actual settings of the VM, VMOpt's can change an instance of these settings.

Directories

Path Synopsis
cmd
inst_gen
This binary generates the source code for the CPU instructions of the eBPF machine go run cmd/inst_gen/main.go | gofmt > inst_gen.go
This binary generates the source code for the CPU instructions of the eBPF machine go run cmd/inst_gen/main.go | gofmt > inst_gen.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL