congratulations to me:
jeff@jeff-debian:~/repos/gatorcat$ git grep -r "posix"
jeff@jeff-debian:~/repos/gatorcat$
implementation that at least compiles:
pub const LinuxRawSocket = struct {
send_mutex: std.Io.Mutex = .init,
recv_mutex: std.Io.Mutex = .init,
socket: std.os.linux.socket_t,
pub const InitError = error{
/// The ifname argument provided is too long.
InterfaceNameTooLong,
/// Non-descriptive error. OS returned an error.
NicError,
};
pub fn init(
ifname: [:0]const u8,
) InitError!LinuxRawSocket {
if (ifname.len > std.os.linux.IFNAMESIZE - 1) return error.InterfaceNameTooLong;
assert(ifname.len <= std.os.linux.IFNAMESIZE - 1); // ifname too long
const ETH_P_ETHERCAT = @intFromEnum(telegram.EtherType.ETHERCAT);
const socket_rc: usize = std.os.linux.socket(
std.os.linux.AF.PACKET,
std.os.linux.SOCK.RAW,
std.mem.nativeToBig(u32, ETH_P_ETHERCAT),
);
switch (std.os.linux.errno(socket_rc)) {
.SUCCESS => {},
else => |err| {
logger.err("failed to open raw socket: {}", .{err});
return error.NicError;
},
}
const socket: std.os.linux.socket_t = @intCast(socket_rc);
var timeout_rcv = std.os.linux.timeval{
.sec = 0,
.usec = 1,
};
const rcvtime0_setsockopt_rc = std.os.linux.setsockopt(
socket,
std.os.linux.SOL.SOCKET,
std.os.linux.SO.RCVTIMEO,
std.mem.asBytes(&timeout_rcv),
std.mem.asBytes(&timeout_rcv).len,
);
switch (std.os.linux.errno(rcvtime0_setsockopt_rc)) {
.SUCCESS => {},
else => |err| {
logger.err("failed to setsockopt RCVTIME0: {}", .{err});
return error.NicError;
},
}
var timeout_snd = std.os.linux.timeval{
.sec = 0,
.usec = 1,
};
const sndtime0_setsockopt_rc = std.os.linux.setsockopt(
socket,
std.os.linux.SOL.SOCKET,
std.os.linux.SO.SNDTIMEO,
std.mem.asBytes(&timeout_snd).ptr,
std.mem.asBytes(&timeout_snd).len,
);
switch (std.os.linux.errno(sndtime0_setsockopt_rc)) {
.SUCCESS => {},
else => |err| {
logger.err("failed to setsockopt SNDTIME0: {}", .{err});
return error.NicError;
},
}
const dontroute_enable: c_int = 1;
const dontroute_setsockopt_rc = std.os.linux.setsockopt(
socket,
std.os.linux.SOL.SOCKET,
std.os.linux.SO.DONTROUTE,
std.mem.asBytes(&dontroute_enable).ptr,
std.mem.asBytes(&dontroute_enable).len,
);
switch (std.os.linux.errno(dontroute_setsockopt_rc)) {
.SUCCESS => {},
else => |err| {
logger.err("failed to setsockopt DONTROUTE: {}", .{err});
return error.NicError;
},
}
var ifr = std.mem.zeroes(std.os.linux.ifreq);
@memcpy(ifr.ifrn.name[0..ifname.len], ifname);
ifr.ifrn.name[ifname.len] = 0;
switch (std.os.linux.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
.SUCCESS => {},
else => |err| {
logger.err("failed to find interface with name: {s}, error: {}", .{ ifname, err });
return error.NicError;
},
}
const ifindex: i32 = ifr.ifru.ivalue;
var rval = std.os.linux.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.os.linux.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr)));
switch (rval) {
.SUCCESS => {},
else => {
return error.NicError;
},
}
const sockaddr_ll = std.os.linux.sockaddr.ll{
.family = std.os.linux.AF.PACKET,
.ifindex = ifindex,
.protocol = std.mem.nativeToBig(u16, @as(u16, ETH_P_ETHERCAT)),
.halen = 0, //not used
.addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, //not used
.pkttype = 0, //not used
.hatype = 0, //not used
};
switch (std.os.linux.errno(std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll))))) {
.SUCCESS => {},
else => |err| {
logger.err("failed to bind to interface: {}", .{err});
return error.NicError;
},
}
return LinuxRawSocket{
.socket = socket,
};
}
pub fn deinit(self: *LinuxRawSocket) void {
switch (std.os.linux.errno(std.os.linux.close(self.socket))) {
.BADF => unreachable, // Always a race condition.
.INTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
else => return,
}
}
pub fn send(ctx: *anyopaque, io: std.Io, bytes: []const u8) error{NicError}!void {
const self: *LinuxRawSocket = @ptrCast(@alignCast(ctx));
self.send_mutex.lockUncancelable(io);
defer self.send_mutex.unlock(io);
switch (std.os.linux.errno(std.os.linux.sendto(self.socket, bytes.ptr, bytes.len, 0, null, 0))) {
.SUCCESS => {},
else => |err| {
logger.err("failed to send packet: {}", .{err});
return error.NicError;
},
}
// TODO: look at return length?
}
pub fn recv(ctx: *anyopaque, io: std.Io, out: []u8) error{NicError}!usize {
const self: *LinuxRawSocket = @ptrCast(@alignCast(ctx));
self.recv_mutex.lockUncancelable(io);
defer self.recv_mutex.unlock(io);
while (true) {
const rc = std.os.linux.recvfrom(self.socket, out.ptr, out.len, std.os.linux.MSG.TRUNC, null, null);
switch (std.os.linux.errno(rc)) {
.SUCCESS => return @intCast(rc),
.INTR => continue,
else => |err| {
logger.err("raw socket recv error: {}", .{err});
return error.NicError;
},
}
}
}
pub fn linkLayer(self: *LinuxRawSocket) LinkLayer {
return LinkLayer{
.ptr = self,
.vtable = &.{ .send = send, .recv = recv },
};
}
};