`
xumingyong
  • 浏览: 175799 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Use Erlang NIF to snoop, capture packets(in Windows XP), in OTP-R13B04

阅读更多

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:

  • The function prototypes of the NIFs have changed to expect argc and argv arguments. The arity of a NIF is by that no longer limited to 3.
  • enif_get_data renamed as enif_priv_data.
  • enif_make_string got a third argument for character encoding.

----------------------------------------------------------------------------------

 

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 xumingyong@gmail.com
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。

 

 

 

 

 

分享到:
评论
3 楼 mryufeng 2010-03-05  
解释epmd等2进制协议是erlang的拿手呀
2 楼 xumingyong 2010-03-03  
GeoffCant在git clone git://git.erlang.geek.nz/bgrep.git中用erlang binary实现了IPV4的解析。

erlsnoop是用纯C实现的,用erl应该会更简单,只是要实现一个完整的EPMD协议还是挺麻烦的。
1 楼 mryufeng 2010-03-02  
最好是做个erlsnoop兼容的 参看 http://mryufeng.iteye.com/blog/167695

这样就太方便大家了...

相关推荐

Global site tag (gtag.js) - Google Analytics