I have resolved this, and figure I will leave my solution here for the next person.
Firstly we create the socket:
function get_socket()::IOStream
#fd = ccall(:socket, Cint, (Cint, Cint, Cint), AF_PACKET, SOCK_RAW, hton(IPPROTO_RAW))
fd = ccall(:socket, Cint, (Cint, Cint, Cint), Int32(17), Int32(3), 0xff00)
if fd == -1
@error "Failed to open socket" errno=Base.Libc.errno()
end
return fdio(fd)
end
Then in order to send data we need to use the sendto() function sendto(2), and to do that across a connectionless socket (like a raw socket) you’ll need to create a sockaddr_ll struct:
struct Sockaddr_ll
sll_family::Cushort
sll_protocol::Cushort
sll_ifindex::Cint
sll_hatype::Cushort
sll_pkttype::Cuchar
sll_halen::Cuchar
sll_addr::NTuple{6, Cuchar}
end
and the sendto ccall too:
bytes = ccall(:sendto, Cint, (Cint, Ptr{UInt8}, Csize_t, Cint, Ptr{Sockaddr_ll}, Cint), sockfd, packet, length(packet), 0, Ref(sockaddr_ll), sizeof(sockaddr_ll))
where sockfd is the output of get_socket(), packet is a Vector{UInt8}.
In my implementation I have hard-coded the values of Sockaddr_ll (other than interface and addr):
function Sockaddr_ll(;
sll_family::Cushort=hton(UInt16(3)),#hton(AF_PACKET),
sll_protocol::Cushort=hton(0x0800),#hton(ETH_P_IP),
sll_ifindex::Cint, # Interface id here
sll_hatype::Cushort=hton(Cushort(1)),#hton(ARPHRD_ETHER),
sll_pkttype::Cuchar=Cuchar(0), # This field doesn't matter for sending packets
sll_halen::Cuchar=Cuchar(6),#ETH_ALEN,
sll_addr::NTuple{6, Cuchar} # Mac address here
)
new(sll_family, sll_protocol, sll_ifindex, sll_hatype, sll_pkttype, sll_halen, sll_addr)
end
You can use the if_nametoindex function from c to get the interface name.