Monday, November 19, 2007

NAGIOS (beurk) nrpe support for erlang

NAGIOS a pretty bad software uses a pretty bad protocol, but NAGIOS seems to be installed everywhere...
I needed a way to bypass its really poor scheduling process, and naturally erlang comes to my rescue... But everything is not so simple.

NRPE this horrible protocols uses fixed length packets (from the code the 2 last characters are never sets to 0, sizeof seems to be really misunderstood by the nagios developer :p).

But NRPE is another crap CRC32 code, and for efficiency and time saving I didn't wanted to reimplemented it in Erlang, so I wrote a nrpe_crc32 port...

Here's the crc32 code:


#include <unistd.h>
#include <stdio.h>
#include <string.h>

static unsigned long crc32_table[256];

typedef struct packet_struct
{
int16_t packet_version;
int16_t packet_type;
u_int32_t crc32_value;
int16_t result_code;
char buffer[MAX_PACKETBUFFER_LENGTH];
} packet;

/* build the crc table - must be called before calculating the crc value */
void generate_crc32_table(void){
unsigned long crc, poly;
int i, j;

poly=0xEDB88320L;
for(i=0;i<256;i++){
crc=i;
for(j=8;j>0;j--){
if(crc & 1)
crc=(crc>>1)^poly;
else
crc>>=1;
}
crc32_table[i]=crc;
}

return;
}

/* calculates the CRC 32 value for a buffer */
unsigned long calculate_crc32(char *buffer, unsigned int buffer_size){
register unsigned long crc;
int this_char;
int current_index;

crc=0xFFFFFFFF;

for(current_index=0;current_index this_char=(int)buffer[current_index];
crc=((crc>>8) & 0x00FFFFFF) ^ crc32_table[(crc ^ this_char) & 0xFF];
}

return (crc ^ 0xFFFFFFFF);
}

unsigned long test(const char *value)
{
return calculate_crc32((char *) value, strlen(value));
}


The port_driver:

/* port_driver.c */

#include "erl_driver.h"

extern void generate_crc32_table(void);
extern unsigned long calculate_crc32(char *, unsigned int);

typedef struct {
ErlDrvPort port;
} crc32_data;

static ErlDrvData crc32_drv_start(ErlDrvPort port, char *buff)
{
crc32_data* d = (crc32_data*)driver_alloc(sizeof(crc32_data));
d->port = port;

/* init crc32 table */
generate_crc32_table();
return (ErlDrvData) d;
}

static void crc32_drv_stop(ErlDrvData handle)
{
driver_free((char*)handle);
}

static void crc32_drv_output(ErlDrvData handle, char *buff, int bufflen)
{
crc32_data* d = (crc32_data*)handle;

char fn = buff[0];
char *arg = &buff[1];
unsigned long res;

switch (fn) {
case 1:
res = calculate_crc32(arg, bufflen - 1);
driver_output(d->port, (char *) &res, (sizeof(unsigned long)));
break;
default:
break;
}
}

ErlDrvEntry crc32_driver_entry = {
NULL, /* F_PTR init, N/A */
crc32_drv_start, /* L_PTR start, called when port is opened */
crc32_drv_stop, /* F_PTR stop, called when port is closed */
crc32_drv_output, /* F_PTR output, called when erlang has sent */
NULL, /* F_PTR ready_input, called when input descriptor ready */
NULL, /* F_PTR ready_output, called when output descriptor ready */
"crc32_drv", /* char *driver_name, the argument to open_port */
NULL, /* F_PTR finish, called when unloaded */
NULL, /* F_PTR control, port_command callback */
NULL, /* F_PTR timeout, reserved */
NULL /* F_PTR outputv, reserved */
};

DRIVER_INIT(crc32_drv) /* must match name in driver_entry */
{
return &crc32_driver_entry;
}


The crc32 module, initializing the lib, and calling the crc32 fun:


-module(crc32).

-export([start/0,init/1,compute/1]).

start() ->
start("crc32_drv").

start(SharedLib) ->
case erl_ddll:load_driver(".", SharedLib) of
ok -> ok;
{error, already_loaded} -> ok;
_E -> io:format("Error: ~p~n", [_E]),
exit({error, could_not_load_driver})
end,
spawn(?MODULE, init, [SharedLib]).

init(SharedLib) ->
register(?MODULE, self()),
Port = open_port({spawn, SharedLib}, [binary]),
loop(Port).


compute(X) ->
Bin = iolist_to_binary(X),
call_port(<<1, Bin/binary>>).

call_port(Msg) ->
?MODULE ! {call, self(), Msg},
receive
{?MODULE, Result} ->
Result
end.

loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, Msg}},
receive
{Port, {data, Data}} ->
Caller ! {?MODULE, decode(Data)}
end,
loop(Port);

stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;

{'EXIT', Port, Reason} ->
io:format("~p ~n", [Reason]),
exit(port_terminated)
end.

% Also, Valid for Network
decode(<<U:32/big-unsigned>> = Bin) when is_binary(Bin) ->
U.

decode(X) -> X.



Now the nrpe module, there you'll see why the nrpe is pure crap, fixed packet length for this type of tool is nonsense...


-module(nrpe).

-export([encode/1, request/1, crc32/1, connect/1, connect/2]).


encode(Bin) ->
{ Crc, _} = crc32:compute(Bin),
<<Crc:32, Bin>>.

request(Query) ->
Version = 2,
Type = 1,
Crc = 0,
Code = 0,
Blank = <<0:32/unit:256>>, % 1024 bytes
Q = iolist_to_binary(Query),
Padlen = 1024 - size(Q),
{C, _} = crc32:compute(
<<Version:16, Type:16, Crc:32, Code:16, Q/binary, 0, 0, Blank:Padlen/binary>>),
<<Version:16, Type:16, C:32, Code:16, Q/binary, 0, 0, Blank:Padlen/binary>>.


Building two binaries to only send one, is completely dump. But this is required... Thanks
to nrpe...



crc32(Bin) ->
{Crc, _} = crc32:compute(Bin),
{Crc, Bin}.

%
% send_packet.packet_version=(int16_t)htons(NRPE_PACKET_VERSION_2);
% send_packet.packet_type=(int16_t)htons(QUERY_PACKET);
% strncpy(&send_packet.buffer[0],query,MAX_PACKETBUFFER_LENGTH);
% send_packet.buffer[MAX_PACKETBUFFER_LENGTH-1]='\x0';
%
% send_packet.crc32_value=(u_int32_t)0L;
% calculated_crc32=calculate_crc32((char *)&send_packet,sizeof(send_packet));
% send_packet.crc32_value=(u_int32_t)htonl(calculated_crc32);

%% #define QUERY_PACKET 1 /* id code for a packet containing a query */
%% #define RESPONSE_PACKET 2 /* id code for a packet containing a response */
%%
%% #define NRPE_PACKET_VERSION_2 2 /* packet version identifier */
%% #define NRPE_PACKET_VERSION_1 1 /* older packet version identifiers (no longer supported) */
%%
%% #define MAX_PACKETBUFFER_LENGTH 1024 /* max amount of data we'll send in one query/response */

%% typedef struct packet_struct{
%% int16_t packet_version;
%% int16_t packet_type;
%% u_int32_t crc32_value;
%% int16_t result_code;
%% char buffer[MAX_PACKETBUFFER_LENGTH];
%% }packet;

connect(Host) ->
connect(Host, 5666).

connect(Host, Port) ->
case gen_tcp:connect(Host, Port, [binary, {active, false}]) of
{ok, Sock} ->
Query = request("test"),
send(Sock, Query),
io:format("Response: '~s'~n", [recv(Sock)]),
close(Sock);

{error, Error} ->
io:format("Connect-error: ~p~n", [Error])
end.

send(Sock, Data) ->
case gen_tcp:send(Sock, Data) of
ok ->
ok;

{error, Error} ->
io:format("send-error: ~p~n", [Error])
end.

recv(Sock) ->
case gen_tcp:recv(Sock, 0, 2000) of
{ok, Packet} ->
io:format("read: ~p~n", [Packet]),
decode(Packet);

{error, Error} ->
io:format("recv-error: ~p~n", [Error])
end.


close(Sock) ->
gen_tcp:close(Sock).


decode(<<Version:16, Type:16, Crc:32, 0, 0, Rest/binary>>) ->
io:format("Version: ~p, Type: ~p, Crc: ~p~n", [Version, Type, Crc]),
decode_response(Rest).

decode_response(Bin) ->
Len = msg_len(Bin, 0),
{Msg, _} = split_binary(Bin, Len),
Msg.


msg_len(<<0, Rest/binary>>, Len) ->
Len;
msg_len(Bin, Len) ->
{_, Next} = split_binary(Bin, 1),
msg_len(Next, Len + 1).


I hope someone will find this interesting :p

1 comment:

Peter Lemenkov said...

Interesting article. Thanks!

Sticky