Created
April 9, 2022 18:37
-
-
Save DavideGalilei/128e2d390f24992b0e3a3d83a291bad0 to your computer and use it in GitHub Desktop.
ARP network request in Nim (local ip address to mac address)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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