Use Errlang NIF to snoop,capture packets(in Windows XP),in OTP-13B 04


1.紹介
 
前のブログhttp://xumingyong.iteye.com/blog/586743では、Errlang/OTP-133 B 03において、nifを使ってネットワークパケットをつかむ機能を実現しましたが、R 13 B 04バージョンにおいて、NIFインターフェースの形式が変化しています.
---------------------------------------
The NIF concept was introduced in R 13 B 03 as an EXPERIMENTAL feature.The interfaces may be changed in any way in comming releass.The pln is however to lift the experimental label and mantain interface backward partity
Incomptible changes in R 13 B 04:
  • The function prototypes of the NIFs have changed to expect argc and argv argv argments.The Rity of a NIF is by thatのlonger limited to 3.
  • enif_ゲットするdata renamed as enif_prvdata.
  • enif_メークstring got a third argment for character encoding.
  • --------------------------------------------
     
    2.nif_R 13 B 04.
     
    主に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
     
    主な変化はR 13 B 03におけるon_である.ロード関数の戻り値はtrueでなければなりません.R 13 B 04には不要です.
     
    %%% 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を見てください.
     
    また、R 13 B 04のlib\tools-2.6.5.1\emacsディレクトリの下で、erlang-skers.elとerlang-skels-old.elの二つのファイルが不足しています.EMACSは正常に文法ハイライト表示ができなくなり、ソースからこの二つのファイルをこのディレクトリにコピーすればいいです.小さなバグ.