Skip to content

Instantly share code, notes, and snippets.

@DavideGalilei
Created April 9, 2022 18:37
Show Gist options
  • Save DavideGalilei/128e2d390f24992b0e3a3d83a291bad0 to your computer and use it in GitHub Desktop.
Save DavideGalilei/128e2d390f24992b0e3a3d83a291bad0 to your computer and use it in GitHub Desktop.
ARP network request in Nim (local ip address to mac address)
# Credits to:
# https://stackoverflow.com/questions/16710040/arp-request-and-reply-using-c-socket-programming
# https://stackoverflow.com/questions/55203086/how-to-use-raw-socket-send-and-receive-arp-package-in-python
# https://www.howtogeek.com/70374/how-to-geek-explains-what-is-wake-on-lan-and-how-do-i-enable-it/
# https://datatracker.ietf.org/doc/html/rfc826
import std/[net, nativesockets, strutils, streams, strformat]
import posix
var
AF_PACKET {.importc, header: "<sys/socket.h>".}: cushort
ETH_P_ARP {.importc, header: "<linux/if_ether.h>".}: cushort
ETH_P_ALL {.importc, header: "<linux/if_ether.h>".}: cushort
ETH_P_IP {.importc, header: "<linux/if_ether.h>".}: uint16
ARPHRD_ETHER {.importc, header: "<linux/if_arp.h>".}: uint16
PACKET_OTHERHOST {.importc, header: "<linux/if_arp.h>".}: uint8
SIOCGIFINDEX {.importc, header: "<sys/ioctl.h>".}: cint
SIOCGIFHWADDR {.importc, header: "<sys/ioctl.h>".}: cint
SO_BINDTODEVICE {.importc, header: "<sys/socket.h>".}: cint
const
MAC_LENGTH = 6
IPV4_LENGTH = 4
type
ArpError = ref object of CatchableError
ArpPacket {.packed.} = object
# ARP header
destination: array[MAC_LENGTH, uint8]
sender: array[MAC_LENGTH, uint8]
proto_type: uint16
# ARP frame
hrd: uint16
pro: uint16
hln: uint8
pln: uint8
op: uint16
sha: array[MAC_LENGTH, uint8]
spa: array[IPV4_LENGTH, uint8]
tha: array[MAC_LENGTH, uint8]
tpa: array[IPV4_LENGTH, uint8]
SockAddr_ll = object
family: uint16
protocol: uint16
ifindex: uint32
hatype: uint16
pkttype: uint8
halen: uint8
`addr`: array[8, uint8]
proc ioctl(fd: cint, request: cint): cint {.varargs, cdecl, importc: "ioctl", header: "<sys/ioctl.h>".}
proc setsockopt(sockfd: cint, level: cint, optname: cint, optval: pointer, optlen: csize_t): cint {.cdecl, importc: "setsockopt", header: "<sys/socket.h>".}
proc getMac(interfaceName: string): string =
## Retrieve your pc's mac address
let s = createNativeSocket(
domain = Domain.AF_INET,
sockType = SockType.SOCK_DGRAM,
protocol = Protocol.IPPROTO_IP)
var buf: array[18 + MAC_LENGTH, char]
# Should be 256, but it doesn't segfault
# so I kept it smaller :)
copyMem(addr buf[0], unsafeAddr interfaceName[0], min(16, len(interfaceName)))
discard ioctl(s.cint, 0x8927, addr buf[0])
return cast[string](buf[18 ..< 18 + MAC_LENGTH])
proc decodeArp(source: string): ArpPacket =
let stream = newStringStream(source)
var destination = stream.readStr(MAC_LENGTH)
copyMem(addr result.destination[0], addr destination[0], MAC_LENGTH)
var source = stream.readStr(MAC_LENGTH)
copyMem(addr result.sender[0], addr source[0], MAC_LENGTH)
result.proto_type = stream.readUint16()
result.hrd = nativesockets.htons(stream.readUint16())
result.pro = nativesockets.htons(stream.readUint16())
result.hln = stream.readUint8() # hardware length (MAC_LENGTH)
result.pln = stream.readUint8() # ip length (IPV4_LENGTH)
result.op = nativesockets.htons(stream.readUint16())
var sender_mac = stream.readStr(int(result.hln))
copyMem(addr result.sha[0], addr sender_mac[0], result.hln)
var sender_ip = stream.readStr(int(result.pln))
copyMem(addr result.spa[0], addr sender_ip[0], result.pln)
var target_mac = stream.readStr(int(result.hln))
copyMem(addr result.tha[0], addr target_mac[0], result.hln)
var target_ip = stream.readStr(int(result.pln))
copyMem(addr result.tpa[0], addr target_ip[0], result.pln)
proc getMacFromIp(ipv4: string, iface: string): ArpPacket =
template raiseArpError(description: string) =
raise ArpError(msg: description & &" ({errno}) {strerror(errno)}")
var mac = getMac(iface)
let socket = createNativeSocket(
domain = AF_PACKET.cint,
sockType = posix.SOCK_RAW,
protocol = posix.htons(ETH_P_ARP).cint)
if cint(socket) == -1:
raiseArpError("Must be run as sudo to create a raw socket")
var sockaddr = SockAddr_ll(
family: AF_PACKET.uint16,
protocol: nativesockets.htons(ETH_P_ARP),
ifindex: if_nametoindex(iface).uint32,
hatype: nativesockets.htons(ARPHRD_ETHER),
pkttype: PACKET_OTHERHOST,
halen: 0)
copyMem(addr sockaddr.`addr`, addr mac[0], MAC_LENGTH)
let destMac = cast[array[6, uint8]](['\xff', '\xff', '\xff', '\xff', '\xff', '\xff']) # broadcast
let mac_array = cast[ptr array[MAC_LENGTH, uint8]](addr mac[0])[]
var packet = ArpPacket(
destination: destMac,
sender: mac_array,
proto_type: nativesockets.htons(ETH_P_ARP),
hrd: nativesockets.htons(1),
pro: nativesockets.htons(ETH_P_IP), # IPV4
hln: MAC_LENGTH,
pln: IPV4_LENGTH,
op: nativesockets.htons(1), # 1=REQUEST, 2=REPLY
sha: mac_array,
spa: getPrimaryIPAddr().address_v4,
tha: destMac,
tpa: parseIpAddress(ipv4).address_v4,
)
let sent = socket.sendto(addr packet, sizeof packet, 0, cast[ptr SockAddr](addr sockaddr), SockLen(sizeof SockAddr_ll))
if sent < 0:
raiseArpError("Could not send ARP packet")
var buf = newSeq[char](sizeof ArpPacket)
var zero: SockLen = 0
while true:
discard socket.recvfrom(addr buf[0], len buf, 0, nil, addr zero)
let arp = decodeArp(cast[string](buf))
# echo arp
if arp.op != 2 or arp.spa != packet.tpa:
continue
return arp
proc humanMac(source: array[MAC_LENGTH, byte | uint8 | char]): string =
result.add(toHex(source[0], 2))
for i in source[1 .. ^1]:
result.add(':')
result.add(toHex(i, 2))
let arp = getMacFromIp("192.168.178.155", "enp4s0")
echo arp
echo humanMac(arp.sha)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment