Here is a functioning example that lets me write data how I want. This program must either be run as root, or using the CAP_NET_RAW capability (man)
const std = @import("std");
const Io = std.Io;
const zig_raw_socket = @import("zig_raw_socket");
pub fn main() !void {
const socket: i32 = @intCast(std.os.linux.socket(
std.posix.AF.PACKET,
std.posix.SOCK.RAW,
0,
));
// hardcoded interface name for test
const ifname = "enp7s0";
var ifr: std.posix.ifreq = std.mem.zeroInit(std.posix.ifreq, .{});
@memcpy(ifr.ifrn.name[0..ifname.len], ifname);
ifr.ifrn.name[ifname.len] = 0;
try std.posix.ioctl_SIOCGIFINDEX(socket, &ifr);
const ifindex: i32 = ifr.ifru.ivalue;
var rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(&ifr)));
switch (rval) {
.SUCCESS => {},
else => {
return error.NicError;
},
}
ifr.ifru.flags.BROADCAST = true;
ifr.ifru.flags.PROMISC = true;
rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr)));
switch (rval) {
.SUCCESS => {},
else => {
return error.NicError;
},
}
const sockaddr_ll = std.posix.sockaddr.ll{
.family = std.posix.AF.PACKET,
.ifindex = ifindex,
.protocol = std.mem.nativeToBig(u16, @as(u16, std.os.linux.ETH.P.IP)),
.halen = 0, //not used
.addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, //not used
.pkttype = 0, //not used
.hatype = 0, //not used
};
_ = std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll)));
const payload: []const u8 = "raw bytes sent over the wire";
_ = std.os.linux.sendto(
socket,
payload.ptr,
payload.len,
0,
@ptrCast(&sockaddr_ll),
@sizeOf(@TypeOf(sockaddr_ll)),
);
}
Here is the full strace output:
execve("./zig-out/bin/zig_raw_socket", ["./zig-out/bin/zig_raw_socket"], 0x7fff87e84380 /* 24 vars */) = 0
mmap(NULL, 262223, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efee2aa2000
arch_prctl(ARCH_SET_FS, 0x7efee2ae2028) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0
sigaltstack({ss_sp=0x7efee2aa200a, ss_flags=0, ss_size=262144}, NULL) = 0
rt_sigaction(SIGSEGV, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
rt_sigaction(SIGILL, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
rt_sigaction(SIGFPE, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
socket(AF_PACKET, SOCK_RAW, htons(0 /* ETH_P_??? */)) = 3
ioctl(3, SIOCGIFINDEX, {ifr_name="enp7s0", ifr_ifindex=2}) = 0
ioctl(3, SIOCGIFFLAGS, {ifr_name="enp7s0", ifr_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_PROMISC|IFF_MULTICAST}) = 0
ioctl(3, SIOCSIFFLAGS, {ifr_name="enp7s0", ifr_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_PROMISC|IFF_MULTICAST}) = 0
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_IP), sll_ifindex=if_nametoindex("enp7s0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
sendto(3, "raw bytes sent over the wire", 28, 0, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_IP), sll_ifindex=if_nametoindex("enp7s0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 28
exit_group(0) = ?
+++ exited with 0 +++
I also captured a more complete strace from running this Go program, which shows both writing to and reading from raw sockets how I would like to be able to.
conn-message-trace.txt (42.5 KB)
Here is a screenshot of Wireshark showing the full packet that is sent:
As you can see, the bytes starting from raw bytes are interpreted as the ethernet headers.
Edit: and to be clear, I mentioned calling out to libc not because it cannot be done with the standard library right now (as shown), but because I wasn’t sure if the APIs required to do it are expected to be removed, since the POSIX APIs that @kj4tmp used have been removed on master.
