A framework for building secure, capability-aware applications on FreeBSD.
FreeBSDKit provides idiomatic Swift, C and C++ interfaces to FreeBSD's unique system features including Capsicum sandboxing, jails, process descriptors, kqueue-based signal handling, and inter-process communication with descriptor passing. The framework embraces move-only semantics (~Copyable) to model resource ownership explicitly in the type system.
- FreeBSD 13.0 or later
- Swift 6.2 or later
Add FreeBSDKit to your Package.swift:
dependencies: [
.package(url: "https://github.com/your/FreeBSDKit", from: "1.0.0")
]Then add the specific libraries you need to your target:
.target(
name: "MyApp",
dependencies: [
.product(name: "Capsicum", package: "FreeBSDKit"),
.product(name: "Casper", package: "FreeBSDKit"),
.product(name: "Descriptors", package: "FreeBSDKit"),
.product(name: "FPC", package: "FreeBSDKit"),
.product(name: "Procctl", package: "FreeBSDKit"),
.product(name: "ACL", package: "FreeBSDKit"),
.product(name: "Rctl", package: "FreeBSDKit"),
.product(name: "Cpuset", package: "FreeBSDKit"),
.product(name: "DTraceCore", package: "FreeBSDKit"),
.product(name: "DBlocks", package: "FreeBSDKit"),
]
)Foundation protocols and utilities shared across the framework.
import FreeBSDKit
// Type-safe sysctl access
let hostname: String = try BSDSysctl.getString("kern.hostname")
let boottime: timeval = try BSDSysctl.get("kern.boottime")
let physmem: Int64 = try BSDSysctl.get("hw.physmem")
// Extended attributes
try ExtendedAttributes.set(
path: "/path/to/file",
namespace: .user,
name: "myattr",
data: "value".data(using: .utf8)!
)
let data = try ExtendedAttributes.get(
path: "/path/to/file",
namespace: .user,
name: "myattr"
)Key Types:
BSDSysctl- Type-safe sysctl reading and writingExtendedAttributes- Extended attribute operations on files and descriptorsBSDError- Unified error handling for BSD system callsBSDSignal- Signal enumeration with catchability checksBSDResource- Protocol for ownership-aware resources
Swift interface to FreeBSD's Capsicum capability-mode sandbox.
import Capsicum
// Check if already in capability mode
let inCapMode = try Capsicum.status()
// Enter capability mode (irreversible)
try Capsicum.enter()
// After this point, global namespaces are inaccessible.
// Only operations on existing file descriptors with
// appropriate rights are permitted.Limiting Descriptor Rights:
import Capsicum
// Create a right set with specific capabilities
let rights = CapsicumRightSet(rights: [
.read,
.write,
.fstat,
.seek
])
// Limit a file descriptor (rights can only be reduced, never expanded)
_ = CapsicumHelper.limit(fd: fd, rights: rights)
// Limit allowed fcntl operations
try CapsicumHelper.limitFcntls(fd: fd, rights: [.getfl, .setfl])
// Limit allowed ioctl commands
try CapsicumHelper.limitIoctls(fd: fd, commands: [
IoctlCommand(rawValue: FIONREAD)
])Key Types:
Capsicum- Enter capability mode and query statusCapsicumRightSet- Set of capability rights for a descriptorCapsicumRight- Individual capability rights (read, write, seek, etc.)FcntlRights- Allowed fcntl operationsIoctlCommand- Allowed ioctl commands
Move-only descriptor types with explicit ownership semantics.
import Descriptors
// All descriptors are ~Copyable (move-only)
// Ownership transfers on assignment, preventing use-after-close
var file = FileCapability(fd)
// Read from a descriptor
let result = try file.read(maxBytes: 4096)
switch result {
case .data(let bytes):
print("Read \(bytes.count) bytes")
case .eof:
print("End of file")
}
// Write to a descriptor
let written = try file.writeOnce(data)
// Descriptor is closed when consumed
file.close()Socket Operations:
import Descriptors
// Create a Unix domain socket
var socket = try SocketCapability.create(
domain: .unix,
type: .stream,
protocol: 0
)
// Bind and listen
try socket.bind(path: "/tmp/my.sock")
try socket.listen(backlog: 5)
// Accept connections
var client = try socket.accept()Pipe Operations:
import Descriptors
// Create a pipe pair
let (read, write) = try PipeCapability.create()
// Write to the pipe
try write.writeOnce("Hello".data(using: .utf8)!)
// Read from the pipe
let result = try read.read(maxBytes: 100)Device Operations:
import Descriptors
// Open a device
var device = try DeviceCapability.open(
path: "/dev/random",
flags: [.readOnly]
)
// Read random bytes
let result = try device.read(maxBytes: 32)
// Query device properties
let deviceType = try device.deviceType()
let isDisk = try device.isDisk()
// For block devices
let sectorSize = try device.sectorSize()
let mediaSize = try device.mediaSize()Key Types:
FileCapability- Regular file descriptorDirectoryCapability- Directory descriptor with openat supportDeviceCapability- Device file descriptor with ioctl supportSocketCapability- Socket descriptorPipeReadCapability/PipeWriteCapability- Pipe endpointsKqueueCapability- Kqueue event notificationProcessCapability- Process descriptor (pdfork)SharedMemoryCapability- POSIX shared memoryEventCapability- Eventfd-like notificationsSystemJailDescriptor- Jail descriptor
Capsicum capability wrappers for descriptors with built-in right limiting.
import Capabilities
// Open a file with capability wrapping
var file = FileCapability(fd)
// Limit rights directly on the capability
_ = file.limit(rights: CapsicumRightSet(rights: [.read, .fstat]))
// Limit fcntl/ioctl operations
try file.limitFcntls(rights: [.getfl])
try file.limitIoctls(commands: [])
// Query current limits
let fcntls = try file.getFcntls()
let ioctls = try file.getIoctls()Directory-Relative Operations (Capsicum-Safe):
import Capabilities
// Open a directory capability
var dir = try DirectoryCapability.open(path: "/var/data")
// Open files relative to the directory (works in capability mode)
var file = try dir.openFile(relativePath: "config.json", flags: [.readOnly])
// Create subdirectories
try dir.createDirectory(relativePath: "cache", mode: 0o755)
// Stat files relative to directory
let st = try dir.stat(relativePath: "config.json")Kqueue and libdispatch-based signal handling.
import SignalDispatchers
import Descriptors
// Create a kqueue for signal delivery
var kq = try KqueueCapability.create()
// Create handler for specific signals
let handler = try KqueueSignalHandler(
kqueue: kq,
signals: [.int, .term, .hup]
)
// Register handlers
await handler.on(.int) {
print("Received SIGINT")
}
await handler.on(.term) {
print("Received SIGTERM, shutting down...")
}
// Run the signal handling loop (never returns normally)
try await handler.run()Using libdispatch:
import SignalDispatchers
// GCD-based signal handling
let handler = try GCDSignalHandler(signals: [.int, .term])
handler.on(.int) {
print("SIGINT received")
}
handler.on(.term) {
print("SIGTERM received")
}
// Signals are delivered via GCDInterface to FreeBSD jail management.
import Jails
import Descriptors
// Build jail parameters
var iov = JailIOVector()
iov.add(key: "name", value: "myjail")
iov.add(key: "path", value: "/jail/myjail")
iov.add(key: "host.hostname", value: "jailed.local")
iov.add(key: "persist", value: true)
// Create jail and get descriptor
let flags: JailSetFlags = [.create, .getDesc, .ownDesc]
var jailDesc = try SystemJailDescriptor.set(iov: &iov, flags: flags)
// Attach current process to jail
try jailDesc.attach()
// Remove jail (requires owning descriptor)
try jailDesc.remove()IPC protocol with descriptor passing over Unix sockets.
import FPC
// Server side
let listener = try FPCListener(path: "/tmp/myservice.sock")
while true {
var endpoint = try await listener.accept()
// Receive a message
var request = try await endpoint.receive()
// Extract descriptors from message
if let file = request.fileDescriptor(at: 0) {
// Process the received file descriptor
}
// Send a reply
let reply = FPCMessage.reply(to: request, id: .pong)
try await endpoint.send(reply)
}Client Side:
import FPC
// Connect to server
var endpoint = try await FPCClient.connect(path: "/tmp/myservice.sock")
// Send a request with file descriptors
var fd = FileCapability(openedFd)
let message = FPCMessage.request(
.lookup,
payload: "config".data(using: .utf8)!,
descriptors: [fd.toOpaqueRef()]
)
try await endpoint.send(message)
// Receive reply
let reply = try await endpoint.receive()Custom Message IDs:
extension MessageID {
// User message IDs start at 256
static let fileOpen = MessageID(rawValue: 256)
static let fileOpenReply = MessageID(rawValue: 257)
static let processSpawn = MessageID(rawValue: 258)
}Wire Format:
- 16-byte header with version, message ID, correlation ID, flags
- Up to 64KB inline payload
- Up to 16 file descriptors per message
- Out-of-line (OOL) support for larger payloads via shared memory
Security labeling tool for FreeBSD MAC Framework integration.
import MacLabel
// Load configuration from JSON
let config = try LabelConfiguration<FileLabel>.load(from: configPath)
// Create labeler
var labeler = Labeler(configuration: config)
labeler.verbose = true
// Validate all paths exist before applying
try labeler.validateConfiguration()
// Apply labels to files
let results = try labeler.apply()
for result in results {
if result.success {
print("Labeled: \(result.path)")
} else {
print("Failed: \(result.path) - \(result.error!)")
}
}
// Verify labels match configuration
let verification = try labeler.verify()Configuration Format (JSON):
{
"attributeName": "mac_labels",
"labels": [
{
"path": "/usr/local/bin/myapp",
"attributes": {
"security_level": "high",
"network_access": "restricted"
}
},
{
"path": "/var/data/*",
"attributes": {
"data_class": "sensitive"
}
}
]
}CLI Tool (maclabel):
# Validate configuration
maclabel validate -c config.json
# Apply labels
sudo maclabel apply -c config.json
# Show current labels
maclabel show -c config.json
# Verify labels match configuration
maclabel verify -c config.json
# Remove labels
sudo maclabel remove -c config.jsonSwift interface to FreeBSD's Casper (libcasper) services for use in Capsicum sandboxes.
Casper provides privileged services to capability-mode processes that lack direct access to global namespaces. Each service runs in a separate sandboxed process and communicates via Unix domain sockets.
import Casper
import Capsicum
// Initialize Casper BEFORE entering capability mode (single-threaded context)
let casper = try CasperChannel.create()
// Open services you need
let dns = try CasperDNS(casper: casper)
let sysctl = try CasperSysctl(casper: casper)
let pwd = try CasperPwd(casper: casper)
let grp = try CasperGrp(casper: casper)
// Limit services to minimum required operations
try dns.limitTypes([.nameToAddress])
try dns.limitFamilies([AF_INET, AF_INET6])
try sysctl.limitNames([
("kern.hostname", .read),
("hw.physmem", .read)
])
try pwd.limitUsers(names: ["root", "www"])
try pwd.limitCommands([.getpwnam, .getpwuid])
// Enter capability mode
try Capsicum.enter()
// Use services within the sandbox
let addresses = try dns.getaddrinfo(hostname: "example.com", port: "443")
let hostname = try sysctl.getString("kern.hostname")
if let user = pwd.getpwnam("www") {
print("www uid: \(user.uid)")
}Available Services:
| Service | Purpose | Key Operations |
|---|---|---|
CasperDNS |
DNS resolution | getaddrinfo, getnameinfo, gethostbyname |
CasperSysctl |
Sysctl access | get, set, getString, nameToMIB |
CasperPwd |
Password database | getpwnam, getpwuid, getpwent |
CasperGrp |
Group database | getgrnam, getgrgid, getgrent |
CasperSyslog |
System logging | openlog, syslog, closelog |
Syslog Service:
import Casper
let casper = try CasperChannel.create()
let syslog = try CasperSyslog(casper: casper)
// Open connection to syslog
syslog.openlog(ident: "myapp", options: [.pid, .cons], facility: .daemon)
// Log messages within capability mode
syslog.syslog(priority: .info, message: "Application started")
syslog.syslog(priority: .err, message: "Something went wrong")
// Close when done
syslog.closelog()Swift interface to FreeBSD's procctl(2) system call for process control operations.
import Procctl
// ASLR (Address Space Layout Randomization)
let aslrStatus = try Procctl.ASLR.getStatus()
if aslrStatus.isActive {
print("ASLR is active")
}
try Procctl.ASLR.forceEnable() // Takes effect on next exec
// Process tracing control
if try Procctl.Trace.isEnabled() {
try Procctl.Trace.disable() // Prevent debugging/tracing
}
// Parent death signal (like Linux PR_SET_PDEATHSIG)
try Procctl.ParentDeathSignal.set(signal: SIGTERM)
// No new privileges (prevent setuid escalation)
try Procctl.NoNewPrivileges.enable()
// Capability trap (SIGTRAP on Capsicum violations for debugging)
try Procctl.CapabilityTrap.enable()Process Reaper (Container/Supervisor Support):
import Procctl
// Become a process reaper - orphaned descendants reparent here instead of init
try Procctl.Reaper.acquire()
// Check reaper status
let status = try Procctl.Reaper.getStatus()
print("Children: \(status.childCount), Descendants: \(status.descendantCount)")
// Get PIDs of all descendants
let pids = try Procctl.Reaper.getPids()
for pid in pids {
print("PID \(pid.pid), zombie: \(pid.isZombie)")
}
// Kill all descendants
let result = try Procctl.Reaper.killAll(signal: SIGTERM)
print("Killed \(result.killed) processes")
// Release reaper role
try Procctl.Reaper.release()Security Controls:
import Procctl
// W^X (Write XOR Execute) enforcement
let wxStatus = try Procctl.WXMap.getStatus()
if !wxStatus.isEnforced {
try Procctl.WXMap.enforce() // Prevent simultaneous W+X mappings
}
// PROT_MAX control (limit mprotect upgrades)
try Procctl.ProtMax.forceEnable()
// Stack gap randomization
try Procctl.StackGap.enable()
// OOM killer protection
try Procctl.OOMProtection.protect(options: .inherit)
// Signal exit logging
try Procctl.LogSigExit.forceEnable()x86_64-Specific Controls:
#if arch(x86_64)
import Procctl
// KPTI (Kernel Page Table Isolation) - Meltdown mitigation
let kptiStatus = try Procctl.KPTI.getStatus()
try Procctl.KPTI.enableOnExec()
// Linear address width (LA48 vs LA57)
let laStatus = try Procctl.LinearAddress.getStatus()
if laStatus.isLA57 {
try Procctl.LinearAddress.setLA48OnExec() // Use 48-bit addresses
}
#endifAvailable Controls:
| Control | Purpose |
|---|---|
ASLR |
Address space layout randomization |
Trace |
Process tracing/debugging control |
ProtMax |
Implicit PROT_MAX for mprotect |
StackGap |
Stack gap randomization |
NoNewPrivileges |
Prevent privilege escalation via setuid |
CapabilityTrap |
SIGTRAP on Capsicum violations |
ParentDeathSignal |
Signal on parent termination |
WXMap |
W^X mapping enforcement |
LogSigExit |
Signal exit logging |
Reaper |
Process reaper for orphan adoption |
OOMProtection |
OOM killer protection |
KPTI |
Kernel page table isolation (x86_64) |
LinearAddress |
Linear address width (x86_64) |
Swift interface to FreeBSD's POSIX.1e and NFSv4 Access Control Lists.
ACLs provide fine-grained access control beyond traditional Unix permissions, allowing you to grant specific permissions to individual users and groups.
import ACL
// Get ACL from a file
let acl = try ACL.get(path: "/path/to/file")
print("Brand: \(acl.brand)") // .posix or .nfs4
// Check if file has extended ACL
if ACL.hasExtendedACL(path: "/path/to/file") {
print("File has extended ACL entries")
}
// Iterate over entries
acl.forEachEntry { entry in
print("Tag: \(entry.tag), Permissions: \(entry.permissions)")
}
// Create ACL from Unix mode
if let acl = ACL.fromMode(0o755) {
try acl.set(path: "/path/to/file")
}
// Parse ACL from text
if let acl = ACL.fromText("user::rwx,group::r-x,other::r-x") {
print(acl.text ?? "")
}Builder API for POSIX.1e ACLs:
import ACL
// Build a POSIX.1e ACL programmatically
var builder = ACL.builder()
_ = builder.ownerPermissions(.all) // rwx for owner
_ = builder.userPermissions(1000, .readWrite) // rw- for uid 1000
_ = builder.groupPermissions(.readExecute) // r-x for owning group
_ = builder.groupPermissions(100, [.read]) // r-- for gid 100
_ = builder.otherPermissions([.read]) // r-- for others
let acl = try builder.build()
try acl.set(path: "/path/to/file")NFSv4 ACLs with Allow/Deny:
import ACL
// Build an NFSv4 ACL with allow/deny entries
var builder = ACL.nfs4Builder()
_ = builder.allowOwner(.fullSet)
_ = builder.denyUser(1000, [.writeData, .delete])
_ = builder.allowGroup(.readSet, flags: [.fileInherit, .directoryInherit])
_ = builder.allowEveryone([.readData, .readACL])
let acl = try builder.build()
try acl.set(path: "/path/to/file", type: .nfs4)Working with Entries:
import ACL
var acl = try ACL()
// Create and configure an entry
let entry = try acl.createEntry()
try entry.setTag(.user)
try entry.setQualifier(1000) // uid
try entry.setPermissions([.read, .write])
// For NFSv4 ACLs
try entry.setEntryType(.allow)
try entry.setNFS4Permissions([.readData, .writeData, .execute])
try entry.setFlags([.fileInherit, .directoryInherit])Permission Types:
| POSIX.1e | NFSv4 |
|---|---|
.read |
.readData, .readNamedAttrs, .readAttributes, .readACL |
.write |
.writeData, .appendData, .writeNamedAttrs, .writeAttributes, .writeACL, .writeOwner |
.execute |
.execute |
.delete, .deleteChild, .synchronize |
Tag Types:
| Tag | Description |
|---|---|
.userObj |
File owner |
.user |
Specific user (requires qualifier) |
.groupObj |
Owning group |
.group |
Specific group (requires qualifier) |
.mask |
Maximum group class permissions (POSIX.1e) |
.other |
All other users (POSIX.1e) |
.everyone |
All users (NFSv4) |
Swift interface to FreeBSD's rctl(4) resource control subsystem for limiting CPU, memory, and I/O resources per-process, per-user, per-jail, or per-login class.
import Rctl
// Check if rctl is enabled in the kernel
if Rctl.isEnabled {
print("rctl is available")
}
// Get resource usage for current process
let usage = try Rctl.getCurrentProcessUsage()
print("CPU time: \(usage["cputime"] ?? "0")")
print("Memory: \(usage["memoryuse"] ?? "0")")
// Get usage for a specific subject
let jailUsage = try Rctl.getUsage(for: .jailName("myjail"))Adding Resource Limits:
import Rctl
// Limit memory for a user to 1GB
try Rctl.limitMemory(Rctl.Size.gb(1), for: .user(1000))
// Limit CPU to 50% for a jail
try Rctl.limitCPU(50, for: .jailName("myjail"))
// Limit open files for a process
try Rctl.limitOpenFiles(256, for: .process(getpid()))
// Limit max processes per user
try Rctl.limitProcesses(100, for: .userName("www"))Custom Rules:
import Rctl
// Create a custom rule with signal action
let rule = Rctl.Rule(
subject: .loginClass("daemon"),
resource: .cpuTime,
action: .signal(SIGXCPU),
amount: 3600 // 1 hour
)
try Rctl.addRule(rule)
// Rule with per-process limit
let memRule = Rctl.Rule(
subject: .jail(5),
resource: .memoryUse,
action: .deny,
amount: Rctl.Size.mb(512),
per: .process
)
try Rctl.addRule(memRule)
// Remove a rule
try Rctl.removeRule(rule)
// Remove all rules for a subject
try Rctl.removeRules(for: .user(1000))Rule Builder:
import Rctl
var builder = Rctl.ruleBuilder()
_ = builder.forSubject(.jailName("webserver"))
_ = builder.limiting(.vmemoryUse)
_ = builder.withAction(.deny)
_ = builder.toAmount(Rctl.Size.gb(4))
_ = builder.per(.process)
if let rule = builder.build() {
try Rctl.addRule(rule)
}Query Existing Rules:
import Rctl
// Get all rules
let allRules = try Rctl.getRules()
// Get rules for a specific subject
let userRules = try Rctl.getRules(for: .user(1000))
// Parse a rule string
if let rule = Rctl.Rule(parsing: "user:1000:memoryuse:deny=536870912/process") {
print("Subject: \(rule.subject)")
print("Resource: \(rule.resource)")
print("Amount: \(rule.amount)")
}Process Descriptor Integration:
import Rctl
import Descriptors
// Fork a child process with pdfork
let result = try ProcessCapability.fork()
if !result.isChild, var desc = result.descriptor as? ProcessCapability {
// Get resource usage via process descriptor
let usage = try Rctl.getUsage(for: desc)
print("Child CPU: \(usage["cputime"] ?? "0")")
// Or create a subject from descriptor
let subject = try Rctl.Subject.process(from: desc)
// Apply limits to the child process
try Rctl.limitMemory(Rctl.Size.mb(256), for: desc)
try Rctl.limitCPU(50, for: desc)
}Subjects:
| Subject | Description | Example |
|---|---|---|
.process(pid) |
Specific process | .process(1234) |
.user(uid) |
User by UID | .user(1000) |
.userName(name) |
User by name | .userName("www") |
.loginClass(name) |
Login class | .loginClass("daemon") |
.jail(jid) |
Jail by JID | .jail(5) |
.jailName(name) |
Jail by name | .jailName("myjail") |
Resources:
| Resource | Description |
|---|---|
.cpuTime |
CPU time in seconds |
.memoryUse |
Resident memory (RSS) |
.vmemoryUse |
Virtual memory |
.maxProc |
Number of processes |
.openFiles |
Open file descriptors |
.threads |
Number of threads |
.swapUse |
Swap space usage |
.pcpu |
CPU percentage (0-100 per CPU) |
.readBps / .writeBps |
I/O bandwidth |
.readIops / .writeIops |
I/O operations per second |
Actions:
| Action | Description |
|---|---|
.deny |
Deny the resource allocation |
.log |
Log via syslog |
.devctl |
Send notification via devctl |
.throttle |
Throttle I/O (for bandwidth limits) |
.signal(sig) |
Send signal (SIGTERM, SIGKILL, etc.) |
Swift interface to FreeBSD's cpuset(2) CPU affinity and NUMA domain subsystem.
import Cpuset
// Get current thread's CPU affinity
let affinity = try Cpuset.getAffinity(for: .currentThread)
print("Running on CPUs: \(affinity.cpus)")
// Get available CPUs
let available = try Cpuset.availableCPUs()
print("Available: \(available)")
// Check system CPU count
let root = try Cpuset.rootCPUs()
print("System has \(root.count) CPUs")Pinning Threads/Processes:
import Cpuset
// Pin current thread to CPU 0
try Cpuset.pinCurrentThread(to: 0)
// Pin to multiple CPUs
try Cpuset.pinCurrentThread(to: [0, 1, 2, 3])
// Pin current process
try Cpuset.pinCurrentProcess(to: 0)
// Reset to all available CPUs
try Cpuset.resetCurrentThreadAffinity()Working with CPU Sets:
import Cpuset
// Create CPU sets
var set = CPUSet()
set.set(cpu: 0)
set.set(cpu: 2)
set.set(cpu: 4)
// From array or range
let fromArray = CPUSet(cpus: [0, 1, 2, 3])
let fromRange = CPUSet(range: 0..<8)
// Set operations
let union = set1.union(set2)
let intersection = set1.intersection(set2)
let difference = set1.subtracting(set2)
// Query
print("CPUs: \(set.cpus)") // [0, 2, 4]
print("Count: \(set.count)") // 3
print("First: \(set.first!)") // 0
print("Empty: \(set.isEmpty)") // falseNUMA Domain Affinity:
import Cpuset
// Get domain policy
let (domains, policy) = try Cpuset.getDomain(for: .currentThread)
print("Policy: \(policy)") // .roundRobin, .firstTouch, .prefer, .interleave
// Set first-touch allocation (local to running CPU)
try Cpuset.useFirstTouchAllocation()
// Prefer a specific domain
try Cpuset.preferDomain(0)
// Round-robin across domains
try Cpuset.useRoundRobinAllocation()
// Interleave across specific domains
try Cpuset.useInterleaveAllocation(domains: [0, 1])Process Descriptor Integration:
import Cpuset
import Descriptors
// Fork with pdfork
let result = try ProcessCapability.fork()
if !result.isChild, let desc = result.descriptor as? ProcessCapability {
// Pin child to specific CPUs
try Cpuset.pin(desc, to: [0, 1])
// Get child's affinity
let childAffinity = try Cpuset.getAffinity(for: desc)
}Named Cpusets:
import Cpuset
// Create a new cpuset (inherits from current)
let setId = try Cpuset.create()
// Assign thread to cpuset
try Cpuset.assign(.currentThread, to: setId)
// Get cpuset ID for a target
let id = try Cpuset.getId(level: .cpuset, for: .currentThread)IRQ and Jail Affinity:
import Cpuset
// Get/set IRQ affinity (requires root)
let irqAffinity = try Cpuset.getIRQAffinity(16)
try Cpuset.setIRQAffinity(16, to: CPUSet(cpu: 0))
// Get/set jail affinity (requires root)
let jailAffinity = try Cpuset.getJailAffinity(5)
try Cpuset.setJailAffinity(5, to: CPUSet(cpus: [0, 1]))Targets:
| Target | Description |
|---|---|
.currentThread |
The calling thread |
.currentProcess |
The calling process |
.thread(tid) |
Specific thread ID |
.process(pid) |
Specific process ID |
.cpuset(id) |
Named cpuset |
.irq(num) |
IRQ number |
.jail(jid) |
Jail ID |
.domain(id) |
NUMA domain |
Levels:
| Level | Description |
|---|---|
.root |
All system CPUs |
.cpuset |
Available CPUs for target's cpuset |
.which |
Actual mask for specific target |
Swift interface to FreeBSD's DTrace dynamic tracing framework.
DTraceCore provides low-level bindings to libdtrace with move-only handle semantics:
import DTraceCore
// Open DTrace handle
var handle = try DTraceHandle.open()
// Set buffer sizes
try handle.setOption("bufsize", value: "4m")
try handle.setOption("aggsize", value: "4m")
// Compile and execute a D program
let program = try handle.compile("""
syscall:::entry
{
@[execname] = count();
}
""")
try handle.exec(program)
// Start tracing
try handle.go()
// Process trace data
while true {
let status = handle.work()
if status == .done || status == .error { break }
handle.sleep()
}
// Print results and stop
try handle.aggregateSnap()
try handle.aggregatePrint()
try handle.stop()DBlocks provides a type-safe Swift DSL for building DTrace scripts:
import DBlocks
// Simple one-liner: count syscalls for 5 seconds
try DBlocks.run(for: 5) {
Probe("syscall:::entry") {
Count(by: "probefunc")
}
}Result Builder DSL:
// Build complete scripts with Swift syntax
let script = DBlocks {
BEGIN {
Printf("Starting trace...")
}
Probe("syscall::read:entry") {
Target(.execname("nginx"))
Timestamp()
}
Probe("syscall::read:return") {
Target(.execname("nginx"))
When("self->ts")
Latency(by: "execname")
}
Tick(10, .seconds) {
Exit(0)
}
END {
Printf("Trace complete")
}
}
print(script.source) // View generated D code
try script.run() // Execute (requires root)Aggregations:
DBlocks {
Probe("syscall:::entry") {
// Simple count
Count()
// Count by key
Count(by: "probefunc")
// Named aggregation with multi-key
Count(by: ["execname", "probefunc"], into: "calls")
// Other aggregation functions
Sum("arg0", by: "execname", into: "bytes")
Min("arg0", by: "execname")
Max("arg0", by: "execname")
Avg("arg0", by: "execname")
Quantize("arg0", by: "execname") // Power-of-2 histogram
Lquantize("arg0", low: 0, high: 100, step: 10, by: "execname")
}
END {
Printa("calls") // Print specific aggregation
Clear("calls") // Clear aggregation
Trunc("calls", 10) // Keep top 10
Normalize("calls", 1_000_000) // Convert units
}
}Variables:
DBlocks {
BEGIN {
Assign(.global("total"), to: "0") // Global: total
}
Probe("syscall::read:entry") {
Assign(.thread("ts"), to: "timestamp") // Thread-local: self->ts
Assign(.clause("temp"), to: "arg0") // Clause-local: this->temp
}
Probe("syscall::read:return") {
When("self->ts")
Assign(.global("total"), to: "total + 1")
}
}Target Predicates:
// Filter by process attributes
Target(.pid(1234))
Target(.execname("nginx"))
Target(.uid(0))
Target(.gid(100))
Target(.jail(5))
Target(.processNameContains("http"))
// Combine with operators
Target(.execname("nginx") || .execname("apache"))
Target(.execname("postgres") && .uid(70))
Target(!.uid(0)) // Not root
// Custom D predicate
Target(.custom("arg0 > 1024"))
When("arg0 > 0") // Additional predicateSpecial Clauses:
DBlocks {
BEGIN { Printf("Start") } // Runs once at start
END { Printa() } // Runs once at end
ERROR { Printf("Error!") } // On DTrace errors
Tick(1, .seconds) { Printa() } // Every second
Tick(hz: 100) { Count() } // 100 Hz
Profile(hz: 997) { Count(by: "execname") } // CPU sampling
}Output Actions:
Probe("syscall::open:entry") {
Printf("%s[%d]: %s", "execname", "pid", "copyinstr(arg0)")
Trace("arg0")
Stack() // Kernel stack trace
Stack(userland: true) // User stack trace
}Predefined Scripts:
// Ready-to-use scripts
let script = DBlocks.syscallCounts(for: .execname("postgres"))
let script = DBlocks.fileOpens(for: .uid(0))
let script = DBlocks.cpuProfile(hz: 997)
let script = DBlocks.ioBytes(for: .execname("nginx"))
let script = DBlocks.syscallLatency("read", for: .pid(1234))Composable Scripts:
// Build scripts incrementally
var script = DBlocks()
script.add("syscall:::entry") {
Count(by: "probefunc")
}
script.merge(DBlocks.syscallCounts())
// Combine scripts with operators
let combined = DBlocks { BEGIN { Printf("Start") } }
+ DBlocks.syscallCounts(for: .execname("nginx"))
+ DBlocks { Tick(5, .seconds) { Exit(0) } }Session API:
// Full control over execution
var session = try DTraceSession.create()
session.output(to: .buffer(buffer))
try session.bufferSize("8m")
try session.jsonOutput()
session.add {
Probe("syscall:::entry") { Count(by: "probefunc") }
}
try session.run(for: 10) // Run for 10 seconds
// Or manual control
try session.start()
while session.isRunning {
_ = session.process()
session.wait()
}
try session.stop()
try session.printAggregations()Output Destinations:
session.output(to: .stdout)
session.output(to: .stderr)
session.output(to: .file("/tmp/trace.log"))
session.output(to: .null)
let buffer = DTraceOutputBuffer()
session.output(to: .buffer(buffer))
// ... run trace ...
let output = buffer.contentsKey Types:
DTraceHandle- Low-level libdtrace handle (~Copyable)DTraceSession- High-level session managerDBlocks- Type-safe D script builder (result builder)DTraceTarget- Process targeting predicatesProbe,BEGIN,END,Tick,Profile- Clause buildersCount,Sum,Min,Max,Avg,Quantize- Aggregation functionsPrintf,Trace,Stack,Timestamp,Latency- Action helpers
Dependency-free C library for parsing MAC labels.
#include <maclabel_parser.h>
// Parse label data
maclabel_parser_t parser;
maclabel_parser_init(&parser, label_data, label_len);
// Iterate over key-value pairs
const char *key, *value;
size_t key_len, value_len;
while (maclabel_parser_next(&parser, &key, &key_len, &value, &value_len) == 0) {
printf("%.*s = %.*s\n", (int)key_len, key, (int)value_len, value);
}This library is designed for use in kernel modules, boot loaders, or other environments where Swift or the full FreeBSDKit framework is not available.
All descriptor types are move-only to prevent common resource management bugs:
var file = FileCapability(fd)
let copy = file // Ownership transfers, 'file' is now invalid
// file.read() // Compile error: 'file' used after being consumedResources can be consumed to extract the underlying handle:
var socket = SocketCapability(fd)
let rawFd: Int32 = socket.take() // Socket is consumed, caller owns fd
// Caller is now responsible for closing rawFdThe framework is designed around Capsicum's capability model:
// Open resources before entering capability mode
var configDir = try DirectoryCapability.open(path: "/etc/myapp")
var dataDir = try DirectoryCapability.open(path: "/var/myapp")
var logFile = try FileCapability.open(path: "/var/log/myapp.log", flags: [.writeOnly, .append])
// Limit rights to minimum needed
_ = configDir.limit(rights: CapsicumRightSet(rights: [.read, .lookup]))
_ = dataDir.limit(rights: CapsicumRightSet(rights: [.read, .write, .lookup, .create]))
_ = logFile.limit(rights: CapsicumRightSet(rights: [.write]))
// Enter sandbox
try Capsicum.enter()
// Now only operations on these descriptors with their limited rights are allowedSeveral C modules provide access to macros and inline functions that Swift cannot import directly:
| Module | Purpose |
|---|---|
CCapsicum |
Capsicum rights macros and helper functions |
CCasper |
Casper (libcasper) service wrappers |
CJails |
Jail flag constants and wrapper functions |
CProcessDescriptor |
Process descriptor (pdfork) functions |
CEventDescriptor |
Event notification functions |
CDeviceIoctl |
Device ioctl constants (FIONREAD, DIOCGSECTORSIZE, etc.) |
CProcctl |
Process control constants and structures |
CACL |
ACL constants and type definitions |
CRctl |
Resource control syscall wrappers |
CCpuset |
CPU affinity macros and syscall wrappers |
CSignal |
Signal handling macros |
CExtendedAttributes |
Extended attribute constants |
CDTrace |
DTrace libdtrace wrappers and constants (used by DTraceCore/DBlocks) |
swift testTests require root privileges for some Capsicum and jail functionality. Tests that require elevated privileges are skipped when running as a regular user.
BSD-2-Clause
FreeBSDKit intentionally does not:
- Provide cross-platform compatibility
- Abstract away FreeBSD-specific semantics
- Support non-FreeBSD operating systems
This project embraces FreeBSD's identity and unique features rather than hiding them behind portable abstractions.