1. 介绍
在上一篇博文http://xumingyong.iteye.com/blog/586743中,我在Erlang/OTP-R13B03中,用nif实现了抓网络包功能,但是由于在R13B04版本中,NIF接口形式发生了变化(见下一段在线文档摘要),所以我的源码也做了相应修改。
---------------------------------------------------------------------------------
The NIF concept was introduced in R13B03 as an EXPERIMENTAL feature. The interfaces may be changed in any way in coming releases. The plan is however to lift the experimental label and maintain interface backward compatibility from R14B.
Incompatible changes in R13B04:
----------------------------------------------------------------------------------
2. nif_R13B04.c
主要是nif函数的声明形式发生了变化。
/* This file used to create a Erlang NIF which sniffer network packets. */ #include "nif.h" pcap_t *devHandler = NULL; static ERL_NIF_TERM lookup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int i = 0; char errbuf[PCAP_ERRBUF_SIZE], str[1024], str1[1024], num[6]; pcap_if_t *alldevs, *d; memset(errbuf, 0, PCAP_ERRBUF_SIZE); memset(str, 0, 1024); memset(str1, 0, 1024); if (pcap_findalldevs_ex("rpcap://", NULL /* auth is not needed */, &alldevs, errbuf) == -1) return enif_make_string(env, errbuf, ERL_NIF_LATIN1); for(d= alldevs; d != NULL; d= d->next) { strcat(str, "{"); itoa(++i, num, 10); strcat(str, num); strcat(str, ", "); strcat(str, d->name); strcat(str, ", "); if (d->description) strcat(str, d->description); else strcat(str,"null"); strcat(str, "}, "); } pcap_freealldevs(alldevs); return enif_make_string(env, str, ERL_NIF_LATIN1); } static ERL_NIF_TERM opendevice(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { /* char dev[64]; */ char dev[1024]; int ip, res, i, j; char errbuf[PCAP_ERRBUF_SIZE]; pcap_if_t *alldevs, *d; memset(errbuf, 0, PCAP_ERRBUF_SIZE); memset(dev, 0, 1024); /* argv[0] = interface index */ res = enif_get_int(env, argv[0], &ip); if(res < 0) { return enif_make_string(env, "error argument", ERL_NIF_LATIN1); } if (pcap_findalldevs_ex("rpcap://", NULL /* auth is not needed */, &alldevs, errbuf) == -1) return enif_make_string(env, errbuf, ERL_NIF_LATIN1); for(d = alldevs, i = 1; d != NULL; d = d->next, i++) { if(i == ip) strcpy(dev, d->name); } pcap_freealldevs(alldevs); /* Parms: dev,snaplen,promisc,timeout_ms,errbuf * to_ms=0 means wait enough packet to arrive. */ devHandler = pcap_open_live(dev, 65535, 1, 0, errbuf); if(devHandler != NULL) return enif_make_atom(env, "ok"); else return enif_make_string(env, errbuf, ERL_NIF_LATIN1); } static ERL_NIF_TERM capture(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int i, res; struct pcap_pkthdr *pkthdr; const u_char *packet = NULL; ErlNifBinary bin; res = pcap_next_ex(devHandler, &pkthdr, &packet); if(res > 0) { enif_alloc_binary(env, pkthdr->len, &bin); for(i = 0; i < pkthdr->len; i++) { bin.data[i] = packet[i]; } bin.size = pkthdr->len; return enif_make_binary(env, &bin); } else if(res == 0) { return enif_make_string(env, "Timeout", ERL_NIF_LATIN1); } else if(res == -1) { return enif_make_string(env, pcap_geterr(devHandler), ERL_NIF_LATIN1); } else { return enif_make_string(env, "Unknown error", ERL_NIF_LATIN1); } } static ErlNifFunc funcs[] = { {"lookup", 0, lookup}, {"capture", 0, capture}, {"opendevice", 1, opendevice} }; ERL_NIF_INIT(nif,funcs,NULL,NULL,NULL,NULL)
3. nif.h
#include "erl_nif.h" #include "stdio.h" #include "pcap.h" #include "string.h" #include "ctype.h" #include "stdlib.h" #ifndef NIF_H #define NIF_H #ifdef __cplusplus extern "C" { #endif static ERL_NIF_TERM opendevice(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM capture(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM lookup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); #ifdef __cplusplus } #endif #endif
4. Makefile
# by [email protected] all: nif_dll nif.beam # for win32 dll compiler # CFLAGS -g used for to generate debug info, used by gdb command. CC = gcc CFLAGS = -shared INPUT = nif_R13B04.c wpcap.lib nif_dll: nif.h $(CC) $(CFLAGS) -o nif.dll $(INPUT) # for erlang beam compiler ERL = erlc .SUFFIXES: .erl .beam .erl.beam: $(ERL) $< clean: del *.beam *.dll erl_crash.dump
5. nif.erl
主要变化是:在R13B03中on_load函数的返回值必须是true,在R13B04中不需要了。
%%% nif sniffer -module(nif). -on_load(on_load/0). -compile(export_all). %%============================================================ %% Automatically loaded when load the module. on_load() -> ok = erlang:load_nif("./nif", 0). start(InterfaceIndex, CaptureNum) -> case opendevice(InterfaceIndex) of ok -> loop(CaptureNum); _Any -> {error, opendevice_fail} end. %% return a tuple, with{id, name, description}... lookup() -> error. %check the lookup() return, here use the Interface id, e.g. 1, 2 or ... opendevice(_Interface) -> error. capture() -> error. loop(0) -> io:format("Time:~w ", [time()]); loop(Count) -> Pkt = capture(), parser(Pkt), loop(Count-1). %%============================================================ parser(Pkt) when is_binary(Pkt) -> <<_Addr:12/binary-little, TypeOrLen:2/binary-little, _Res/binary-little>> = Pkt, case (TypeOrLen > <<16#05, 16#DC>>) of true -> parser(ethernet, Pkt); false -> parser(ieee, Pkt) end; parser(Pkt) when not(is_binary(Pkt)) -> io:format("---Not a binary data-------------------------------~n"), io:format("\t~p~n", [Pkt]). parser(ethernet, Pkt) -> <<Dst:6/binary-little, Src:6/binary-little, Type:2/binary-little, Res/binary-little>> = Pkt, io:format("---Ethernet II frame-------------------------------"), io:format("~nDst MAC:\t"), printHex(Dst), io:format("~nSrc MAC:\t"), printHex(Src), io:format("~nType:\t\t"), printHex(Type), printEthernetIIType(Type), io:format("~nData:\t\t"), printHex(Res), io:format("~n~n"); parser(ieee, Pkt) -> <<Dst:6/binary-little, Src:6/binary-little, Len:2/binary-little, DSAP:1/binary-little, SSAP:1/binary-little, Ctrl:1/binary-little, % ieee802.2 class 1, connectionless type. Normally be 0x03. Res/binary-little>> = Pkt, io:format("---IEEE802.3 frame-------------------------------"), io:format("~nDst MAC:\t"), printHex(Dst), io:format("~nSrc MAC:\t"), printHex(Src), io:format("~nTotal Length:\t"), printHex(Len), io:format("~nDSAP:\t\t"), printHex(DSAP), printIeeeDSAP(DSAP), io:format("~nSSAP:\t\t"), printHex(SSAP), printIeeeSSAP(SSAP), io:format("~nControl:\t"), printHex(Ctrl), io:format("~nData:\t\t"), printHex(Res), io:format("~n~n"). printHex(Bin) -> [io:format("~2.16.0B ",[X]) || X <- binary_to_list(Bin)]. printEthernetIIType(Type) -> case Type of <<16#08, 16#06>> -> io:format("\t= ARP"); <<16#08, 16#00>> -> io:format("\t= IP"); <<16#02, 16#00>> -> io:format("\t= PUP"); <<16#80, 16#35>> -> io:format("\t= RARP"); <<16#86, 16#DD>> -> io:format("\t= IPv6"); <<16#88, 16#63>> -> io:format("\t= PPPOE Discovery"); <<16#88, 16#64>> -> io:format("\t= PPPoE Session"); <<16#88, 16#47>> -> io:format("\t= MPLS Unicast"); <<16#88, 16#48>> -> io:format("\t= MPLS Multicast"); <<16#81, 16#37>> -> io:format("\t= IPX/SPX"); <<16#80, 16#00>> -> io:format("\t= IS-IS"); <<16#88, 16#09>> -> io:format("\t= LACP"); <<16#88, 16#8E>> -> io:format("\t= 802.1x"); <<16#81, 16#4C>> -> io:format("\t= SNMP"); <<16#88, 16#0B>> -> io:format("\t= PPP"); <<16#88, 16#A7>> -> io:format("\t= Cluster"); <<16#90, 16#00>> -> io:format("\t= Loopback"); <<16#90, 16#10>> -> io:format("\t= Vlan Tag"); <<16#90, 16#20>> -> io:format("\t= Vlan Tag"); _Any -> io:format("\t= unknown") end. printIeeeDSAP(Dsap) -> <<IG>> = Dsap, case IG bsr 7 of 0 -> io:format("\t I/G=0 (Individual address)"); 1 -> io:format("\t I/G=1 (Group address)") end. printIeeeSSAP(Ssap) -> <<CR>> = Ssap, case CR bsr 7 of 0 -> io:format("\t C/R=0 (Command)"); 1 -> io:format("\t C/R=1 (Response)") end.
6. 测试
省略,见上一篇博文http://xumingyong.iteye.com/blog/586743。
另外:需要注意的是,R13B04的lib\tools-2.6.5.1\emacs目录下,缺少了erlang-skels.el和erlang-skels-old.el两个文件,导致EMACS不能正常进行语法高亮显示,可在源码中将这两个文件拷贝到该目录下即可。一个小小的bug。