When working with PF_PACKET raw sockets, the caller needs to provide the source/destination MAC and IP addresses.
Playing with a spoofing DNS proxy, I got tired of hardcoding the addresses, then WTF'ing every time I switched networks. So I added some functions to procket to lookup the system network interface and its MAC and IP addresses.
Retrieving the MAC Address of an Interface
Under Linux, getting the MAC address of an interface involves calling an ioctl() with the request set to SIOCGIFHWADDR and passing in a struct ifreq.
Here is the code to do so in C:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <err.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <netinet/ether.h> int main(int argc, char *argv[]) { int s = -1; struct ifreq ifr = {0}; char *dev = NULL; struct sockaddr *sa; dev = strdup((argc == 2 ? argv[1] : "eth0")); if (dev == NULL) err(EXIT_FAILURE, "strdup"); if ( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) err(EXIT_FAILURE, "socket"); (void)memcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)-1); if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) err(EXIT_FAILURE, "ioctl"); sa = (struct sockaddr *)&ifr.ifr_hwaddr; (void)printf("%02x:%02x:%02x:%02x:%02x:%02x\n", sa->sa_data[0], sa->sa_data[1], sa->sa_data[2], sa->sa_data[3], sa->sa_data[4], sa->sa_data[5]); free(dev); exit (EXIT_SUCCESS); }
The equivalent in Erlang uses procket:ioctl/2
macaddress(Socket, Dev) -> {ok, <<_Ifname:16/bytes, ?PF_INET:16, % family SM1,SM2,SM3,SM4,SM5,SM6, % mac address _/binary>>} = procket:ioctl(Socket, ?SIOCGIFHWADDR, list_to_binary([ Dev, <<0:((15*8) - (length(Dev)*8)), 0:8, 0:128>> ])), {SM1,SM2,SM3,SM4,SM5,SM6}.Results may differ depending on the endian-ness of your platform.
Retrieving the IP Address of an Interface
The IP address of an interface can be obtained by another ioctl() with a request value of SIOCGIFADDR. In C:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <err.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <netinet/in.h> #include <arpa/inet.h> #include <net/if.h> int main(int argc, char *argv[]) { int s = -1; struct ifreq ifr = {0}; char *dev = NULL; struct sockaddr_in *sa; dev = strdup((argc == 2 ? argv[1] : "eth0")); if (dev == NULL) err(EXIT_FAILURE, "strdup"); if ( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) err(EXIT_FAILURE, "socket"); (void)memcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)-1); ifr.ifr_addr.sa_family = PF_INET; if (ioctl(s, SIOCGIFADDR, &ifr) < 0) err(EXIT_FAILURE, "ioctl"); sa = (struct sockaddr_in *)&ifr.ifr_hwaddr; (void)printf("%s\n", inet_ntoa(sa->sin_addr)); free(dev); exit (EXIT_SUCCESS); }
And the Erlang version:
ipv4address(Socket, Dev) -> {ok, <<_Ifname:16/bytes, ?PF_INET:16/native, % sin_family _:16, % sin_port SA1,SA2,SA3,SA4, % sin_addr _/binary>>} = procket:ioctl(Socket, ?SIOCGIFADDR, list_to_binary([ Dev, <<0:((15*8) - (length(Dev)*8)), 0:8>>, <<?PF_INET:16/native, % family 0:112>> ])), {SA1,SA2,SA3,SA4}.
Looking Up an IP Address in the ARP Cache
ARP cache lookups can be done by using:
ioctl(socket, SIOCGARP, struct arpreq);
But utilities on Linux just seem to parse /proc/net/arp:arplookup({IP1,IP2,IP3,IP4}) -> {ok, FD} = file:open("/proc/net/arp", [read,raw]), arploop(FD, inet_parse:ntoa({IP1,IP2,IP3,IP4})). arploop(FD, Address) -> case file:read_line(FD) of eof -> file:close(FD), not_found; {ok, Line} -> case lists:prefix(Address, Line) of true -> file:close(FD), M = string:tokens( lists:nth(?HWADDR_OFF, string:tokens(Line, " \n")), ":"), list_to_tuple([ erlang:list_to_integer(E, 16) || E <- M ]); false -> arploop(FD, Address) end end.
Getting a List of Interfaces
To get the list of interfaces on a system, yet another ioctl() is used, this time passing in SIOCGIFCONF and this structure as arguments:
struct ifconf { int ifc_len; /* Size of buffer. */ union { __caddr_t ifcu_buf; struct ifreq *ifcu_req; } ifc_ifcu; };Here is an example of retrieving the interface list in C.
The ioctl() takes, as an argument, a structure using a length and a pointer to a buffer. procket doesn't have a way of allocating a piece of memory though it could be modified to have an NIF that allocates a binary and returns the address of the binary as an integer. Functions could then pass in the memory address but a buggy piece of Erlang code might pass in the wrong value and crash the VM. (Edit: The erl_nif interface in Erlang R14A supports safely passing a reference to a block of memory between functions using enif_alloc_resource() to create a "Resource Object".)
Instead, I simply parse the output of /proc/net/dev. For example, here is the output on my laptop:
$ cat /proc/net/dev Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo: 526441 3620 0 0 0 0 0 0 526441 3620 0 0 0 0 0 0 eth0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 wifi0:10960093 26897 0 0 0 1578 0 0 734661 4876 0 0 0 0 0 0 ath0:14422892 12536 0 0 0 0 0 0 576599 4706 0 0 0 0 0 0Even nastier than the arp cache lookup, since I resorted to using regular expressions.
iflist() -> {ok, FD} = file:open("/proc/net/dev", [raw, read]), iflistloop(FD, []). iflistloop(FD, Ifs) -> case file:read_line(FD) of eof -> file:close(FD), Ifs; {ok, Line} -> iflistloop(FD, iflistmatch(Line, Ifs)) end. iflistmatch(Data, Ifs) -> case re:run(Data, "^\\s*([a-z]+[0-9]+):", [{capture, [1], list}]) of nomatch -> Ifs; {match, [If]} -> [If|Ifs] end.
Finding the Default Interface
In spood, I took the easy way and just sort of guessed. A proper solution would check the routing table. Instead I look for the first interface without a local IP address:
device() -> {ok, S} = procket:listen(0, [{protocol, udp}, {family, inet}, {type, dgram}]), [Dev|_] = [ If || If <- packet:iflist(), ipcheck(S, If) ], procket:close(S), Dev. ipcheck(S, If) -> try packet:ipv4address(S, If) of {127,_,_,_} -> false; {169,_,_,_} -> false; _ -> true catch error:_ -> false end.
Update: Using the inet Module
After having gone through all the above, I discovered that the inet module, which is part of the Erlang standard library, is able to retrieve information about the local interfaces.inet has 2 functions:
- getiflist/0: retrieve a list of all the local interfaces, e.g., {ok, ["eth0", "eth1"]}
- ifget/2: retrieve interface attributes. The arguments can be:
- addr: IP address of interface
- hwaddr: the MAC address of the interface. Works on Linux, doesn't work on Mac OS X (returns an empty list).
(Update: I've submitted a patch to get the MAC address on Mac OS X)
- dstaddr
- netmask
- broadcast
- mtu
- flags: returns the interface status, e.g., [up, broadcast, running, multicast]
- addr: IP address of interface
For example:
- To get a list of interfaces:
1> inet:getiflist(). {ok,["lo","eth0","eth1"]}
- To retrieve the IP and MAC addresses of an interface:
3> inet:ifget("eth0", [addr, hwaddr]). {ok,[{addr,{192,168,1,11}},{hwaddr,[0,11,22,33,44,55]}]}
Update2: Using inet:getifaddrs/0
As of R14B01, Erlang has a supported, cross-platform method for retrieving interface attributes. The functions returns a list holding the interface information. For example:1> inet:getifaddrs(). {ok,[{"lo", [{flags,[up,loopback,running]}, {hwaddr,[0,0,0,0,0,0]}, {addr,{127,0,0,1}}, {netmask,{255,0,0,0}}, {addr,{0,0,0,0,0,0,0,1}}, {netmask,{65535,65535,65535,65535,65535,65535,65535, 65535}}]}]
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.