├── .gitignore ├── opts.c ├── devel ├── gen_msgpack_data.pl ├── dest_info.pl ├── lots-small-bad-queries.pl ├── info.pl ├── streaming-unpack-perl-test.pl ├── timeout.pl ├── ifdescr.pl └── sample-get-request-packet.txt ├── README.markdown ├── LICENSE ├── client_listen.c ├── carp.c ├── main.c ├── request_get.c ├── request_getopt.c ├── request_gettable.c ├── test_msgpack.c ├── cid_info.c ├── Makefile ├── util.c ├── oid_info.c ├── client_input.c ├── snmp.c ├── timers.c ├── request_info.c ├── client_requests_info.c ├── request_common.c ├── request_setopt.c ├── destination.c ├── test_ber.c ├── event_loop.c ├── sid_info.c ├── sqe.h ├── manual.mdwn ├── t └── queries.t ├── ber.c └── bsdqueue.h /.gitignore: -------------------------------------------------------------------------------- 1 | test_ber 2 | test_ber.dSYM/ 3 | test_msgpack 4 | test_msgpack.dSYM/ 5 | *.o 6 | .*.swp 7 | snmp-query-engine 8 | *.core 9 | core 10 | -------------------------------------------------------------------------------- /opts.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | int opt_quiet = 0; 12 | -------------------------------------------------------------------------------- /devel/gen_msgpack_data.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | 6 | use Data::MessagePack; 7 | 8 | my $mp = Data::MessagePack->new()->prefer_integer; 9 | 10 | #print $mp->pack([0,42,{key=>"value"},["1.2.3","4.5.6"]]); 11 | print $mp->pack(["ModeratelyLongStringLongerThan32Bytes",1,-2,{dict=>42}]); 12 | -------------------------------------------------------------------------------- /devel/dest_info.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use IO::Socket::INET; 6 | use Socket ':all'; 7 | use Time::HiRes 'sleep'; 8 | use Net::SNMP::QueryEngine::AnyEvent; 9 | use Data::Dump; 10 | 11 | our $sqe = Net::SNMP::QueryEngine::AnyEvent->new; 12 | 13 | $sqe->dest_info(sub { my ($h,$ok,$r) = @_; dd $r; }, "127.0.0.1", 161); 14 | $sqe->wait; 15 | -------------------------------------------------------------------------------- /devel/lots-small-bad-queries.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use IO::Socket::INET; 6 | use Socket ':all'; 7 | use Time::HiRes 'sleep'; 8 | 9 | our $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:7667", Proto => "tcp") 10 | or die "cannot connect to snmp-query-engine daemon: $!\n"; 11 | $conn->syswrite(" " x 1000); 12 | sleep 0.2; 13 | close $conn; 14 | -------------------------------------------------------------------------------- /devel/info.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use IO::Socket::INET; 6 | use Socket ':all'; 7 | use Time::HiRes 'sleep'; 8 | use Net::SNMP::QueryEngine::AnyEvent; 9 | use Data::Dump; 10 | 11 | our $sqe = Net::SNMP::QueryEngine::AnyEvent->new; 12 | 13 | if (@ARGV) { 14 | $sqe->cmd(sub { my ($h,$ok,$r) = @_; dd $r; }, 15 | 3, ++$sqe->{sqe}{cid}, 1); 16 | } else { 17 | $sqe->info(sub { my ($h,$ok,$r) = @_; dd $r; }); 18 | } 19 | $sqe->wait; 20 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | snmp-query-engine - multiplexing SNMP query engine 2 | 3 | The `snmp-query-engine` daemon accepts multiple 4 | client connections and performs SNMP queries 5 | towards multiple destinations on behalf of its clients, 6 | taking care of multiplexing and throttling the requests. 7 | This allows querying large number of devices for 8 | large amounts of SNMP information quickly, 9 | while controlling the load on the devices 10 | induced by multiple SNMP queries. 11 | 12 | See `manual.mdwn` for more. 13 | 14 | With regard to msgpack dependency: snmp-query-engine 15 | requires at least msgpack 0.5.7, previous versions 16 | have bugs. Unfortunately, msgpack website has changed 17 | its layout substantially, so it is not easy to find 18 | the release source anymore. One possibility is to 19 | fetch 0.5.7 from http://msgpack.org/releases/cpp/msgpack-0.5.7.tar.gz 20 | -------------------------------------------------------------------------------- /devel/streaming-unpack-perl-test.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | 6 | use Data::Dumper; 7 | use Data::MessagePack; 8 | use Data::HexDump; 9 | 10 | my $mp = Data::MessagePack->new()->prefer_integer; 11 | my $d = ""; 12 | $d .= $mp->pack({first=>1, string=>"The quick brown fox jumps over a fat lazy dog"}); 13 | $d .= $mp->pack({second=>1, num => 42, extras => {s => "log(log(log(x))) goes to infinity with great dignity"}}); 14 | print "TOTAL \$d: ", length($d), "\n"; 15 | my $up = Data::MessagePack::Unpacker->new; 16 | my $n = 0; 17 | 18 | while (length($d)) { 19 | my $c = substr($d, 0, 11, ""); 20 | up($c); 21 | } 22 | 23 | sub up 24 | { 25 | my $buf = shift; 26 | my $cl = length($buf); 27 | $n += $cl; 28 | #print HexDump $buf; 29 | again: 30 | my $o = $up->execute($buf, 0); 31 | if ($o) { 32 | print Dumper($up->data); 33 | print "READY $n $o\n"; 34 | substr($buf, 0, $o - ($n-$cl), ""); 35 | $up->reset; 36 | $cl = $n = length($buf); 37 | #print "new n: $n\n"; 38 | #print HexDump $buf; 39 | $o = 0; 40 | goto again if $n; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /devel/timeout.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use IO::Socket::INET; 6 | use Socket ':all'; 7 | use Time::HiRes 'sleep'; 8 | use Data::MessagePack; 9 | use Data::Dump; 10 | 11 | use constant RT_SETOPT => 1; 12 | use constant RT_INFO => 3; 13 | use constant RT_GET => 4; 14 | use constant RT_GETTABLE => 5; 15 | use constant RT_REPLY => 0x10; 16 | use constant RT_ERROR => 0x20; 17 | 18 | our $mp = Data::MessagePack->new()->prefer_integer; 19 | our $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:7667", Proto => "tcp") 20 | or die "cannot connect to snmp-query-engine daemon: $!\n"; 21 | 22 | my $target = shift || "127.0.0.1"; 23 | 24 | dd request([RT_SETOPT,3000,$target,161, {community=>"meow", timeout => 1500, retries => 2, version => 2}]); 25 | dd request([RT_GET,41,$target,161, ["1.3.6.1.2.1.1.5.0"]]); 26 | dd request([RT_INFO,3203]); 27 | 28 | close $conn; 29 | 30 | sub request 31 | { 32 | my $d = shift; 33 | my $p = $mp->pack($d); 34 | $conn->syswrite($p); 35 | my $reply; 36 | $conn->sysread($reply, 65536); 37 | $mp->unpack($reply); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /devel/ifdescr.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use IO::Socket::INET; 6 | use Socket ':all'; 7 | use Time::HiRes 'sleep'; 8 | use Data::MessagePack; 9 | use Data::Dump; 10 | 11 | use constant RT_SETOPT => 1; 12 | use constant RT_INFO => 3; 13 | use constant RT_GET => 4; 14 | use constant RT_GETTABLE => 5; 15 | use constant RT_REPLY => 0x10; 16 | use constant RT_ERROR => 0x20; 17 | 18 | our $mp = Data::MessagePack->new()->prefer_integer; 19 | our $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:7667", Proto => "tcp") 20 | or die "cannot connect to snmp-query-engine daemon: $!\n"; 21 | 22 | my $target = shift || "127.0.0.1"; 23 | 24 | dd request([RT_SETOPT,3201,$target,161,{version=>2}]); 25 | dd request([RT_GETTABLE,3200,$target,161,"1.3.6.1.2.1.2.2.1.2"]); 26 | dd request([RT_GETTABLE,3205,$target,161,"1.3.6.1.2.1.2.2.1.2",3]); 27 | dd request([RT_SETOPT,3201,$target,161,{version=>1}]); 28 | dd request([RT_GETTABLE,3202,$target,161,"1.3.6.1.2.1.2.2.1.2"]); 29 | sleep 0.1; # let timers expire and be cleaned up 30 | dd request([RT_INFO,3203]); 31 | 32 | close $conn; 33 | 34 | sub request 35 | { 36 | my $d = shift; 37 | my $p = $mp->pack($d); 38 | $conn->syswrite($p); 39 | my $reply; 40 | $conn->sysread($reply, 65536); 41 | $mp->unpack($reply); 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013, Anton Berezin "". All rights 2 | reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 22 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /client_listen.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | static void 12 | do_accept(struct socket_info *lsi) 13 | { 14 | struct sockaddr_in addr; 15 | int fd; 16 | unsigned len; 17 | 18 | len = sizeof(addr); 19 | if ( (fd = accept(lsi->fd, (struct sockaddr *)&addr, &len)) < 0) 20 | croak(1, "do_accept: accept"); 21 | if (!opt_quiet) 22 | fprintf(stderr, "incoming connection from %s!\n", inet_ntoa(addr.sin_addr)); 23 | new_client_connection(fd); 24 | } 25 | 26 | void 27 | create_listening_socket(int port) 28 | { 29 | int fd, on; 30 | struct sockaddr_in servaddr; 31 | struct socket_info *si; 32 | 33 | fd = socket(PF_INET, SOCK_STREAM, 0); 34 | if (fd < 0) 35 | croak(1, "create_listening_socket: socket"); 36 | 37 | bzero(&servaddr, sizeof(servaddr)); 38 | servaddr.sin_family = PF_INET; 39 | servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 40 | servaddr.sin_port = htons(port); 41 | 42 | on = 1; 43 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0) 44 | croak(1, "create_listening_socket: setsockopt of SO_REUSEADDR error"); 45 | 46 | if (bind(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) 47 | croak(1, "create_listening_socket: bind"); 48 | 49 | if (listen(fd, 1024) < 0) 50 | croak(1, "create_listening_socket: listen"); 51 | 52 | si = new_socket_info(fd); 53 | on_read(si, do_accept); 54 | } 55 | 56 | -------------------------------------------------------------------------------- /carp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | static void v(int is_croak, int is_x, int exit_code, const char *fmt, va_list ap); 12 | 13 | void 14 | croak(int exit_code, const char *fmt, ...) 15 | { 16 | va_list ap; 17 | va_start(ap, fmt); 18 | v(1, errno, exit_code, fmt, ap); 19 | va_end(ap); 20 | } 21 | 22 | void 23 | croakx(int exit_code, const char *fmt, ...) 24 | { 25 | va_list ap; 26 | va_start(ap, fmt); 27 | v(1, -1, exit_code, fmt, ap); 28 | va_end(ap); 29 | } 30 | 31 | void 32 | v(int is_croak, int use_errno, int exit_code, const char *fmt, va_list ap) 33 | { 34 | fprintf(stderr, "%s: ", thisprogname()); 35 | if (fmt != NULL) { 36 | vfprintf(stderr, fmt, ap); 37 | if (use_errno >= 0) 38 | fprintf(stderr, ": "); 39 | } 40 | if (use_errno >= 0) 41 | fprintf(stderr, "%s\n", strerror(use_errno)); 42 | else 43 | fprintf(stderr, "\n"); 44 | if (is_croak) 45 | exit(exit_code); 46 | } 47 | 48 | #if defined(__linux__) 49 | static char proggy[MAXPATHLEN]; 50 | #endif 51 | 52 | const char *thisprogname(void) 53 | { 54 | #if defined(__FreeBSD__) 55 | return getprogname(); 56 | #elif defined(__APPLE__) 57 | return getprogname(); 58 | #elif defined(__sun__) 59 | return getexecname(); 60 | #elif defined(__linux__) 61 | if (readlink("/proc/self/exe", proggy, MAXPATHLEN) != -1) 62 | return proggy; 63 | return ""; 64 | #else 65 | #error "unsupported OS" 66 | #endif 67 | } 68 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2014, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | void 12 | usage(char *err) 13 | { 14 | FILE *f = err ? stderr : stdout; 15 | if (err && *err) 16 | fprintf(f, "%s\n", err); 17 | fprintf(f, "Usage:\n"); 18 | fprintf(f, " %s [options]\n", thisprogname()); 19 | fprintf(f, "Usage parameters:\n"); 20 | fprintf(f, "\t-h\t\tproduce usage text and quit\n"); 21 | fprintf(f, "\t-p port\t\tlisten on port prt instead of default 7667\n"); 22 | if (0) { 23 | fprintf(f, "\t-f\t\tstay in foreground\n"); 24 | fprintf(f, "\t-p pidfile\tstore process ID in pidfile\n\t\t\t(default: do not store process ID)\n"); 25 | fprintf(f, "\t-l logfile\tprint statistics and verbose output\n\t\t\tinto logfile (default: use stdout)\n"); 26 | fprintf(f, "\t\t\tSend HUP signal to reopen the logfile\n"); 27 | } 28 | fprintf(f, "\t-q\t\tquiet operation\n"); 29 | exit(err ? 1 : 0); 30 | } 31 | 32 | int 33 | main(int argc, char **argv) 34 | { 35 | int o; 36 | int port = 7667; 37 | 38 | gettimeofday(&prog_start, NULL); 39 | bzero(&PS, sizeof(PS)); 40 | PS.max_packets_on_the_wire = 1000000; 41 | PS.program_version = 2014052300; 42 | 43 | while ( (o = getopt(argc, argv, "hp:q")) != -1) { 44 | switch (o) { 45 | case 'h': 46 | usage(NULL); 47 | break; 48 | case 'p': 49 | port = strtol(optarg, NULL, 10); 50 | break; 51 | case 'q': 52 | opt_quiet = 1; 53 | break; 54 | default: 55 | usage(""); 56 | } 57 | } 58 | argc -= optind; 59 | argv += optind; 60 | if (argc != 0) 61 | usage("extraneous arguments"); 62 | 63 | create_snmp_socket(); 64 | create_listening_socket(port); 65 | event_loop(); 66 | 67 | return 0; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /request_get.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | /* 12 | * get request: 13 | * [ 0, $cid, $ip, $port, [$oids] ] 14 | * 15 | */ 16 | 17 | int 18 | handle_get_request(struct socket_info *si, unsigned cid, msgpack_object *o) 19 | { 20 | unsigned port = 65536; 21 | struct in_addr ip; 22 | struct client_requests_info *cri; 23 | struct cid_info *ci; 24 | struct oid_info_head oi; 25 | 26 | if (o->via.array.size != 5) 27 | return error_reply(si, RT_GET|RT_ERROR, cid, "bad request length"); 28 | 29 | if (o->via.array.ptr[RI_GET_PORT].type == MSGPACK_OBJECT_POSITIVE_INTEGER) 30 | port = o->via.array.ptr[RI_GET_PORT].via.u64; 31 | if (port > 65535) 32 | return error_reply(si, RT_GET|RT_ERROR, cid, "bad port number"); 33 | 34 | if (!object2ip(&o->via.array.ptr[RI_GET_IP], &ip)) 35 | return error_reply(si, RT_GET|RT_ERROR, cid, "bad IP"); 36 | 37 | if (o->via.array.ptr[RI_GET_OIDS].type != MSGPACK_OBJECT_ARRAY) 38 | return error_reply(si, RT_GET|RT_ERROR, cid, "oids must be an array"); 39 | if (o->via.array.ptr[RI_GET_OIDS].via.array.size < 1) 40 | return error_reply(si, RT_GET|RT_ERROR, cid, "oids is an empty array"); 41 | 42 | cri = get_client_requests_info(&ip, port, si); 43 | ci = get_cid_info(cri, cid); 44 | if (ci->n_oids != 0) 45 | return error_reply(si, RT_GET|RT_ERROR, cid, "duplicate request id"); 46 | 47 | TAILQ_INIT(&oi); 48 | if ( (ci->n_oids = allocate_oid_info_list(&oi, &o->via.array.ptr[RI_GET_OIDS], ci)) == 0) { 49 | // XXX free allocated objects 50 | return error_reply(si, RT_GET|RT_ERROR, cid, "bad oid list"); 51 | } 52 | TAILQ_CONCAT(&cri->oids_to_query, &oi, oid_list); 53 | 54 | PS.get_requests++; 55 | si->PS.get_requests++; 56 | maybe_query_destination(cri->dest); 57 | return 0; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /request_getopt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | /* 12 | * getopt request: 13 | * [ RT_GETOPT, $cid, $ip, $port ] 14 | * 15 | * reply: 16 | * [ RT_GETOPT|RT_REPLY, $cid, { options } ] 17 | * where options has the following keys: 18 | * - ip: same as $ip in the request 19 | * - port: same as $port in the request 20 | * - community: snmp community 21 | * - version: snmp version (1 or 2) 22 | * - max_packets: max packets on the wire to this destination 23 | * - max_req_size: max request packet size, in bytes, including IP & UDP header 24 | * - timeout: timeout waiting for reply in milliseconds 25 | * - retries: number of times to send a request before giving up 26 | * - XXX more stuff 27 | * 28 | */ 29 | 30 | int 31 | handle_getopt_request(struct socket_info *si, unsigned cid, msgpack_object *o) 32 | { 33 | unsigned port = 65536; 34 | struct in_addr ip; 35 | struct client_requests_info *cri; 36 | msgpack_sbuffer* buffer; 37 | msgpack_packer* pk; 38 | 39 | if (o->via.array.size != 4) 40 | return error_reply(si, RT_GETOPT|RT_ERROR, cid, "bad request length"); 41 | 42 | if (o->via.array.ptr[RI_GETOPT_PORT].type == MSGPACK_OBJECT_POSITIVE_INTEGER) 43 | port = o->via.array.ptr[RI_GETOPT_PORT].via.u64; 44 | if (port > 65535) 45 | return error_reply(si, RT_GETOPT|RT_ERROR, cid, "bad port number"); 46 | 47 | if (!object2ip(&o->via.array.ptr[RI_GETOPT_IP], &ip)) 48 | return error_reply(si, RT_GETOPT|RT_ERROR, cid, "bad IP"); 49 | 50 | PS.getopt_requests++; 51 | si->PS.getopt_requests++; 52 | 53 | cri = get_client_requests_info(&ip, port, si); 54 | 55 | buffer = msgpack_sbuffer_new(); 56 | pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); 57 | msgpack_pack_array(pk, 3); 58 | msgpack_pack_int(pk, RT_GETOPT|RT_REPLY); 59 | msgpack_pack_int(pk, cid); 60 | msgpack_pack_options(pk, cri); 61 | 62 | tcp_send(si, buffer->data, buffer->size); 63 | msgpack_sbuffer_free(buffer); 64 | msgpack_packer_free(pk); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /request_gettable.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | /* 12 | * gettable request: 13 | * [ 0, $cid, $ip, $port, $oid, optional $max_repetitions ] 14 | * 15 | */ 16 | 17 | int 18 | handle_gettable_request(struct socket_info *si, unsigned cid, msgpack_object *o) 19 | { 20 | unsigned port = 65536; 21 | struct in_addr ip; 22 | struct client_requests_info *cri; 23 | struct cid_info *ci; 24 | struct oid_info *oi; 25 | int max_repetitions; 26 | 27 | if (o->via.array.size != 5 && o->via.array.size != 6) 28 | return error_reply(si, RT_GETTABLE|RT_ERROR, cid, "bad request length"); 29 | 30 | if (o->via.array.ptr[RI_GETTABLE_PORT].type == MSGPACK_OBJECT_POSITIVE_INTEGER) 31 | port = o->via.array.ptr[RI_GETTABLE_PORT].via.u64; 32 | if (port > 65535) 33 | return error_reply(si, RT_GETTABLE|RT_ERROR, cid, "bad port number"); 34 | 35 | if (!object2ip(&o->via.array.ptr[RI_GETTABLE_IP], &ip)) 36 | return error_reply(si, RT_GETTABLE|RT_ERROR, cid, "bad IP"); 37 | 38 | cri = get_client_requests_info(&ip, port, si); 39 | max_repetitions = cri->dest->max_repetitions; 40 | 41 | if (o->via.array.size == 6) { 42 | max_repetitions = -1; 43 | if (o->via.array.ptr[RI_GETTABLE_MREP].type == MSGPACK_OBJECT_POSITIVE_INTEGER) 44 | max_repetitions = o->via.array.ptr[RI_GETTABLE_MREP].via.u64; 45 | if (max_repetitions < 1 || max_repetitions > 255) 46 | return error_reply(si, RT_GETTABLE|RT_ERROR, cid, "bad max repetitions"); 47 | } 48 | 49 | ci = get_cid_info(cri, cid); 50 | if (ci->n_oids != 0) 51 | return error_reply(si, RT_GETTABLE|RT_ERROR, cid, "duplicate request id"); 52 | 53 | if (!( oi = allocate_oid_info(&o->via.array.ptr[RI_GETTABLE_OID], ci))) 54 | return error_reply(si, RT_GETTABLE|RT_ERROR, cid, "bad oid"); 55 | oi->last_known_table_entry = oi; 56 | oi->max_repetitions = max_repetitions; 57 | ci->n_oids++; 58 | 59 | TAILQ_INSERT_TAIL(&cri->oids_to_query, oi, oid_list); 60 | 61 | PS.gettable_requests++; 62 | si->PS.gettable_requests++; 63 | maybe_query_destination(cri->dest); 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /test_msgpack.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | void 12 | add_input_bytes(msgpack_unpacker *unpacker, char *buf, int sz) 13 | { 14 | msgpack_unpacked result; 15 | int got = 0; 16 | 17 | printf("reading %d bytes\n", sz); 18 | //getchar(); 19 | printf("ENT u(%u) f(%u) o(%u) p(%u)\n", (unsigned)unpacker->used, (unsigned)unpacker->free, (unsigned)unpacker->off, (unsigned)unpacker->parsed); 20 | msgpack_unpacker_reserve_buffer(unpacker, sz); 21 | printf("EXP u(%u) f(%u) o(%u) p(%u)\n", (unsigned)unpacker->used, (unsigned)unpacker->free, (unsigned)unpacker->off, (unsigned)unpacker->parsed); 22 | memcpy(msgpack_unpacker_buffer(unpacker), buf, sz); 23 | msgpack_unpacker_buffer_consumed(unpacker, sz); 24 | printf("CON u(%u) f(%u) o(%u) p(%u)\n", (unsigned)unpacker->used, (unsigned)unpacker->free, (unsigned)unpacker->off, (unsigned)unpacker->parsed); 25 | 26 | msgpack_unpacked_init(&result); 27 | while (msgpack_unpacker_next(unpacker, &result)) { 28 | got = 1; 29 | msgpack_object_print(stdout, result.data); 30 | printf("\n"); 31 | } 32 | if (got) { 33 | msgpack_unpacker_expand_buffer(unpacker, 0); 34 | printf("XXX u(%u) f(%u) o(%u) p(%u)\n", (unsigned)unpacker->used, (unsigned)unpacker->free, (unsigned)unpacker->off, (unsigned)unpacker->parsed); 35 | } 36 | msgpack_unpacked_destroy(&result); 37 | } 38 | 39 | int 40 | main(void) 41 | { 42 | // [0,42,{key=>"value"},["1.2.3","4.5.6"]] 43 | char *buf1 = "\x94\x00\x2a\x81\xa3\x6b\x65\x79\xa5\x76\x61\x6c\x75\x65\x92\xa5\x31\x2e\x32\x2e\x33\xa5\x34\x2e\x35\x2e\x36"; 44 | int buf1_len = 27; 45 | // ["ModeratelyLongStringLongerThan32Bytes",1,-2,{dict=>42}] 46 | char *buf2 = "\x94\xda\x00\x25\x4d\x6f\x64\x65\x72\x61\x74\x65\x6c\x79\x4c\x6f" 47 | "\x6e\x67\x53\x74\x72\x69\x6e\x67\x4c\x6f\x6e\x67\x65\x72\x54\x68" 48 | "\x61\x6e\x33\x32\x42\x79\x74\x65\x73\x01\xfe\x81\xa4\x64\x69\x63" 49 | "\x74\x2a"; 50 | int buf2_len = 50; 51 | msgpack_unpacker unpacker; 52 | 53 | msgpack_unpacker_init(&unpacker, MSGPACK_UNPACKER_INIT_BUFFER_SIZE); 54 | 55 | while (buf1_len) { 56 | add_input_bytes(&unpacker, buf1, 3); 57 | buf1_len -= 3; 58 | buf1 += 3; 59 | printf("buf1 len is now %d\n", buf1_len); 60 | } 61 | while (buf2_len) { 62 | add_input_bytes(&unpacker, buf2, 2); 63 | buf2_len -= 2; 64 | buf2 += 2; 65 | printf("buf2 len is now %d\n", buf2_len); 66 | } 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /devel/sample-get-request-packet.txt: -------------------------------------------------------------------------------- 1 | 00000000 30 3c 02 01 01 04 06 70 75 62 6c 69 63 a0 2f 02 |0<.....public./.| 2 | 00000010 04 6c ca 30 bc 02 01 00 02 01 00 30 21 30 0f 06 |.l.0.......0!0..| 3 | 00000020 0b 2b 06 01 02 01 02 02 01 02 87 69 05 00 30 0e |.+.........i..0.| 4 | 00000030 06 0a 2b 06 01 02 01 02 02 01 02 19 05 00 |..+........... | 5 | 6 | PDU Types 7 | 0xA0 = GetRequest 8 | 0xA1 = GetNextRequest 9 | 0xA2 = GetResponse 10 | 0xA3 = SetRequest 11 | 0xA4 = Trap 12 | 13 | Error statuses: 14 | 15 | 0 no error 16 | 1 too big 17 | 2 no such name 18 | 3 bad values 19 | 4 read only 20 | 5 generic error 21 | (v2 only) 22 | 6 no access 23 | 7 wrong type 24 | 8 wrong length 25 | 9 wrong encoding 26 | 10 wrong value 27 | 11 no creation 28 | 12 inconsistent value 29 | 13 resource unavailable 30 | 14 commit failed 31 | 15 undo failed 32 | 16 authorization error 33 | 17 not writable 34 | 18 inconsistent name 35 | 36 | 30 sequence 37 | 3c of length 3c = 60 bytes \ 38 | 02 integer (= SNMP version) | 39 | 01 of size 1 | 40 | 01 value 1 (= version 2c) | 41 | 04 octet string (= community) | 42 | 06 of size 6 | 43 | 70 75 62 6c 69 63 value "public" | 44 | a0 PDU = GetRequest | 45 | 2f of size 2f = 47 bytes \ | 46 | 02 integer (= request-id) | | 47 | 04 of size 4 | | 48 | 6c ca 30 bc value 1825190076 | | 49 | 02 integer (= error-status) | | 50 | 01 of size 1 | | 51 | 00 value 0 ("no error") | | 52 | 02 integer (= error-index) | | 53 | 01 of size 1 | | 54 | 00 value 0 | | 55 | 30 sequence \ | | 56 | 21 of size 0x21 = 33 bytes | | | 57 | 30 sequence \ | | | 58 | 0f of size 0x0f = 15 bytes | | | | 59 | 06 OID | | | | 60 | 0b of size 0x0b = 11 bytes | | | | 61 | 2b 06 01 02 01 02 02 01 02 87 69 | | | | 62 | = 1.3.6.1.2.1.2.2.1.2.1001 | | | | 63 | 05 00 null of size 0 = no value / | | | 64 | | | | 65 | 30 0e sequence of size 14 \ | | | 66 | 06 0a OID of size 10 | | | | 67 | 2b 06 01 02 01 02 02 01 02 19 | | | | 68 | = 1.3.6.1.2.1.2.2.1.2.25 | | | | 69 | 05 00 null of size 0 = no value / / / / 70 | -------------------------------------------------------------------------------- /cid_info.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | struct cid_info * 12 | get_cid_info(struct client_requests_info *cri, unsigned cid) 13 | { 14 | struct cid_info **ci_slot, *ci; 15 | 16 | JLI(ci_slot, cri->cid_info, cid); 17 | if (ci_slot == PJERR) 18 | croak(2, "get_cid_info: JLI(cid) failed"); 19 | if (!*ci_slot) { 20 | ci = malloc(sizeof(*ci)); 21 | if (!ci) 22 | croak(2, "get_cid_info: malloc(cid_info)"); 23 | 24 | PS.active_cid_infos++; 25 | PS.total_cid_infos++; 26 | cri->si->PS.active_cid_infos++; 27 | cri->si->PS.total_cid_infos++; 28 | 29 | bzero(ci, sizeof(*ci)); 30 | ci->cri = cri; 31 | ci->fd = cri->fd; 32 | ci->cid = cid; 33 | TAILQ_INIT(&ci->oids_done); 34 | *ci_slot = ci; 35 | } 36 | return *ci_slot; 37 | } 38 | 39 | int 40 | free_cid_info(struct cid_info *ci) 41 | { 42 | Word_t rc; 43 | 44 | PS.active_cid_infos--; 45 | ci->cri->si->PS.active_cid_infos--; 46 | JLD(rc, ci->cri->cid_info, ci->cid); 47 | free_oid_info_list(&ci->oids_done); 48 | free(ci); 49 | return 1; 50 | } 51 | 52 | void 53 | cid_reply(struct cid_info *ci, int type) 54 | { 55 | msgpack_sbuffer* buffer = msgpack_sbuffer_new(); 56 | msgpack_packer* pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); 57 | struct oid_info *oi; 58 | 59 | msgpack_pack_array(pk, 3); 60 | msgpack_pack_int(pk, type|RT_REPLY); 61 | msgpack_pack_int(pk, ci->cid); 62 | msgpack_pack_array(pk, ci->n_oids_done); 63 | TAILQ_FOREACH(oi, &ci->oids_done, oid_list) { 64 | msgpack_pack_array(pk, 2); 65 | msgpack_pack_oid(pk, oi->oid); 66 | msgpack_pack_ber(pk, oi->value); 67 | PS.oids_returned_to_client++; 68 | ci->cri->si->PS.oids_returned_to_client++; 69 | } 70 | tcp_send(ci->cri->si, buffer->data, buffer->size); 71 | msgpack_sbuffer_free(buffer); 72 | msgpack_packer_free(pk); 73 | free_cid_info(ci); 74 | } 75 | 76 | void 77 | dump_cid_info(msgpack_packer *pk, struct cid_info *ci) 78 | { 79 | char buf[512]; 80 | Word_t n_oids_done; 81 | struct oid_info *oi; 82 | 83 | #define PACK msgpack_pack_string(pk, buf) 84 | #define DUMPi(field) msgpack_pack_named_int(pk, #field, ci->field) 85 | #define DUMPs(field) msgpack_pack_named_string(pk, #field, ci->field) 86 | snprintf(buf, 512, "CID(%d)", ci->cid); PACK; 87 | msgpack_pack_map(pk, 8); 88 | 89 | msgpack_pack_string(pk, "cri"); 90 | snprintf(buf, 512, "CRI(%s:%d->%d)", inet_ntoa(ci->cri->dest->ip), ci->cri->dest->port, ci->cri->fd); PACK; 91 | 92 | DUMPi(cid); 93 | DUMPi(fd); 94 | DUMPi(n_oids); 95 | DUMPi(n_oids_being_queried); 96 | DUMPi(n_oids_done); 97 | 98 | n_oids_done = 0; 99 | TAILQ_FOREACH(oi, &ci->oids_done, oid_list) { 100 | n_oids_done++; 101 | } 102 | msgpack_pack_named_int(pk, "#OIDS_DONE", n_oids_done); 103 | 104 | msgpack_pack_string(pk, "@OIDS_DONE"); 105 | msgpack_pack_array(pk, n_oids_done); 106 | TAILQ_FOREACH(oi, &ci->oids_done, oid_list) { 107 | dump_oid_info(pk, oi); 108 | } 109 | 110 | #undef DUMPi 111 | #undef DUMPs 112 | #undef PACK 113 | } 114 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Part of `snmp-query-engine`. 3 | # 4 | # Copyright 2012-2013, Anton Berezin 5 | # Modified BSD license. 6 | # (See LICENSE file in the distribution.) 7 | # 8 | # 9 | CC?= cc 10 | OPTIMIZE= -O3 -g 11 | INCPATH= -I/usr/local/include -I/opt/local/include 12 | LIBPATH= -L/usr/local/lib -L/opt/local/lib 13 | CFLAGS= -Wall -Wno-unused-function -Werror $(OPTIMIZE) $(INCPATH) 14 | 15 | all: snmp-query-engine test_ber test_msgpack 16 | 17 | STDOBJ=event_loop.o carp.o client_input.o client_listen.o opts.o util.o destination.o \ 18 | client_requests_info.o cid_info.o ber.o oid_info.o sid_info.o \ 19 | snmp.o request_common.o request_setopt.o request_getopt.o \ 20 | request_info.o request_get.o request_gettable.o timers.o 21 | 22 | STDLINK=$(STDOBJ) $(LIBPATH) -lJudy -lmsgpackc 23 | 24 | clean: 25 | rm -f *.o snmp-query-engine test_ber test_msgpack *.core core 26 | 27 | snmp-query-engine: main.o $(STDOBJ) 28 | $(CC) $(CFLAGS) -o snmp-query-engine main.o $(STDLINK) 29 | 30 | main.o: main.c sqe.h 31 | $(CC) -c $(CFLAGS) -o main.o main.c 32 | 33 | event_loop.o: event_loop.c sqe.h 34 | $(CC) -c $(CFLAGS) -o event_loop.o event_loop.c 35 | 36 | carp.o: carp.c sqe.h 37 | $(CC) -c $(CFLAGS) -o carp.o carp.c 38 | 39 | opts.o: opts.c sqe.h 40 | $(CC) -c $(CFLAGS) -o opts.o opts.c 41 | 42 | util.o: util.c sqe.h 43 | $(CC) -c $(CFLAGS) -o util.o util.c 44 | 45 | client_input.o: client_input.c sqe.h 46 | $(CC) -c $(CFLAGS) -o client_input.o client_input.c 47 | 48 | client_listen.o: client_listen.c sqe.h 49 | $(CC) -c $(CFLAGS) -o client_listen.o client_listen.c 50 | 51 | destination.o: destination.c sqe.h 52 | $(CC) -c $(CFLAGS) -o destination.o destination.c 53 | 54 | client_requests_info.o: client_requests_info.c sqe.h 55 | $(CC) -c $(CFLAGS) -o client_requests_info.o client_requests_info.c 56 | 57 | cid_info.o: cid_info.c sqe.h 58 | $(CC) -c $(CFLAGS) -o cid_info.o cid_info.c 59 | 60 | sid_info.o: sid_info.c sqe.h 61 | $(CC) -c $(CFLAGS) -o sid_info.o sid_info.c 62 | 63 | ber.o: ber.c sqe.h 64 | $(CC) -c $(CFLAGS) -o ber.o ber.c 65 | 66 | snmp.o: snmp.c sqe.h 67 | $(CC) -c $(CFLAGS) -o snmp.o snmp.c 68 | 69 | oid_info.o: oid_info.c sqe.h 70 | $(CC) -c $(CFLAGS) -o oid_info.o oid_info.c 71 | 72 | request_common.o: request_common.c sqe.h 73 | $(CC) -c $(CFLAGS) -o request_common.o request_common.c 74 | 75 | request_setopt.o: request_setopt.c sqe.h 76 | $(CC) -c $(CFLAGS) -o request_setopt.o request_setopt.c 77 | 78 | request_getopt.o: request_getopt.c sqe.h 79 | $(CC) -c $(CFLAGS) -o request_getopt.o request_getopt.c 80 | 81 | request_info.o: request_info.c sqe.h 82 | $(CC) -c $(CFLAGS) -o request_info.o request_info.c 83 | 84 | request_get.o: request_get.c sqe.h 85 | $(CC) -c $(CFLAGS) -o request_get.o request_get.c 86 | 87 | request_gettable.o: request_gettable.c sqe.h 88 | $(CC) -c $(CFLAGS) -o request_gettable.o request_gettable.c 89 | 90 | timers.o: timers.c sqe.h 91 | $(CC) -c $(CFLAGS) -o timers.o timers.c 92 | 93 | test_ber: test_ber.c $(STDOBJ) 94 | $(CC) $(CFLAGS) -o test_ber test_ber.c $(STDLINK) 95 | 96 | test_msgpack: test_msgpack.c $(STDOBJ) 97 | $(CC) $(CFLAGS) -o test_msgpack test_msgpack.c $(STDLINK) 98 | 99 | test: snmp-query-engine 100 | prove t/queries.t 101 | 102 | test-details: snmp-query-engine 103 | perl t/queries.t 104 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | char * 12 | object_strdup(msgpack_object *o) 13 | { 14 | char *s; 15 | 16 | if (o->type == MSGPACK_OBJECT_BIN) { 17 | s = malloc(o->via.bin.size + 1); 18 | if (!s) 19 | croak(1, "object_strdup: malloc(%d)", o->via.bin.size + 1); 20 | memcpy(s, o->via.bin.ptr, o->via.bin.size); 21 | s[o->via.bin.size] = 0; 22 | } else if (o->type == MSGPACK_OBJECT_STR) { 23 | s = malloc(o->via.str.size + 1); 24 | if (!s) 25 | croak(1, "object_strdup: malloc(%d)", o->via.str.size + 1); 26 | memcpy(s, o->via.str.ptr, o->via.str.size); 27 | s[o->via.str.size] = 0; 28 | } else { 29 | return NULL; 30 | } 31 | return s; 32 | } 33 | 34 | char * 35 | object2string(msgpack_object *o, char s[], int bufsize) 36 | { 37 | switch (o->type) { 38 | case MSGPACK_OBJECT_BIN: 39 | if (o->via.bin.size >= bufsize) return NULL; 40 | memcpy(s, o->via.bin.ptr, o->via.bin.size); 41 | s[o->via.bin.size] = 0; 42 | break; 43 | case MSGPACK_OBJECT_STR: 44 | if (o->via.str.size >= bufsize) return NULL; 45 | memcpy(s, o->via.str.ptr, o->via.str.size); 46 | s[o->via.str.size] = 0; 47 | break; 48 | case MSGPACK_OBJECT_POSITIVE_INTEGER: 49 | if (snprintf(s, bufsize, "%"PRIu64, o->via.u64) >= bufsize) 50 | return NULL; 51 | break; 52 | default: 53 | return NULL; 54 | } 55 | 56 | return s; 57 | } 58 | 59 | int 60 | object_string_eq(msgpack_object *o, char *s) 61 | { 62 | int l; 63 | if (o->type == MSGPACK_OBJECT_BIN) { 64 | l = strlen(s); 65 | if (o->via.bin.size != l) return 0; 66 | return strncmp(o->via.bin.ptr, s, l) == 0; 67 | } else if (o->type == MSGPACK_OBJECT_STR) { 68 | l = strlen(s); 69 | if (o->via.str.size != l) return 0; 70 | return strncmp(o->via.str.ptr, s, l) == 0; 71 | } else { 72 | return 0; 73 | } 74 | } 75 | 76 | int 77 | object2ip(msgpack_object *o, struct in_addr *ip) 78 | { 79 | char buf[16]; 80 | if (!object2string(o, buf, 16)) return 0; 81 | return inet_pton(AF_INET, buf, ip); 82 | } 83 | 84 | static int sid_initialized = 0; 85 | static unsigned sid; 86 | 87 | unsigned 88 | next_sid(void) 89 | { 90 | struct timeval tv; 91 | if (!sid_initialized) { 92 | sid_initialized = 1; 93 | /* do we want better randomness than this - is it worth it? */ 94 | gettimeofday(&tv, NULL); 95 | sid = (tv.tv_sec % 500009) + tv.tv_usec; 96 | } 97 | sid++; 98 | sid &= 0xffffffff; 99 | if (sid == 0) 100 | sid++; 101 | return sid; 102 | } 103 | 104 | void 105 | dump_buf(FILE *f, void *buf, int len) 106 | { 107 | unsigned char *s = buf; 108 | int i; 109 | char o[68]; 110 | int pos[] = { 0,3,6,9,12,15,18,21,25,28,31,34,37,40,43,46 }; 111 | char hex[] = "0123456789abcdef"; 112 | 113 | while (len) { 114 | memset(o, ' ', 67); 115 | o[67] = 0; 116 | for (i = 0; i < 16 && len > 0; i++, len--, s++) { 117 | o[pos[i]] = hex[*s >> 4]; 118 | o[pos[i]+1] = hex[*s & 0x0f]; 119 | o[51+i] = isprint(*s) ? *s : '.'; 120 | } 121 | fprintf(f, "%s\n", o); 122 | } 123 | } 124 | 125 | static char oid2str_buf[4096]; 126 | 127 | char * 128 | oid2str(struct ber o) 129 | { 130 | if (!decode_string_oid(o.buf, o.max_len, oid2str_buf, 4096)) 131 | strcpy(oid2str_buf, "oid-too-long"); 132 | return oid2str_buf; 133 | } 134 | 135 | -------------------------------------------------------------------------------- /oid_info.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | int 12 | free_oid_info_list(struct oid_info_head *list) 13 | { 14 | struct oid_info *n1, *n2; 15 | 16 | n1 = TAILQ_FIRST(list); 17 | while (n1 != NULL) { 18 | n2 = TAILQ_NEXT(n1, oid_list); 19 | if (0){ 20 | fprintf(stderr, " freeing an oid (C:%u,S:%u) %s\n", n1->cid, n1->sid, oid2str(n1->oid)); 21 | } 22 | free(n1->oid.buf); 23 | free(n1->value.buf); 24 | PS.active_oid_infos--; 25 | free(n1); 26 | n1 = n2; 27 | } 28 | TAILQ_INIT(list); 29 | return 1; 30 | } 31 | 32 | int 33 | free_oid_info(struct oid_info *oi) 34 | { 35 | free(oi->oid.buf); 36 | free(oi->value.buf); 37 | PS.active_oid_infos--; 38 | free(oi); 39 | return 1; 40 | } 41 | 42 | int 43 | allocate_oid_info_list(struct oid_info_head *list, msgpack_object *o, struct cid_info *ci) 44 | { 45 | int i; 46 | struct oid_info *oi; 47 | char tmp_buf[2048]; 48 | struct ber e; 49 | 50 | for (i = 0; i < o->via.array.size; i++) { 51 | if (o->via.array.ptr[i].type == MSGPACK_OBJECT_BIN) { 52 | e = ber_init(tmp_buf, 2048); 53 | if (encode_string_oid(o->via.array.ptr[i].via.bin.ptr, o->via.array.ptr[i].via.bin.size, &e) < 0) goto not_good; 54 | } else if (o->via.array.ptr[i].type == MSGPACK_OBJECT_STR) { 55 | e = ber_init(tmp_buf, 2048); 56 | if (encode_string_oid(o->via.array.ptr[i].via.str.ptr, o->via.array.ptr[i].via.str.size, &e) < 0) goto not_good; 57 | } else { 58 | goto not_good; 59 | } 60 | 61 | oi = malloc(sizeof(*oi)); 62 | if (!oi) 63 | croak(2, "allocate_oid_info_list: malloc(oid_info)"); 64 | PS.active_oid_infos++; 65 | PS.total_oid_infos++; 66 | bzero(oi, sizeof(*oi)); 67 | oi->cid = ci->cid; 68 | oi->fd = ci->fd; 69 | oi->oid = ber_dup(&e); 70 | 71 | TAILQ_INSERT_TAIL(list, oi, oid_list); 72 | } 73 | return o->via.array.size; 74 | 75 | not_good: 76 | free_oid_info_list(list); 77 | return 0; 78 | } 79 | 80 | struct oid_info * 81 | allocate_oid_info(msgpack_object *o, struct cid_info *ci) 82 | { 83 | struct oid_info *oi; 84 | char tmp_buf[2048]; 85 | struct ber e; 86 | 87 | if (o->type == MSGPACK_OBJECT_BIN) { 88 | e = ber_init(tmp_buf, 2048); 89 | if (encode_string_oid(o->via.bin.ptr, o->via.bin.size, &e) < 0) return NULL; 90 | } else if (o->type == MSGPACK_OBJECT_STR) { 91 | e = ber_init(tmp_buf, 2048); 92 | if (encode_string_oid(o->via.str.ptr, o->via.str.size, &e) < 0) return NULL; 93 | } else { 94 | return NULL; 95 | } 96 | 97 | oi = malloc(sizeof(*oi)); 98 | if (!oi) 99 | croak(2, "allocate_oid_info: malloc(oid_info)"); 100 | PS.active_oid_infos++; 101 | PS.total_oid_infos++; 102 | bzero(oi, sizeof(*oi)); 103 | oi->cid = ci->cid; 104 | oi->fd = ci->fd; 105 | oi->oid = ber_dup(&e); 106 | 107 | return oi; 108 | } 109 | 110 | void 111 | dump_oid_info(msgpack_packer *pk, struct oid_info *oi) 112 | { 113 | #define DUMPi(field) msgpack_pack_named_int(pk, #field, oi->field) 114 | msgpack_pack_map(pk, 6); 115 | 116 | DUMPi(sid); 117 | DUMPi(cid); 118 | DUMPi(fd); 119 | DUMPi(max_repetitions); 120 | msgpack_pack_string(pk, "oid"); 121 | msgpack_pack_oid(pk, oi->oid); 122 | msgpack_pack_string(pk, "value"); 123 | if (!oi->value.buf) 124 | msgpack_pack_nil(pk); 125 | else 126 | msgpack_pack_ber(pk, oi->value); 127 | 128 | #undef DUMPi 129 | } 130 | -------------------------------------------------------------------------------- /client_input.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | static void 12 | client_gone(struct socket_info *si) 13 | { 14 | struct client_connection *c = si->udata; 15 | 16 | PS.active_client_connections--; 17 | 18 | si->udata = NULL; 19 | free_all_client_request_info_for_fd(si->fd); 20 | delete_socket_info(si); 21 | if (c) { 22 | msgpack_unpacked_destroy(&c->input); 23 | msgpack_unpacker_destroy(&c->unpacker); 24 | free(c); 25 | } 26 | if (!opt_quiet) 27 | fprintf(stderr, "client disconnect\n"); 28 | } 29 | 30 | static void 31 | client_input(struct socket_info *si) 32 | { 33 | struct client_connection *c = si->udata; 34 | char buf[1500]; 35 | int n; 36 | int got = 0; 37 | int ok; 38 | 39 | if (!c) 40 | croak(1, "client_input: no client_connection information"); 41 | if ( (n = read(si->fd, buf, 1500)) == -1) { 42 | switch (errno) { 43 | case EPIPE: 44 | client_gone(si); 45 | return; 46 | case ECONNRESET: 47 | client_gone(si); 48 | return; 49 | } 50 | croak(1, "client_input: read error"); 51 | } 52 | if (n == 0) { 53 | client_gone(si); 54 | return; 55 | } 56 | 57 | msgpack_unpacker_reserve_buffer(&c->unpacker, n); 58 | memcpy(msgpack_unpacker_buffer(&c->unpacker), buf, n); 59 | msgpack_unpacker_buffer_consumed(&c->unpacker, n); 60 | 61 | while (msgpack_unpacker_next(&c->unpacker, &c->input)) { 62 | msgpack_object *o; 63 | uint32_t cid; 64 | uint32_t type; 65 | 66 | got = 1; 67 | ok = -1; 68 | PS.client_requests++; 69 | si->PS.client_requests++; 70 | 71 | //if (!opt_quiet) { 72 | // printf("got client input: "); 73 | // msgpack_object_print(stdout, c->input.data); 74 | // printf("\n"); 75 | //} 76 | o = &c->input.data; 77 | if (o->type != MSGPACK_OBJECT_ARRAY) { 78 | error_reply(si, RT_ERROR, 0, "Request is not an array"); 79 | goto end; 80 | } 81 | if (o->via.array.size < 1) { 82 | error_reply(si, RT_ERROR, 0, "Request is an empty array"); 83 | goto end; 84 | } 85 | if (o->via.array.size < 2) { 86 | error_reply(si, RT_ERROR, 0, "Request without an id"); 87 | goto end; 88 | } 89 | if (o->via.array.ptr[RI_CID].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { 90 | error_reply(si, RT_ERROR, 0, "Request id is not a positive integer"); 91 | goto end; 92 | } 93 | cid = o->via.array.ptr[RI_CID].via.u64; 94 | if (o->via.array.ptr[RI_TYPE].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { 95 | error_reply(si, RT_ERROR, cid, "Request type is not a positive integer"); 96 | goto end; 97 | } 98 | type = o->via.array.ptr[RI_TYPE].via.u64; 99 | switch (type) { 100 | case RT_SETOPT: 101 | ok = handle_setopt_request(si, cid, o); 102 | break; 103 | case RT_GETOPT: 104 | ok = handle_getopt_request(si, cid, o); 105 | break; 106 | case RT_INFO: 107 | ok = handle_info_request(si, cid, o); 108 | break; 109 | case RT_DEST_INFO: 110 | ok = handle_dest_info_request(si, cid, o); 111 | break; 112 | case RT_GET: 113 | ok = handle_get_request(si, cid, o); 114 | break; 115 | case RT_GETTABLE: 116 | ok = handle_gettable_request(si, cid, o); 117 | break; 118 | default: 119 | error_reply(si, type|RT_ERROR, cid, "Unknown request type"); 120 | } 121 | end: 122 | if (ok < 0) { 123 | PS.invalid_requests++; 124 | si->PS.invalid_requests++; 125 | } 126 | } 127 | if (got) { 128 | msgpack_unpacker_expand_buffer(&c->unpacker, 0); 129 | } 130 | } 131 | 132 | void new_client_connection(int fd) 133 | { 134 | struct socket_info *si; 135 | struct client_connection *c; 136 | int flags; 137 | 138 | if ( (flags = fcntl(fd, F_GETFL, 0)) < 0) 139 | croak(1, "new_client_connection: fcntl(F_GETFL)"); 140 | if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) 141 | croak(1, "new_client_connection: fcntl(F_SETFL)"); 142 | #if defined(SO_NOSIGPIPE) 143 | { 144 | int no_sigpipe = 1; 145 | if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof (no_sigpipe)) < 0) 146 | croak(1, "new_client_connection: setsockopt of SO_NOSIGPIPE error"); 147 | } 148 | #endif 149 | si = new_socket_info(fd); 150 | c = malloc(sizeof(*c)); 151 | if (!c) 152 | croak(1, "new_client_connection: malloc(client_connection)"); 153 | bzero(c, sizeof(*c)); 154 | si->udata = c; 155 | 156 | PS.active_client_connections++; 157 | PS.total_client_connections++; 158 | 159 | msgpack_unpacker_init(&c->unpacker, MSGPACK_UNPACKER_INIT_BUFFER_SIZE); 160 | msgpack_unpacked_init(&c->input); 161 | on_eof(si, client_gone); 162 | on_read(si, client_input); 163 | } 164 | -------------------------------------------------------------------------------- /snmp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2014, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | static struct socket_info *snmp = NULL; 12 | 13 | static void 14 | snmp_process_datagram(struct socket_info *snmp, struct sockaddr_in *from, char *buf, int n) 15 | { 16 | struct ber enc, *e; 17 | unsigned char t; 18 | unsigned l; 19 | unsigned sid; 20 | char *trace; 21 | struct destination *dest; 22 | struct sid_info *si; 23 | 24 | PS.octets_received += n; 25 | dest = find_destination(&from->sin_addr, ntohs(from->sin_port)); 26 | if (!dest) { 27 | fprintf(stderr, "destination %s:%d is not knowing, ignoring packet\n", inet_ntoa(from->sin_addr), ntohs(from->sin_port)); 28 | return; 29 | } 30 | dest->octets_received += n; 31 | dest->packets_on_the_wire--; 32 | if (dest->packets_on_the_wire < 0) 33 | dest->packets_on_the_wire = 0; 34 | PS.packets_on_the_wire--; 35 | if (PS.packets_on_the_wire < 0) 36 | PS.packets_on_the_wire = 0; 37 | // fprintf(stderr, "%s: snmp_receive->(%d)\n", inet_ntoa(dest->ip), dest->packets_on_the_wire); 38 | 39 | enc = ber_init(buf, n); e = &enc; 40 | 41 | #define CHECK(prob, val) if ((val) < 0) { trace = "decoding" # prob; goto bad_snmp_packet; } 42 | CHECK("start sequence", decode_sequence(e, NULL)); 43 | CHECK("version", decode_integer(e, -1, NULL)); 44 | 45 | trace = "community type/len"; 46 | if (decode_type_len(e, &t, &l) < 0) goto bad_snmp_packet; 47 | trace = "community type"; 48 | if (t != AT_STRING) goto bad_snmp_packet; 49 | e->b += l; e->len += l; // XXX skip community 50 | 51 | CHECK("PDU", decode_composite(e, PDU_GET_RESPONSE, NULL)); 52 | CHECK("decoding request id", decode_integer(e, -1, &sid)); 53 | #undef CHECK 54 | 55 | si = find_sid_info(dest, sid); 56 | if (!si) { 57 | fprintf(stderr, "unable to find sid_info with sid %u, ignoring packet\n", sid); 58 | return; 59 | } 60 | 61 | // fprintf(stderr, "this packet appears to be legit, sid %u(%u)\n", sid, si->sid); 62 | if (process_sid_info_response(si, e)) 63 | free_sid_info(si); 64 | maybe_query_destination(dest); 65 | 66 | return; 67 | 68 | bad_snmp_packet: 69 | PS.bad_snmp_responses++; 70 | fprintf(stderr, "bad SNMP packet, ignoring: %s\n", trace); 71 | maybe_query_destination(dest); 72 | } 73 | 74 | static void 75 | snmp_receive(struct socket_info *snmp) 76 | { 77 | struct sockaddr_in from; 78 | socklen_t len; 79 | char buf[65000]; 80 | int n; 81 | 82 | while (1) { 83 | len = sizeof(from); 84 | if ( (n = recvfrom(snmp->fd, buf, 65000, 0, (struct sockaddr *)&from, &len)) < 0) { 85 | if (errno == EAGAIN) 86 | return; 87 | croak(1, "snmp_receive: recvfrom"); 88 | } 89 | snmp_process_datagram(snmp, &from, buf, n); 90 | } 91 | } 92 | 93 | void 94 | create_snmp_socket(void) 95 | { 96 | int fd; 97 | int n; 98 | int flags; 99 | 100 | if (snmp) 101 | croakx(1, "create_snmp_socket: socket already exists"); 102 | 103 | fd = socket(PF_INET, SOCK_DGRAM, 0); 104 | if (fd < 0) 105 | croak(1, "create_snmp_socket: socket"); 106 | 107 | flags = fcntl(fd, F_GETFL, 0); 108 | if (flags < 0) 109 | croak(1, "create_snmp_socket: fcntl(getfl)"); 110 | flags |= O_NONBLOCK; 111 | if (fcntl(fd, F_SETFL, flags) < 0) 112 | croak(1, "create_snmp_socket: fcntl(setfl)"); 113 | 114 | /* try a very large receive buffer size */ 115 | n = 100 * 1024 * 1024; 116 | while (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) < 0) 117 | n /= 2; 118 | PS.udp_receive_buffer_size = n; 119 | 120 | /* additionally, try a very large send buffer size :) */ 121 | n = 100 * 1024 * 1024; 122 | while (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)) < 0) 123 | n /= 2; 124 | PS.udp_send_buffer_size = n; 125 | 126 | snmp = new_socket_info(fd); 127 | on_read(snmp, snmp_receive); 128 | } 129 | 130 | void snmp_send(struct destination *dest, struct ber *packet) 131 | { 132 | destination_start_timing(dest); 133 | 134 | dest->packets_on_the_wire++; 135 | PS.packets_on_the_wire++; 136 | //fprintf(stderr, "%s: snmp_send->(%d)\n", inet_ntoa(dest->ip), dest->packets_on_the_wire); 137 | if (sendto(snmp->fd, packet->buf, packet->len, 0, 138 | (struct sockaddr *)&dest->dest_addr, 139 | sizeof(dest->dest_addr)) 140 | != packet->len) 141 | { 142 | if (errno == EAGAIN) { 143 | PS.udp_send_buffer_overflow++; 144 | return; 145 | } 146 | croak(1, "snmp_send: sendto"); 147 | } 148 | //fprintf(stderr, "UDP datagram of %d bytes sent to %s:%d\n", packet->len, inet_ntoa(dest->dest_addr.sin_addr), ntohs(dest->dest_addr.sin_port)); 149 | //dump_buf(stderr, packet->buf, packet->len); 150 | 151 | dest->octets_sent += packet->len; 152 | PS.octets_sent += packet->len; 153 | } 154 | 155 | -------------------------------------------------------------------------------- /timers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | struct timeval prog_start; 12 | static JudyL timers = NULL; 13 | 14 | struct timer * 15 | new_timer(struct timeval *when) 16 | { 17 | void **sec_slot; 18 | struct timer **usec_slot, *t; 19 | 20 | JLI(sec_slot, timers, when->tv_sec); 21 | if (sec_slot == PJERR) 22 | croak(2, "new_timer: JLI(timers) failed"); 23 | if (!*sec_slot) { 24 | PS.active_timers_sec++; 25 | PS.total_timers_sec++; 26 | } 27 | JLI(usec_slot, *sec_slot, when->tv_usec); 28 | if (usec_slot == PJERR) 29 | croak(2, "new_timer: JLI(*sec_slot) failed"); 30 | if (!*usec_slot) { 31 | PS.active_timers_usec++; 32 | PS.total_timers_usec++; 33 | t = malloc(sizeof(*t)); 34 | if (!t) 35 | croak(2, "new_timer: malloc(timer)"); 36 | bzero(t, sizeof(*t)); 37 | t->when = *when; 38 | TAILQ_INIT(&t->timed_out_sids); 39 | TAILQ_INIT(&t->throttled_destinations); 40 | *usec_slot = t; 41 | } 42 | return *usec_slot; 43 | } 44 | 45 | struct timer * 46 | find_timer(struct timeval *when) 47 | { 48 | void **sec_slot; 49 | struct timer **usec_slot; 50 | 51 | JLG(sec_slot, timers, when->tv_sec); 52 | if (sec_slot == PJERR) 53 | croak(2, "find_timer: JLG(timers) failed"); 54 | if (!sec_slot) return NULL; 55 | 56 | JLG(usec_slot, *sec_slot, when->tv_usec); 57 | if (usec_slot == PJERR) 58 | croak(2, "find_timer: JLG(*sec_slot) failed"); 59 | if (!usec_slot) return NULL; 60 | return *usec_slot; 61 | } 62 | 63 | int 64 | cleanup_timer(struct timer *t) 65 | { 66 | Word_t rc; 67 | void **sec_slot; 68 | struct timeval tv; 69 | 70 | if (!t) return 1; 71 | if (!TAILQ_EMPTY(&t->timed_out_sids)) return 0; 72 | if (!TAILQ_EMPTY(&t->throttled_destinations)) return 0; 73 | tv = t->when; 74 | free(t); 75 | 76 | JLG(sec_slot, timers, tv.tv_sec); 77 | if (sec_slot == PJERR) 78 | croak(2, "cleanup_timer: JLG(timers)#1 failed"); 79 | if (!sec_slot) return 1; 80 | JLD(rc, *sec_slot, tv.tv_usec); 81 | PS.active_timers_usec--; 82 | 83 | JLG(sec_slot, timers, tv.tv_sec); 84 | if (sec_slot == PJERR) 85 | croak(2, "cleanup_timer: JLG(timers)#2 failed"); 86 | if (!sec_slot) return 1; 87 | if (!*sec_slot) { 88 | JLD(rc, timers, tv.tv_sec); 89 | PS.active_timers_sec--; 90 | } 91 | return 1; 92 | } 93 | 94 | int 95 | ms_to_next_timer(void) 96 | { 97 | struct timer *t; 98 | struct timeval now; 99 | 100 | t = next_timer(); 101 | if (!t) return 1000; 102 | 103 | gettimeofday(&now, NULL); 104 | if (now.tv_sec > t->when.tv_sec) return 0; 105 | if (now.tv_sec == t->when.tv_sec) { 106 | if (now.tv_usec > t->when.tv_usec) return 0; 107 | return (t->when.tv_usec - now.tv_usec)/1000; 108 | } 109 | if (t->when.tv_sec - now.tv_sec > 5) return 1000; 110 | return 1000*(t->when.tv_sec - now.tv_sec) + ((int)t->when.tv_usec - (int)now.tv_usec)/1000; 111 | } 112 | 113 | struct timer * 114 | next_timer(void) 115 | { 116 | Word_t sec, usec; 117 | void **sec_slot; 118 | struct timer **usec_slot; 119 | 120 | again: 121 | sec = 0; 122 | JLF(sec_slot, timers, sec); 123 | if (!sec_slot) return NULL; 124 | usec = 0; 125 | JLF(usec_slot, *sec_slot, usec); 126 | if (!usec_slot) return NULL; 127 | if (cleanup_timer(*usec_slot)) goto again; 128 | return *usec_slot; 129 | } 130 | 131 | struct timeval last_unclog; 132 | 133 | void 134 | trigger_timers(void) 135 | { 136 | struct timer *t; 137 | struct timeval now; 138 | 139 | gettimeofday(&now, NULL); 140 | if (last_unclog.tv_sec < now.tv_sec) { 141 | last_unclog = now; 142 | unclog_all_destinations(); 143 | } 144 | 145 | again: 146 | t = next_timer(); 147 | if (!t) return; 148 | if (t->when.tv_sec > now.tv_sec) return; 149 | if (t->when.tv_sec == now.tv_sec && t->when.tv_usec > now.tv_usec) return; 150 | if (!TAILQ_EMPTY(&t->throttled_destinations)) { 151 | destination_timer(TAILQ_FIRST(&t->throttled_destinations)); 152 | goto again; 153 | } 154 | if (!TAILQ_EMPTY(&t->timed_out_sids)) { 155 | sid_timer(TAILQ_FIRST(&t->timed_out_sids)); 156 | goto again; 157 | } 158 | cleanup_timer(t); 159 | goto again; 160 | } 161 | 162 | void 163 | set_timeout(struct timeval *tv, int timeout) 164 | { 165 | struct timeval to; 166 | gettimeofday(tv, NULL); 167 | to.tv_sec = timeout / 1000; 168 | to.tv_usec = 1000 * (timeout % 1000); 169 | to.tv_usec += tv->tv_usec; 170 | to.tv_sec += tv->tv_sec; 171 | to.tv_sec += to.tv_usec / 1000000; 172 | to.tv_usec %= 1000000; 173 | *tv = to; 174 | } 175 | 176 | int64_t 177 | ms_passed_since(struct timeval *tv) 178 | { 179 | struct timeval now; 180 | 181 | if (!tv->tv_sec) return INT_MAX; 182 | gettimeofday(&now, NULL); 183 | return (now.tv_sec - tv->tv_sec) * 1000 + (now.tv_usec - tv->tv_usec) / 1000; 184 | } 185 | 186 | -------------------------------------------------------------------------------- /request_info.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | /* 12 | * info request: 13 | * [ 0, $cid ] 14 | * 15 | * dest_info request: 16 | * [ RT_DEST_INFO, $cid, $ip, $port ] 17 | * 18 | */ 19 | 20 | static int 21 | pack_stats(struct program_stats *PS, msgpack_packer *pk) 22 | { 23 | int n = 0; 24 | 25 | #define STAT(what) if (PS->what >= 0) { n++; if (pk) msgpack_pack_named_int(pk, #what, PS->what); } 26 | 27 | STAT(active_client_connections); 28 | STAT(total_client_connections); 29 | 30 | STAT(client_requests); 31 | STAT(invalid_requests); 32 | STAT(setopt_requests); 33 | STAT(getopt_requests); 34 | STAT(info_requests); 35 | STAT(get_requests); 36 | STAT(gettable_requests); 37 | STAT(dest_info_requests); 38 | 39 | STAT(snmp_retries); 40 | STAT(snmp_sends); 41 | STAT(snmp_v1_sends); 42 | STAT(snmp_v2c_sends); 43 | STAT(snmp_timeouts); 44 | STAT(udp_timeouts); 45 | STAT(bad_snmp_responses); 46 | STAT(good_snmp_responses); 47 | STAT(oids_non_increasing); 48 | STAT(oids_requested); 49 | STAT(oids_returned_from_snmp); 50 | STAT(oids_returned_to_client); 51 | STAT(oids_ignored); 52 | 53 | STAT(octets_received); 54 | STAT(octets_sent); 55 | 56 | STAT(active_timers_sec); 57 | STAT(active_timers_usec); 58 | STAT(total_timers_sec); 59 | STAT(total_timers_usec); 60 | STAT(uptime); 61 | 62 | STAT(active_sid_infos); 63 | STAT(total_sid_infos); 64 | STAT(active_oid_infos); 65 | STAT(total_oid_infos); 66 | STAT(active_cid_infos); 67 | STAT(total_cid_infos); 68 | STAT(active_cr_infos); 69 | STAT(total_cr_infos); 70 | 71 | STAT(destination_throttles); 72 | STAT(destination_ignores); 73 | 74 | STAT(udp_receive_buffer_size); 75 | STAT(udp_send_buffer_size); 76 | STAT(udp_send_buffer_overflow); 77 | STAT(packets_on_the_wire); 78 | STAT(max_packets_on_the_wire); 79 | STAT(global_throttles); 80 | STAT(program_version); 81 | 82 | #undef STAT 83 | return n; 84 | } 85 | 86 | int 87 | handle_info_request(struct socket_info *si, unsigned cid, msgpack_object *o) 88 | { 89 | msgpack_sbuffer* buffer; 90 | msgpack_packer* pk; 91 | char *key; 92 | int l; 93 | 94 | if (o->via.array.size < 2) 95 | return error_reply(si, RT_INFO|RT_ERROR, cid, "bad request length"); 96 | 97 | PS.info_requests++; 98 | si->PS.info_requests++; 99 | 100 | buffer = msgpack_sbuffer_new(); 101 | pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); 102 | msgpack_pack_array(pk, 3); 103 | msgpack_pack_int(pk, RT_INFO|RT_REPLY); 104 | msgpack_pack_int(pk, cid); 105 | 106 | if (o->via.array.size == 3 && 107 | o->via.array.ptr[2].type == MSGPACK_OBJECT_POSITIVE_INTEGER) 108 | { 109 | switch (o->via.array.ptr[2].via.u64) { 110 | case 1: /* dump destinations */ 111 | dump_all_destinations(pk); 112 | goto send; 113 | } 114 | } 115 | 116 | PS.uptime = ms_passed_since(&prog_start); 117 | si->PS.uptime = ms_passed_since(&si->created); 118 | 119 | msgpack_pack_map(pk, 2); 120 | 121 | key = "global"; 122 | l = strlen(key); 123 | msgpack_pack_bin(pk, l); 124 | msgpack_pack_bin_body(pk, key, l); 125 | msgpack_pack_map(pk, pack_stats(&PS, NULL)); 126 | pack_stats(&PS, pk); 127 | 128 | key = "connection"; 129 | l = strlen(key); 130 | msgpack_pack_bin(pk, l); 131 | msgpack_pack_bin_body(pk, key, l); 132 | msgpack_pack_map(pk, pack_stats(&si->PS, NULL)); 133 | pack_stats(&si->PS, pk); 134 | 135 | send: 136 | tcp_send(si, buffer->data, buffer->size); 137 | msgpack_sbuffer_free(buffer); 138 | msgpack_packer_free(pk); 139 | return 0; 140 | } 141 | 142 | static int 143 | pack_dest_stats(struct destination *dest, msgpack_packer *pk) 144 | { 145 | int n = 0; 146 | 147 | #define STAT(what) if (dest->what >= 0) { n++; if (pk) msgpack_pack_named_int(pk, #what, dest->what); } 148 | 149 | STAT(octets_received); 150 | STAT(octets_sent); 151 | 152 | #undef STAT 153 | return n; 154 | } 155 | 156 | int 157 | handle_dest_info_request(struct socket_info *si, unsigned cid, msgpack_object *o) 158 | { 159 | msgpack_sbuffer* buffer; 160 | msgpack_packer* pk; 161 | unsigned port = 65536; 162 | struct in_addr ip; 163 | struct client_requests_info *cri; 164 | 165 | if (o->via.array.size != 4) 166 | return error_reply(si, RT_DEST_INFO|RT_ERROR, cid, "bad request length"); 167 | 168 | if (o->via.array.ptr[RI_DEST_INFO_PORT].type == MSGPACK_OBJECT_POSITIVE_INTEGER) 169 | port = o->via.array.ptr[RI_DEST_INFO_PORT].via.u64; 170 | if (port > 65535) 171 | return error_reply(si, RT_DEST_INFO|RT_ERROR, cid, "bad port number"); 172 | 173 | if (!object2ip(&o->via.array.ptr[RI_DEST_INFO_IP], &ip)) 174 | return error_reply(si, RT_DEST_INFO|RT_ERROR, cid, "bad IP"); 175 | 176 | PS.dest_info_requests++; 177 | si->PS.dest_info_requests++; 178 | 179 | buffer = msgpack_sbuffer_new(); 180 | pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); 181 | msgpack_pack_array(pk, 3); 182 | msgpack_pack_int(pk, RT_DEST_INFO|RT_REPLY); 183 | msgpack_pack_int(pk, cid); 184 | 185 | cri = get_client_requests_info(&ip, port, si); 186 | msgpack_pack_map(pk, pack_dest_stats(cri->dest, NULL)); 187 | pack_dest_stats(cri->dest, pk); 188 | 189 | tcp_send(si, buffer->data, buffer->size); 190 | msgpack_sbuffer_free(buffer); 191 | msgpack_packer_free(pk); 192 | return 0; 193 | } 194 | -------------------------------------------------------------------------------- /client_requests_info.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | static JudyL dest_by_fd; // -> JudyL(by ip) -> JudyL(by port) -> destination 12 | 13 | struct client_requests_info * 14 | get_client_requests_info(struct in_addr *ip, unsigned port, struct socket_info *si) 15 | { 16 | struct destination *dest; 17 | void **fd_slot, **ip_slot; 18 | struct client_requests_info **cri_slot, **dest_cri_slot, *cri; 19 | 20 | dest = get_destination(ip, port); 21 | 22 | JLI(fd_slot, dest_by_fd, si->fd); 23 | if (fd_slot == PJERR) 24 | croak(2, "get_client_requests_info: JLI(fd) failed"); 25 | JLI(ip_slot, *fd_slot, ip->s_addr); 26 | if (ip_slot == PJERR) 27 | croak(2, "get_client_requests_info: JLI(ip) failed"); 28 | JLI(cri_slot, *ip_slot, port); 29 | if (cri_slot == PJERR) 30 | croak(2, "get_client_requests_info: JLI(port) failed"); 31 | if (!*cri_slot) { 32 | cri = malloc(sizeof(*cri)); 33 | if (!cri) 34 | croak(2, "get_client_requests_info: malloc(cri)"); 35 | 36 | PS.active_cr_infos++; 37 | PS.total_cr_infos++; 38 | si->PS.active_cr_infos++; 39 | si->PS.total_cr_infos++; 40 | 41 | bzero(cri, sizeof(*cri)); 42 | 43 | cri->version = 1; /* 2c is the default */ 44 | strcpy(cri->community, "public"); 45 | cri->timeout = DEFAULT_TIMEOUT; 46 | cri->retries = DEFAULT_RETRIES; 47 | 48 | cri->dest = dest; 49 | cri->fd = si->fd; 50 | cri->si = si; 51 | TAILQ_INIT(&cri->oids_to_query); 52 | TAILQ_INIT(&cri->sid_infos); 53 | *cri_slot = cri; 54 | } 55 | 56 | JLI(dest_cri_slot, dest->client_requests_info, si->fd); 57 | if (dest_cri_slot == PJERR) 58 | croak(2, "get_client_requests_info: JLI(dest/fd) failed"); 59 | *dest_cri_slot = *cri_slot; 60 | return *cri_slot; 61 | } 62 | 63 | int 64 | free_all_client_request_info_for_fd(int fd) 65 | { 66 | void **fd_slot, **ip_slot; 67 | struct client_requests_info **cri_slot; 68 | Word_t ip, port; 69 | Word_t rc; 70 | 71 | JLG(fd_slot, dest_by_fd, fd); 72 | if (fd_slot == PJERR) 73 | croak(2, "free_all_client_request_info_for_fd: JLG(fd) failed"); 74 | if (!fd_slot) 75 | return 1; 76 | 77 | ip = 0; 78 | JLF(ip_slot, *fd_slot, ip); 79 | while (ip_slot) { 80 | port = 0; 81 | JLF(cri_slot, *ip_slot, port); 82 | while (cri_slot) { 83 | free_client_request_info(*cri_slot); 84 | JLN(cri_slot, *ip_slot, port); 85 | } 86 | JLFA(rc, *ip_slot); 87 | JLN(ip_slot, *fd_slot, ip); 88 | } 89 | JLFA(rc, *fd_slot); 90 | JLD(rc, dest_by_fd, fd); 91 | return 1; 92 | } 93 | 94 | int 95 | free_client_request_info(struct client_requests_info *cri) 96 | { 97 | struct cid_info **ci_slot; 98 | Word_t cid; 99 | Word_t rc; 100 | struct sid_info *si, *si_temp; 101 | 102 | PS.active_cr_infos--; 103 | cri->si->PS.active_cr_infos--; 104 | si = TAILQ_FIRST(&cri->sid_infos); 105 | while (si != NULL) { 106 | si_temp = TAILQ_NEXT(si, sid_list); 107 | free_sid_info(si); 108 | cri->dest->packets_on_the_wire--; 109 | if (cri->dest->packets_on_the_wire < 0) 110 | cri->dest->packets_on_the_wire = 0; 111 | PS.packets_on_the_wire--; 112 | if (PS.packets_on_the_wire < 0) 113 | PS.packets_on_the_wire = 0; 114 | si = si_temp; 115 | } 116 | TAILQ_INIT(&cri->sid_infos); 117 | 118 | free_oid_info_list(&cri->oids_to_query); 119 | cid = 0; 120 | JLF(ci_slot, cri->cid_info, cid); 121 | while (ci_slot) { 122 | free_cid_info(*ci_slot); 123 | JLN(ci_slot, cri->cid_info, cid); 124 | } 125 | JLFA(rc, cri->cid_info); 126 | JLD(rc, cri->dest->client_requests_info, cri->fd); 127 | free(cri); 128 | return 1; 129 | } 130 | 131 | void 132 | dump_client_request_info(msgpack_packer *pk, struct client_requests_info *cri) 133 | { 134 | char buf[512]; 135 | Word_t n_cid, n_query_queue, n_sid, cid; 136 | struct oid_info *oi; 137 | struct sid_info *si; 138 | struct cid_info **cid_slot; 139 | 140 | #define PACK msgpack_pack_string(pk, buf) 141 | #define DUMPi(field) msgpack_pack_named_int(pk, #field, cri->field) 142 | #define DUMPs(field) msgpack_pack_named_string(pk, #field, cri->field) 143 | snprintf(buf, 512, "CRI(%d)", cri->fd); PACK; 144 | msgpack_pack_map(pk, 12); 145 | 146 | msgpack_pack_string(pk, "dest"); 147 | snprintf(buf, 512, "DEST(%s:%d)", inet_ntoa(cri->dest->ip), cri->dest->port); PACK; 148 | 149 | DUMPi(fd); 150 | DUMPi(version); 151 | DUMPs(community); 152 | DUMPi(timeout); 153 | DUMPi(retries); 154 | 155 | JLC(n_cid, cri->cid_info, 0, -1); 156 | msgpack_pack_named_int(pk, "#CID", n_cid); 157 | 158 | n_query_queue = 0; 159 | TAILQ_FOREACH(oi, &cri->oids_to_query, oid_list) { 160 | n_query_queue++; 161 | } 162 | msgpack_pack_named_int(pk, "#QUERY_QUEUE", n_query_queue); 163 | 164 | msgpack_pack_string(pk, "@QUERY_QUEUE"); 165 | msgpack_pack_array(pk, n_query_queue); 166 | TAILQ_FOREACH(oi, &cri->oids_to_query, oid_list) { 167 | dump_oid_info(pk, oi); 168 | } 169 | 170 | n_sid = 0; 171 | TAILQ_FOREACH(si, &cri->sid_infos, sid_list) { 172 | n_sid++; 173 | } 174 | msgpack_pack_named_int(pk, "#SID", n_sid); 175 | 176 | msgpack_pack_string(pk, "@CID"); 177 | msgpack_pack_map(pk, n_cid); 178 | cid = 0; 179 | JLF(cid_slot, cri->cid_info, cid); 180 | while (cid_slot) { 181 | dump_cid_info(pk, *cid_slot); 182 | JLN(cid_slot, cri->cid_info, cid); 183 | } 184 | 185 | msgpack_pack_string(pk, "@SID"); 186 | msgpack_pack_map(pk, n_sid); 187 | TAILQ_FOREACH(si, &cri->sid_infos, sid_list) { 188 | dump_sid_info(pk, si); 189 | } 190 | 191 | #undef DUMPi 192 | #undef DUMPs 193 | #undef PACK 194 | } 195 | -------------------------------------------------------------------------------- /request_common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | int 12 | error_reply(struct socket_info *si, unsigned code, unsigned cid, char *error) 13 | { 14 | msgpack_sbuffer* buffer = msgpack_sbuffer_new(); 15 | msgpack_packer* pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); 16 | int l = strlen(error); 17 | 18 | msgpack_pack_array(pk, 3); 19 | msgpack_pack_unsigned_int(pk, code); 20 | msgpack_pack_unsigned_int(pk, cid); 21 | msgpack_pack_bin(pk, l); 22 | msgpack_pack_bin_body(pk, error, l); 23 | 24 | tcp_send(si, buffer->data, buffer->size); 25 | msgpack_sbuffer_free(buffer); 26 | msgpack_packer_free(pk); 27 | return -1; 28 | } 29 | 30 | int 31 | msgpack_pack_named_int(msgpack_packer *pk, char *name, int64_t val) 32 | { 33 | int l = strlen(name); 34 | msgpack_pack_bin(pk, l); 35 | msgpack_pack_bin_body(pk, name, l); 36 | msgpack_pack_int64(pk, val); 37 | /* XXX */ 38 | return 0; 39 | } 40 | 41 | int 42 | msgpack_pack_named_string(msgpack_packer *pk, char *name, char *val) 43 | { 44 | int l = strlen(name); 45 | msgpack_pack_bin(pk, l); 46 | msgpack_pack_bin_body(pk, name, l); 47 | l = strlen(val); 48 | msgpack_pack_bin(pk, l); 49 | msgpack_pack_bin_body(pk, val, l); 50 | /* XXX */ 51 | return 0; 52 | } 53 | 54 | int 55 | msgpack_pack_string(msgpack_packer *pk, char *s) 56 | { 57 | int l = strlen(s); 58 | msgpack_pack_bin(pk, l); 59 | msgpack_pack_bin_body(pk, s, l); 60 | /* XXX */ 61 | return 0; 62 | } 63 | 64 | int 65 | msgpack_pack_options(msgpack_packer *pk, struct client_requests_info *cri) 66 | { 67 | msgpack_pack_map(pk, 15); 68 | msgpack_pack_named_string(pk, "ip", inet_ntoa(cri->dest->ip)); 69 | msgpack_pack_named_int(pk, "port", cri->dest->port); 70 | msgpack_pack_named_string(pk, "community", cri->community); 71 | msgpack_pack_named_int(pk, "version", cri->version + 1); 72 | msgpack_pack_named_int(pk, "max_packets", cri->dest->max_packets_on_the_wire); 73 | msgpack_pack_named_int(pk, "max_req_size", cri->dest->max_request_packet_size); 74 | msgpack_pack_named_int(pk, "max_reply_size", cri->dest->max_reply_packet_size); 75 | msgpack_pack_named_int(pk, "estimated_value_size", cri->dest->estimated_value_size); 76 | msgpack_pack_named_int(pk, "max_oids_per_request", cri->dest->max_oids_per_request); 77 | msgpack_pack_named_int(pk, "timeout", cri->timeout); 78 | msgpack_pack_named_int(pk, "retries", cri->retries); 79 | msgpack_pack_named_int(pk, "min_interval", cri->dest->min_interval); 80 | msgpack_pack_named_int(pk, "max_repetitions", cri->dest->max_repetitions); 81 | msgpack_pack_named_int(pk, "ignore_threshold", cri->dest->ignore_threshold); 82 | msgpack_pack_named_int(pk, "ignore_duration", cri->dest->ignore_duration); 83 | return 0; 84 | } 85 | 86 | static void inline 87 | pack_error(msgpack_packer *pk, char *error) 88 | { 89 | int l = strlen(error); 90 | msgpack_pack_array(pk, 1); 91 | msgpack_pack_bin(pk, l); 92 | msgpack_pack_bin_body(pk, error, l); 93 | } 94 | 95 | void 96 | msgpack_pack_oid(struct msgpack_packer *pk, struct ber oid) 97 | { 98 | char *stroid; 99 | int l; 100 | 101 | stroid = oid2str(oid); 102 | l = strlen(stroid); 103 | msgpack_pack_bin(pk, l); 104 | msgpack_pack_bin_body(pk, stroid, l); 105 | } 106 | 107 | void 108 | msgpack_pack_ber(struct msgpack_packer *pk, struct ber value) 109 | { 110 | unsigned char t; 111 | unsigned len, u32; 112 | unsigned long long u64; 113 | struct in_addr ip; 114 | char *strip; 115 | char unsupported[30]; /* "unsupported type 0xXX" */ 116 | 117 | if (decode_type_len(&value, &t, &len) < 0) 118 | t = VAL_DECODE_ERROR; 119 | switch (t) { 120 | case AT_INTEGER: 121 | case AT_COUNTER: 122 | case AT_UNSIGNED: 123 | if (decode_integer(&value, len, &u32) < 0) goto decode_error; 124 | if (t == AT_INTEGER) { 125 | /* XXX quick fix, but should be correct! */ 126 | if (len == 1 && (u32 & 0x80)) { 127 | msgpack_pack_int64(pk, ((int32_t)(uint32_t)u32) - 0x100); 128 | } else if (len == 2 && (u32 & 0x8000)) { 129 | msgpack_pack_int64(pk, ((int32_t)(uint32_t)u32) - 0x10000); 130 | } else if (len == 3 && (u32 & 0x800000)) { 131 | msgpack_pack_int64(pk, ((int32_t)(uint32_t)u32) - 0x1000000); 132 | } else { 133 | msgpack_pack_int64(pk, (int32_t)(uint32_t)u32); 134 | } 135 | } else { 136 | msgpack_pack_uint64(pk, u32); 137 | } 138 | break; 139 | case AT_STRING: 140 | msgpack_pack_bin(pk, len); 141 | msgpack_pack_bin_body(pk, value.b, len); 142 | break; 143 | case AT_NULL: 144 | msgpack_pack_nil(pk); 145 | break; 146 | case AT_TIMETICKS: 147 | if (decode_timeticks(&value, len, &u64) < 0) goto decode_error; 148 | msgpack_pack_uint64(pk, u64); 149 | break; 150 | case AT_COUNTER64: 151 | if (decode_counter64(&value, len, &u64) < 0) goto decode_error; 152 | msgpack_pack_uint64(pk, u64); 153 | break; 154 | case AT_IP_ADDRESS: 155 | if (decode_ipv4_address(&value, len, &ip) < 0) goto decode_error; 156 | strip = inet_ntoa(ip); 157 | len = strlen(strip); 158 | msgpack_pack_bin(pk, len); 159 | msgpack_pack_bin_body(pk, strip, len); 160 | break; 161 | case AT_OID: 162 | msgpack_pack_oid(pk, value); 163 | break; 164 | case AT_NO_SUCH_OBJECT: 165 | pack_error(pk, "no-such-object"); 166 | break; 167 | case AT_NO_SUCH_INSTANCE: 168 | pack_error(pk, "no-such-instance"); 169 | break; 170 | case AT_END_OF_MIB_VIEW: 171 | pack_error(pk, "end-of-mib"); 172 | break; 173 | case VAL_TIMEOUT: 174 | pack_error(pk, "timeout"); 175 | break; 176 | case VAL_MISSING: 177 | pack_error(pk, "missing"); 178 | break; 179 | decode_error: 180 | case VAL_DECODE_ERROR: 181 | pack_error(pk, "decode-error"); 182 | break; 183 | case VAL_IGNORED: 184 | pack_error(pk, "ignored"); 185 | break; 186 | case VAL_NON_INCREASING: 187 | pack_error(pk, "non-increasing"); 188 | break; 189 | case VAL_STRING_ERROR: 190 | msgpack_pack_array(pk, 1); 191 | msgpack_pack_bin(pk, len); 192 | msgpack_pack_bin_body(pk, value.b, len); 193 | break; 194 | default: 195 | snprintf(unsupported, 30, "unsupported type 0x%02x", t); 196 | pack_error(pk, unsupported); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /request_setopt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | /* 12 | * setopt request: 13 | * [ 0, $cid, $ip, $port, {options} ] 14 | * 15 | */ 16 | 17 | static void *option2index; /* a JudySL tree */ 18 | 19 | #define OPT_version 1 20 | #define OPT_community 2 21 | #define OPT_max_packets 3 22 | #define OPT_max_req_size 4 23 | #define OPT_timeout 5 24 | #define OPT_retries 6 25 | #define OPT_min_interval 7 26 | #define OPT_max_repetitions 8 27 | #define OPT_ignore_threshold 9 28 | #define OPT_ignore_duration 10 29 | #define OPT_max_reply_size 11 30 | #define OPT_estimated_value_size 12 31 | #define OPT_max_oids_per_request 13 32 | #define OPT_global_max_packets 14 33 | 34 | static void 35 | build_option2index(void) 36 | { 37 | Word_t *val; 38 | 39 | #define ADD(var) JSLI(val, option2index, (unsigned char *)#var); if (val == PJERR) croak(2, "build_option2index: JSLI(" #var ") failed"); *val = OPT_##var; 40 | ADD(version); 41 | ADD(community); 42 | ADD(max_packets); 43 | ADD(global_max_packets); 44 | ADD(max_req_size); 45 | ADD(max_reply_size); 46 | ADD(estimated_value_size); 47 | ADD(max_oids_per_request); 48 | ADD(timeout); 49 | ADD(retries); 50 | ADD(min_interval); 51 | ADD(max_repetitions); 52 | ADD(ignore_threshold); 53 | ADD(ignore_duration); 54 | #undef ADD 55 | } 56 | 57 | int 58 | handle_setopt_request(struct socket_info *si, unsigned cid, msgpack_object *o) 59 | { 60 | unsigned port = 65536; 61 | struct in_addr ip; 62 | struct client_requests_info *cri; 63 | struct destination d; 64 | struct client_requests_info c; 65 | msgpack_sbuffer* buffer; 66 | msgpack_packer* pk; 67 | msgpack_object *h, *v; 68 | msgpack_object_type t; 69 | int i; 70 | 71 | if (o->via.array.size != 5) 72 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "bad request length"); 73 | 74 | if (o->via.array.ptr[RI_SETOPT_PORT].type == MSGPACK_OBJECT_POSITIVE_INTEGER) 75 | port = o->via.array.ptr[RI_SETOPT_PORT].via.u64; 76 | if (port > 65535) 77 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "bad port number"); 78 | 79 | if (!object2ip(&o->via.array.ptr[RI_SETOPT_IP], &ip)) 80 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "bad IP"); 81 | 82 | if (o->via.array.ptr[RI_SETOPT_OPT].type != MSGPACK_OBJECT_MAP) 83 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "options is not a map"); 84 | 85 | cri = get_client_requests_info(&ip, port, si); 86 | memcpy(&d, cri->dest, sizeof(d)); 87 | memcpy(&c, cri, sizeof(c)); 88 | 89 | if (!option2index) 90 | build_option2index(); 91 | h = &o->via.array.ptr[RI_SETOPT_OPT]; 92 | for (i = 0; i < h->via.map.size; i++) { 93 | char name[256]; 94 | Word_t *val; 95 | 96 | if (!object2string(&h->via.map.ptr[i].key, name, 256)) 97 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "bad option key"); 98 | JSLG(val, option2index, (unsigned char *)name); 99 | if (!val) 100 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "bad option key"); 101 | v = &h->via.map.ptr[i].val; 102 | t = v->type; 103 | switch (*val) { 104 | case OPT_version: 105 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || (v->via.u64 != 1 && v->via.u64 != 2)) 106 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid SNMP version"); 107 | c.version = v->via.u64 - 1; 108 | break; 109 | case OPT_community: 110 | if (!object2string(v, c.community, 256)) 111 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid SNMP community"); 112 | break; 113 | case OPT_max_packets: 114 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 1 || v->via.u64 > 1000) 115 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid max packets"); 116 | d.max_packets_on_the_wire = v->via.u64; 117 | break; 118 | case OPT_global_max_packets: 119 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 1 || v->via.u64 > 2000000) 120 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid global max packets"); 121 | PS.max_packets_on_the_wire = v->via.u64; 122 | break; 123 | case OPT_max_req_size: 124 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 500 || v->via.u64 > 50000) 125 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid max request size"); 126 | d.max_request_packet_size = v->via.u64; 127 | break; 128 | case OPT_max_reply_size: 129 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 500 || v->via.u64 > 50000) 130 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid max reply size"); 131 | d.max_reply_packet_size = v->via.u64; 132 | break; 133 | case OPT_estimated_value_size: 134 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 1 || v->via.u64 > 1024) 135 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid estimated value size"); 136 | d.estimated_value_size = v->via.u64; 137 | break; 138 | case OPT_max_oids_per_request: 139 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 1 || v->via.u64 > 1024) 140 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid max oids per request"); 141 | d.max_oids_per_request = v->via.u64; 142 | break; 143 | case OPT_timeout: 144 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 > 30000) 145 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid timeout"); 146 | c.timeout = v->via.u64; 147 | break; 148 | case OPT_retries: 149 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 1 || v->via.u64 > 10) 150 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid retries"); 151 | c.retries = v->via.u64; 152 | break; 153 | case OPT_min_interval: 154 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 > 10000) 155 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid min interval"); 156 | d.min_interval = v->via.u64; 157 | break; 158 | case OPT_max_repetitions: 159 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 < 1 || v->via.u64 > 255) 160 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid max repetitions"); 161 | d.max_repetitions = v->via.u64; 162 | break; 163 | case OPT_ignore_threshold: 164 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 > 1000) 165 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid ignore threshold"); 166 | d.ignore_threshold = v->via.u64; 167 | if (cri->dest->ignore_threshold != d.ignore_threshold) 168 | bzero(&d.ignore_until, sizeof(cri->dest->ignore_until)); 169 | break; 170 | case OPT_ignore_duration: 171 | if (t != MSGPACK_OBJECT_POSITIVE_INTEGER || v->via.u64 > 86400000) 172 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "invalid ignore duration"); 173 | d.ignore_duration = v->via.u64; 174 | if (cri->dest->ignore_duration != d.ignore_duration) 175 | bzero(&d.ignore_until, sizeof(cri->dest->ignore_until)); 176 | break; 177 | default: 178 | return error_reply(si, RT_SETOPT|RT_ERROR, cid, "bad option key"); 179 | } 180 | } 181 | 182 | PS.setopt_requests++; 183 | si->PS.setopt_requests++; 184 | 185 | memcpy(cri, &c, sizeof(c)); /* This is safe to do, I am sure */ 186 | memcpy(cri->dest, &d, sizeof(d)); /* This is safe to do, I am sure */ 187 | buffer = msgpack_sbuffer_new(); 188 | pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); 189 | msgpack_pack_array(pk, 3); 190 | msgpack_pack_int(pk, RT_SETOPT|RT_REPLY); 191 | msgpack_pack_int(pk, cid); 192 | msgpack_pack_options(pk, &c); 193 | 194 | tcp_send(si, buffer->data, buffer->size); 195 | msgpack_sbuffer_free(buffer); 196 | msgpack_packer_free(pk); 197 | return 0; 198 | } 199 | -------------------------------------------------------------------------------- /destination.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | JudyL by_ip; 12 | 13 | struct destination *get_destination(struct in_addr *ip, unsigned port) 14 | { 15 | void **ip_slot; 16 | struct destination **dest_slot, *d; 17 | 18 | JLI(ip_slot, by_ip, ip->s_addr); 19 | if (ip_slot == PJERR) 20 | croak(2, "get_destination: JLI failed"); 21 | JLI(dest_slot, *ip_slot, port); 22 | if (dest_slot == PJERR) 23 | croak(2, "get_destination: JLI failed"); 24 | if (!*dest_slot) { 25 | d = malloc(sizeof(*d)); 26 | if (!d) 27 | croak(2, "get_destination: malloc(destination)"); 28 | bzero(d, sizeof(*d)); 29 | d->ip = *ip; 30 | d->port = port; 31 | d->dest_addr.sin_family = PF_INET; 32 | d->dest_addr.sin_addr = *ip; 33 | d->dest_addr.sin_port = htons(port); 34 | d->max_packets_on_the_wire = DEFAULT_MAX_PACKETS_ON_THE_WIRE; 35 | d->max_request_packet_size = DEFAULT_MAX_REQUEST_PACKET_SIZE; 36 | d->max_reply_packet_size = DEFAULT_MAX_REPLY_PACKET_SIZE; 37 | d->estimated_value_size = DEFAULT_ESTIMATED_VALUE_SIZE; 38 | d->max_oids_per_request = DEFAULT_MAX_OIDS_PER_REQUEST; 39 | d->min_interval = DEFAULT_MIN_INTERVAL; 40 | d->max_repetitions = DEFAULT_MAX_REPETITIONS; 41 | d->ignore_threshold = DEFAULT_IGNORE_THRESHOLD; 42 | d->ignore_duration = DEFAULT_IGNORE_DURATION; 43 | *dest_slot = d; 44 | } 45 | return *dest_slot; 46 | } 47 | 48 | struct destination *find_destination(struct in_addr *ip, unsigned port) 49 | { 50 | void **ip_slot; 51 | struct destination **dest_slot; 52 | 53 | JLG(ip_slot, by_ip, ip->s_addr); 54 | if (ip_slot == PJERR || !ip_slot) return NULL; 55 | 56 | JLG(dest_slot, *ip_slot, port); 57 | if (dest_slot == PJERR || !dest_slot) return NULL; 58 | return *dest_slot; 59 | } 60 | 61 | void 62 | flush_ignored_destination(struct destination *dest) 63 | { 64 | struct client_requests_info *cri; 65 | Word_t fd; 66 | struct client_requests_info **cri_slot; 67 | struct sid_info *si, *si_temp; 68 | struct oid_info *oi, *oi_temp; 69 | struct cid_info *ci; 70 | int current_packets_on_the_wire = dest->packets_on_the_wire; 71 | 72 | fd = 0; 73 | JLF(cri_slot, dest->client_requests_info, fd); 74 | while (cri_slot) { 75 | cri = *cri_slot; 76 | 77 | si = TAILQ_FIRST(&cri->sid_infos); 78 | while (si != NULL) { 79 | si_temp = TAILQ_NEXT(si, sid_list); 80 | if (si->table_oid) { 81 | oid_done(si, si->table_oid, &BER_IGNORED, RT_GETTABLE, 0); 82 | si->table_oid = NULL; 83 | } else { 84 | all_oids_done(si, &BER_IGNORED); 85 | } 86 | free_sid_info(si); 87 | si = si_temp; 88 | } 89 | TAILQ_INIT(&cri->sid_infos); 90 | 91 | TAILQ_FOREACH_SAFE(oi, &cri->oids_to_query, oid_list, oi_temp) { 92 | ci = get_cid_info(cri, oi->cid); 93 | if (!ci || ci->n_oids == 0) 94 | croakx(2, "flush_ignored_destination: cid_info unexpectedly missing"); 95 | /* XXX free old value? */ 96 | oi->value = ber_rewind(ber_dup(&BER_IGNORED)); 97 | oi->sid = 0; 98 | TAILQ_REMOVE(&cri->oids_to_query, oi, oid_list); 99 | TAILQ_INSERT_TAIL(&ci->oids_done, oi, oid_list); 100 | PS.oids_ignored++; 101 | ci->n_oids_done++; 102 | if (ci->n_oids_done == ci->n_oids) 103 | cid_reply(ci, oi->last_known_table_entry ? RT_GETTABLE : RT_GET); 104 | } 105 | 106 | JLN(cri_slot, dest->client_requests_info, fd); 107 | } 108 | dest->packets_on_the_wire = 0; 109 | PS.packets_on_the_wire -= current_packets_on_the_wire; 110 | if (PS.packets_on_the_wire < 0) 111 | PS.packets_on_the_wire = 0; 112 | } 113 | 114 | void 115 | maybe_query_destination(struct destination *dest) 116 | { 117 | struct client_requests_info **cri_slot; 118 | Word_t fd; 119 | struct timeval now; 120 | 121 | if (dest->ignore_threshold && dest->timeouts_in_a_row >= dest->ignore_threshold) { 122 | PS.destination_ignores++; 123 | dest->timeouts_in_a_row = 0; 124 | set_timeout(&dest->ignore_until, dest->ignore_duration); 125 | } 126 | if (dest->ignore_until.tv_sec > 0) { 127 | gettimeofday(&now, NULL); 128 | if (now.tv_sec < dest->ignore_until.tv_sec || 129 | (now.tv_sec == dest->ignore_until.tv_sec && now.tv_usec < dest->ignore_until.tv_usec)) 130 | { 131 | flush_ignored_destination(dest); 132 | return; 133 | } else { 134 | /* we are past ignore duration here */ 135 | bzero(&dest->ignore_until, sizeof(dest->ignore_until)); 136 | } 137 | } 138 | 139 | if (PS.packets_on_the_wire >= PS.max_packets_on_the_wire) { 140 | PS.global_throttles++; 141 | return; 142 | } 143 | if (dest->packets_on_the_wire >= dest->max_packets_on_the_wire) { 144 | PS.destination_throttles++; 145 | //fprintf(stderr, "%s: max_packets_on_the_wire(%d)\n", inet_ntoa(dest->ip), dest->packets_on_the_wire); 146 | return; 147 | } 148 | if (dest->can_query_at.tv_sec) { 149 | gettimeofday(&now, NULL); 150 | if (now.tv_sec < dest->can_query_at.tv_sec || 151 | (now.tv_sec == dest->can_query_at.tv_sec && now.tv_usec < dest->can_query_at.tv_usec)) 152 | { 153 | //fprintf(stderr, "%s: min_interval\n", inet_ntoa(dest->ip)); 154 | return; 155 | } 156 | } 157 | 158 | //fprintf(stderr, "%s: ok(%d)\n", inet_ntoa(dest->ip), dest->packets_on_the_wire); 159 | /* Then find a client_requests_info, in a round-robin fashion, 160 | * which has anything to send. Be careful with when to stop. 161 | */ 162 | fd = dest->fd_of_last_query; 163 | //again: 164 | JLN(cri_slot, dest->client_requests_info, fd); 165 | if (dest->fd_of_last_query == 0) 166 | dest->fd_of_last_query = fd; 167 | if (!cri_slot) { 168 | fd = 0; 169 | JLF(cri_slot, dest->client_requests_info, fd); 170 | if (!cri_slot) return; /* no clients for this destination */ 171 | } 172 | build_snmp_query(*cri_slot); 173 | // else if (fd != dest->fd_of_last_query) 174 | // goto again; 175 | dest->fd_of_last_query = fd; 176 | // XXX goto 177 | } 178 | 179 | void 180 | destination_timer(struct destination *dest) 181 | { 182 | // XXX implement me 183 | //fprintf(stderr, "%s: min_interval timer tick (%d)\n", inet_ntoa(dest->ip), dest->packets_on_the_wire); 184 | destination_stop_timing(dest); 185 | maybe_query_destination(dest); 186 | } 187 | 188 | void 189 | destination_stop_timing(struct destination *dest) 190 | { 191 | struct timer *t; 192 | 193 | t = find_timer(&dest->can_query_at); 194 | if (t) TAILQ_REMOVE(&t->throttled_destinations, dest, timer_chain); 195 | bzero(&dest->can_query_at, sizeof(dest->can_query_at)); 196 | } 197 | 198 | void 199 | destination_start_timing(struct destination *dest) 200 | { 201 | struct timer *t; 202 | 203 | destination_stop_timing(dest); 204 | set_timeout(&dest->can_query_at, dest->min_interval); 205 | t = new_timer(&dest->can_query_at); 206 | TAILQ_INSERT_TAIL(&t->throttled_destinations, dest, timer_chain); 207 | } 208 | 209 | static void 210 | dump_destination(msgpack_packer *pk, struct destination *dest) 211 | { 212 | char buf[512]; 213 | Word_t n_cri, n_sid, fd; 214 | struct client_requests_info **cri_slot; 215 | 216 | #define PACK msgpack_pack_string(pk, buf) 217 | #define DUMPi(field) msgpack_pack_named_int(pk, #field, dest->field) 218 | #define DUMPs(field) msgpack_pack_named_string(pk, #field, dest->field) 219 | snprintf(buf, 512, "DEST(%s:%d)", inet_ntoa(dest->ip), dest->port); PACK; 220 | msgpack_pack_map(pk, 14); 221 | DUMPi(max_packets_on_the_wire); 222 | DUMPi(max_request_packet_size); 223 | DUMPi(max_reply_packet_size); 224 | DUMPi(estimated_value_size); 225 | DUMPi(max_oids_per_request); 226 | DUMPi(min_interval); 227 | DUMPi(max_repetitions); 228 | DUMPi(ignore_threshold); 229 | DUMPi(ignore_duration); 230 | DUMPi(packets_on_the_wire); 231 | /* XXX can_query_at */ 232 | DUMPi(fd_of_last_query); 233 | JLC(n_cri, dest->client_requests_info, 0, -1); 234 | msgpack_pack_named_int(pk, "#CRI", n_cri); 235 | JLC(n_sid, dest->sid_info, 0, -1); 236 | msgpack_pack_named_int(pk, "#SID", n_sid); 237 | 238 | msgpack_pack_string(pk, "@CRI"); 239 | msgpack_pack_map(pk, n_cri); 240 | fd = 0; 241 | JLF(cri_slot, dest->client_requests_info, fd); 242 | while (cri_slot) { 243 | dump_client_request_info(pk, *cri_slot); 244 | JLN(cri_slot, dest->client_requests_info, fd); 245 | } 246 | 247 | #undef DUMPi 248 | #undef DUMPs 249 | #undef PACK 250 | } 251 | 252 | void 253 | unclog_all_destinations(void) 254 | { 255 | struct destination **dest_slot; 256 | void **ip_slot; 257 | Word_t ip, port; 258 | 259 | ip = 0; 260 | JLF(ip_slot, by_ip, ip); 261 | while (ip_slot) { 262 | port = 0; 263 | JLF(dest_slot, *ip_slot, port); 264 | while (dest_slot) { 265 | maybe_query_destination(*dest_slot); 266 | JLN(dest_slot, *ip_slot, port); 267 | } 268 | JLN(ip_slot, by_ip, ip); 269 | } 270 | } 271 | 272 | void 273 | dump_all_destinations(msgpack_packer *pk) 274 | { 275 | struct destination **dest_slot; 276 | void **ip_slot; 277 | Word_t ip, port, rc; 278 | int n_dest = 0; 279 | 280 | ip = 0; 281 | JLF(ip_slot, by_ip, ip); 282 | while (ip_slot) { 283 | JLC(rc, *ip_slot, 0, -1); 284 | n_dest += rc; 285 | JLN(ip_slot, by_ip, ip); 286 | } 287 | msgpack_pack_map(pk, n_dest); 288 | 289 | ip = 0; 290 | JLF(ip_slot, by_ip, ip); 291 | while (ip_slot) { 292 | port = 0; 293 | JLF(dest_slot, *ip_slot, port); 294 | while (dest_slot) { 295 | dump_destination(pk, *dest_slot); 296 | JLN(dest_slot, *ip_slot, port); 297 | } 298 | JLN(ip_slot, by_ip, ip); 299 | } 300 | } 301 | 302 | -------------------------------------------------------------------------------- /test_ber.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | int 12 | test_encode_string_oid(int *test, char *oid, int oid_len, const char *res, int len) 13 | { 14 | char *buf = malloc(len + 20); 15 | struct ber e = ber_init(buf, len + 20); 16 | char out_buf[4096]; 17 | int oid_len2; 18 | 19 | (*test)++; 20 | buf[len] = '\x55'; 21 | if (oid_len >= 0) { 22 | oid[oid_len] = '\xAA'; 23 | oid_len2 = oid_len; 24 | } else { 25 | oid_len2 = strlen(oid); 26 | } 27 | if (encode_string_oid(oid, oid_len, &e) < 0) { 28 | fprintf(stderr, "test %d, encode_string_oid: unexpected failure, oid %s\n", *test, oid); 29 | free(buf); 30 | return 0; 31 | } 32 | if (oid_len >= 0 && oid[oid_len] != '\xAA') { 33 | fprintf(stderr, "test %d, encode_string_oid: corrupted OID string\n", *test); 34 | free(buf); 35 | return 0; 36 | } 37 | if (e.len != len) { 38 | fprintf(stderr, "test %d, encode_string_oid: unexpected length (%d != %d), oid %s\n", *test, e.len, len, oid); 39 | free(buf); 40 | return 0; 41 | } 42 | if (buf[len] != '\x55') { 43 | fprintf(stderr, "test %d, encode_string_oid: buffer corruped, oid %s\n", *test, oid); 44 | free(buf); 45 | return 0; 46 | } 47 | if (!decode_string_oid((unsigned char *)buf, len, out_buf, 4096)) { 48 | fprintf(stderr, "test %d, decode_string_oid: cannot decode encoded oid %s\n", *test, oid); 49 | free(buf); 50 | return 0; 51 | } 52 | // fprintf(stderr, "ORIGINAL: %s\nDECODED : %s\n", oid, out_buf); 53 | if (strlen(out_buf) != (*oid == '.' ? oid_len2-1 : oid_len2)) { 54 | fprintf(stderr, "test %d, decode_string_oid: decoded oid len != encoded oid len, %s\n", *test, oid); 55 | free(buf); 56 | return 0; 57 | } 58 | if (strncmp(*oid == '.' ? oid+1 : oid, out_buf, *oid == '.' ? oid_len2-1 : oid_len2) != 0) { 59 | fprintf(stderr, "test %d, decode_string_oid: decoded oid != encoded oid %s\n", *test, oid); 60 | free(buf); 61 | return 0; 62 | } 63 | if (memcmp(buf, res, len) != 0) { 64 | fprintf(stderr, "test %d, encode_string_oid: unexpected buffer content, oid %s\n", *test, oid); 65 | free(buf); 66 | return 0; 67 | } 68 | e = ber_init(buf, len); 69 | if (encode_string_oid(oid, oid_len, &e) < 0) { 70 | fprintf(stderr, "test %d, encode_string_oid: unexpected failure with just enough buffer space, oid %s\n", *test, oid); 71 | free(buf); 72 | return 0; 73 | } 74 | e = ber_init(buf, len-1); 75 | if (encode_string_oid(oid, oid_len, &e) == 0) { 76 | fprintf(stderr, "test %d, encode_string_oid: unexpected success with slightly not enough buffer space, oid %s\n", *test, oid); 77 | free(buf); 78 | return 0; 79 | } 80 | free(buf); 81 | return 1; 82 | } 83 | 84 | int 85 | test_encode_string(int *test, const char *test_string, int real_string_offset) 86 | { 87 | int len = strlen(test_string); 88 | const char *s = test_string + real_string_offset; 89 | char *buf = malloc(len + 20); 90 | struct ber e = ber_init(buf, len + 20); 91 | 92 | (*test)++; 93 | buf[len] = '\x55'; 94 | if (encode_string(s, &e) < 0) { 95 | fprintf(stderr, "test %d, encode_string: unexpected failure, string %s\n", *test, s); 96 | free(buf); 97 | return 0; 98 | } 99 | if (e.len != len) { 100 | fprintf(stderr, "test %d, encode_string: unexpected length (%d != %d), string %s\n", *test, e.len, len, s); 101 | free(buf); 102 | return 0; 103 | } 104 | if (buf[len] != '\x55') { 105 | fprintf(stderr, "test %d, encode_string: buffer corruped, string %s\n", *test, s); 106 | free(buf); 107 | return 0; 108 | } 109 | if (memcmp(buf, test_string, len) != 0) { 110 | fprintf(stderr, "test %d, encode_string: unexpected buffer content, string %s\n", *test, s); 111 | free(buf); 112 | return 0; 113 | } 114 | e = ber_init(buf, len); 115 | if (encode_string(s, &e) < 0) { 116 | fprintf(stderr, "test %d, encode_string: unexpected failure with just enough buffer space, string %s\n", *test, s); 117 | free(buf); 118 | return 0; 119 | } 120 | e = ber_init(buf, len-1); 121 | if (encode_string(s, &e) == 0) { 122 | fprintf(stderr, "test %d, encode_string: unexpected success with slightly not enough buffer space, string %s\n", *test, s); 123 | free(buf); 124 | return 0; 125 | } 126 | free(buf); 127 | return 1; 128 | } 129 | 130 | int 131 | test_oid_compare(int *test, const char *o1, const char *o2, int expected) 132 | { 133 | char buf1[4096]; 134 | char buf2[4096]; 135 | struct ber oid1 = ber_init(buf1, 4096); 136 | struct ber oid2 = ber_init(buf2, 4096); 137 | int got; 138 | 139 | (*test)++; 140 | if (encode_string_oid(o1, -1, &oid1) < 0) { 141 | fprintf(stderr, "test %d, encode_string_oid: unexpected failure, oid %s\n", *test, o1); 142 | return 0; 143 | } 144 | if (encode_string_oid(o2, -1, &oid2) < 0) { 145 | fprintf(stderr, "test %d, encode_string_oid: unexpected failure, oid %s\n", *test, o2); 146 | return 0; 147 | } 148 | if ((got = oid_compare(&oid1, &oid2)) != expected) { 149 | fprintf(stderr, "test %d, oid_compare(\"%s\",\"%s\"): expected %d, got %d\n", *test, o1, o2, expected, got); 150 | return 0; 151 | } 152 | return 1; 153 | } 154 | 155 | int 156 | main(void) 157 | { 158 | int success = 0; 159 | int n_tests = 0; 160 | char oid[256]; 161 | struct in_addr ip; 162 | struct ber bt; 163 | unsigned char buf[80]; 164 | 165 | n_tests++; 166 | bt.buf = buf; 167 | bt.b = buf; 168 | bt.len = 0; 169 | bt.max_len = 80; 170 | strcpy((char *)buf, "\x40\x04\xff\xff\xff\xfc"); 171 | if (decode_ipv4_address(&bt, -1, &ip) < 0) { 172 | fprintf(stderr, "test %d, decode_ipv4_address: unexpected failure\n", n_tests); 173 | } else { 174 | if (strcmp(inet_ntoa(ip), "255.255.255.252") != 0) 175 | fprintf(stderr, "test %d, decode_ipv4_address: bad IP %s, expected 255.255.255.252\n", n_tests, inet_ntoa(ip)); 176 | else 177 | success++; 178 | } 179 | 180 | strcpy(oid, "1.3.6.1.2.1.2.2.1.2.1001"); 181 | success += test_encode_string_oid(&n_tests, oid, 24, "\x06\x0b\x2b\x06\x01\x02\x01\x02\x02\x01\x02\x87\x69", 13); 182 | strcpy(oid, ".1.3.6.1.2.1.2.2.1.2.25"); 183 | success += test_encode_string_oid(&n_tests, oid, 23, "\x06\x0a\x2b\x06\x01\x02\x01\x02\x02\x01\x02\x19", 12); 184 | success += test_encode_string_oid(&n_tests, "1.3.6.1.4.1.2636.3.5.2.1.5.33.118.54.95.73.78.70.82.65.95.67.79.78.78.69.67.84.95.86.49.45.103.101.45.50.47.49.47.48.46.51.56.45.105.81.118.54.95.73.78.70.82.65.95.68.69.70.95.73.67.77.80.95.73.78.70.79.82.77.65.84.73.79.78.65.76.45.118.54.95.73.78.70.82.65.95.67.79.78.78.69.67.84.95.73.67.77.80.95.73.78.70.79.82.77.65.84.73.79.78.65.76.45.103.101.45.50.47.49.47.48.46.51.56.45.105.3", -1, 185 | "\x06\x81\x81\x2b\x06\x01\x04\x01\x94\x4c\x03\x05\x02\x01\x05\x21\x76\x36\x5f\x49\x4e\x46\x52\x41\x5f\x43\x4f\x4e\x4e\x45\x43\x54\x5f\x56\x31\x2d\x67\x65\x2d\x32\x2f\x31\x2f\x30\x2e\x33\x38\x2d\x69\x51\x76\x36\x5f\x49\x4e\x46\x52\x41\x5f\x44\x45\x46\x5f\x49\x43\x4d\x50\x5f\x49\x4e\x46\x4f\x52\x4d\x41\x54\x49\x4f\x4e\x41\x4c\x2d\x76\x36\x5f\x49\x4e\x46\x52\x41\x5f\x43\x4f\x4e\x4e\x45\x43\x54\x5f\x49\x43\x4d\x50\x5f\x49\x4e\x46\x4f\x52\x4d\x41\x54\x49\x4f\x4e\x41\x4c\x2d\x67\x65\x2d\x32\x2f\x31\x2f\x30\x2e\x33\x38\x2d\x69\x03", 186 | 132); 187 | success += test_encode_string_oid(&n_tests, "1.3.6.1.2.1.47.1.3.1.1.1.1.1954816511", -1, 188 | "\x06\x11\x2b\x06\x01\x02\x01\x2f\x01\x03\x01\x01\x01\x01\x87\xa4\x90\xc3\x7f", 189 | 19); 190 | success += test_encode_string(&n_tests, "\x04\x01x", 2); 191 | success += test_encode_string(&n_tests, "\x04\x06public", 2); 192 | success += test_encode_string(&n_tests, "\x04\x81\x97" 193 | "Every inch of wall space is covered by a bookcase. " 194 | "Each bookcase has six shelves, going almost to the ceiling. " 195 | "Some bookshelves are stacked to the brim", 3); 196 | success += test_encode_string(&n_tests, "\x04\x82\x02\x2a" 197 | "Every inch of wall space is covered by a bookcase. " 198 | "Each bookcase has six shelves, going almost to the ceiling. " 199 | "Some bookshelves are stacked to the brim with hardcover books: " 200 | "science, mathematics, history, and everything else. " 201 | "Other shelves have two layers of paperback science fiction, " 202 | "with the back layer of books propped up on old tissue boxes or " 203 | "two-by-fours, so that you can see the back layer of books " 204 | "above the books in front. And it still isn't enough. " 205 | "Books are overflowing onto the tables and the sofas " 206 | "and making little heaps under the windows.", 4); 207 | 208 | success += test_oid_compare(&n_tests, 209 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.991735.35946496.1358.1", 210 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000745.35946496.1519.1", 211 | -1); 212 | success += test_oid_compare(&n_tests, 213 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000745.35946496.1519.1", 214 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.991735.35946496.1358.1", 215 | +1); 216 | success += test_oid_compare(&n_tests, 217 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000744.35946496.1519.1", 218 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000745.35946496.1519.1", 219 | -1); 220 | success += test_oid_compare(&n_tests, 221 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000745.35946496.1519.1", 222 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000744.35946496.1519.1", 223 | +1); 224 | success += test_oid_compare(&n_tests, 225 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000745.35946496.1519.1", 226 | "1.3.6.1.4.1.6527.3.1.2.4.3.7.1.12.10000745.35946496.1519.1", 227 | 0); 228 | 229 | fprintf(stderr, "%d of %d tests passed succesfully\n", success, n_tests); 230 | 231 | { 232 | struct ber e; 233 | char buf[1500]; 234 | e = ber_init(buf, 1500); 235 | if (build_get_request_packet(1, "public", 236 | "1.3.6.1.2.1.2.2.1.2.1001\0" 237 | "1.3.6.1.2.1.2.2.1.2.25\0", 238 | 6789012, &e) < 0) 239 | { 240 | perror("build_get_request_packet"); 241 | } else { 242 | ber_dump(stderr, &e); 243 | } 244 | } 245 | return 0; 246 | } 247 | 248 | -------------------------------------------------------------------------------- /event_loop.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | struct program_stats PS; 12 | static JudyL socks = NULL; 13 | 14 | #ifdef WITH_KQUEUE 15 | int kq = -1; 16 | #endif 17 | #ifdef WITH_EPOLL 18 | int ep = -1; 19 | #endif 20 | 21 | struct socket_info * 22 | new_socket_info(int fd) 23 | { 24 | struct socket_info *si, **slot; 25 | 26 | si = malloc(sizeof(*si)); 27 | if (!si) 28 | croak(1, "new_socket_info: malloc(socket_info)"); 29 | bzero(si, sizeof(*si)); 30 | 31 | si->PS.active_client_connections = -42; 32 | si->PS.total_client_connections = -42; 33 | si->PS.active_timers_sec = -42; 34 | si->PS.active_timers_usec = -42; 35 | si->PS.total_timers_sec = -42; 36 | si->PS.total_timers_usec = -42; 37 | si->PS.bad_snmp_responses = -42; 38 | si->PS.active_oid_infos = -42; 39 | si->PS.total_oid_infos = -42; 40 | si->PS.destination_throttles = -42; 41 | si->PS.oids_ignored = -42; 42 | si->PS.destination_ignores = -42; 43 | si->PS.udp_receive_buffer_size = -42; 44 | si->PS.udp_send_buffer_size = -42; 45 | si->PS.udp_send_buffer_overflow = -42; 46 | si->PS.packets_on_the_wire = -42; 47 | si->PS.max_packets_on_the_wire = -42; 48 | si->PS.global_throttles = -42; 49 | si->PS.program_version = -42; 50 | si->PS.octets_received = -42; 51 | si->PS.octets_sent = -42; 52 | gettimeofday(&si->created, NULL); 53 | 54 | si->fd = fd; 55 | TAILQ_INIT(&si->send_bufs); 56 | JLI(slot, socks, fd); 57 | if (slot == PJERR) 58 | croak(2, "new_socket_info: JLI failed"); 59 | if (*slot) 60 | croak(3, "new_socket_info: assertion failed, fd %d is already there", fd); 61 | *slot = si; 62 | return si; 63 | } 64 | 65 | static struct iovec io_buf[IOV_MAX]; 66 | 67 | static void 68 | flush_buffers(struct socket_info *si) 69 | { 70 | struct send_buf *sb; 71 | int i, n, tot; 72 | 73 | if (!si->n_send_bufs) { 74 | fprintf(stderr, "flush_buffers: fd %d: unexpectedly nothing to flush\n", si->fd); 75 | on_write(si, NULL); 76 | return; 77 | } 78 | /* XXX handle case where there is only one specially */ 79 | i = 0; 80 | tot = 0; 81 | TAILQ_FOREACH(sb, &si->send_bufs, send_list) { 82 | io_buf[i].iov_base = sb->buf + sb->offset; 83 | io_buf[i].iov_len = sb->size - sb->offset; 84 | tot += sb->size - sb->offset; 85 | i++; 86 | if (i >= IOV_MAX) 87 | break; 88 | } 89 | if ( (n = writev(si->fd, io_buf, i)) < 0) { 90 | switch (errno) { 91 | case EPIPE: 92 | fprintf(stderr, "flush_buffers: EPIPE during writev\n"); 93 | if (si->eof_handler) si->eof_handler(si); 94 | return; 95 | case ECONNRESET: 96 | fprintf(stderr, "flush_buffers: ECONNRESET during writev\n"); 97 | if (si->eof_handler) si->eof_handler(si); 98 | return; 99 | } 100 | croak(1, "flush_buffers: writev"); 101 | } 102 | while (n > 0) { 103 | sb = TAILQ_FIRST(&si->send_bufs); 104 | if (!sb) 105 | croakx(2, "flush_buffers: send_bufs queue unexpectedly empty"); 106 | if (n >= sb->size - sb->offset) { 107 | TAILQ_REMOVE(&si->send_bufs, sb, send_list); 108 | n -= sb->size - sb->offset; 109 | free(sb->buf); 110 | free(sb); 111 | si->n_send_bufs--; 112 | } else { 113 | sb->offset += n; 114 | n = 0; 115 | } 116 | } 117 | if (TAILQ_EMPTY(&si->send_bufs)) { 118 | on_write(si, NULL); 119 | } 120 | //if (write(fd, buf, size) < 0) 121 | // croak(1, "tcp_send: write"); 122 | } 123 | 124 | void 125 | tcp_send(struct socket_info *si, void *buf, int size) 126 | { 127 | struct send_buf *sb; 128 | int buf_size; 129 | 130 | sb = TAILQ_LAST(&si->send_bufs, send_buf_head); 131 | if (sb && sb->buf_size - sb->size >= size) { 132 | memcpy(sb->buf + sb->size, buf, size); 133 | sb->size += size; 134 | return; 135 | } 136 | /* XXX in reality, try to send something right away */ 137 | sb = malloc(sizeof(*sb)); 138 | if (!sb) 139 | croak(1, "tcp_send: malloc(send_buf)"); 140 | bzero(sb, sizeof(*sb)); 141 | buf_size = size > 4096 ? size : 4096; 142 | sb->buf = malloc(buf_size); 143 | if (!sb->buf) 144 | croak(1, "tcp_send: malloc(sb->buf)"); 145 | sb->size = size; 146 | sb->buf_size = buf_size; 147 | sb->offset = 0; 148 | memcpy(sb->buf, buf, size); 149 | if (TAILQ_EMPTY(&si->send_bufs)) { 150 | on_write(si, flush_buffers); 151 | } 152 | TAILQ_INSERT_TAIL(&si->send_bufs, sb, send_list); 153 | si->n_send_bufs++; 154 | } 155 | 156 | void 157 | delete_socket_info(struct socket_info *si) 158 | { 159 | int rc; 160 | struct send_buf *n1, *n2; 161 | 162 | n1 = TAILQ_FIRST(&si->send_bufs); 163 | while (n1 != NULL) { 164 | n2 = TAILQ_NEXT(n1, send_list); 165 | free(n1->buf); 166 | free(n1); 167 | n1 = n2; 168 | } 169 | TAILQ_INIT(&si->send_bufs); 170 | si->n_send_bufs = 0; 171 | 172 | JLD(rc, socks, si->fd); 173 | close(si->fd); 174 | free(si); 175 | } 176 | 177 | #ifdef WITH_EPOLL 178 | static void 179 | set_handlers(struct socket_info *si, 180 | void (*read_handler)(struct socket_info *si), 181 | void (*write_handler)(struct socket_info *si)) 182 | { 183 | struct epoll_event set_ev; 184 | int op; 185 | int was_monitored = 0; 186 | 187 | if (ep < 0) { 188 | if ( (ep = epoll_create(10)) < 0) 189 | croak(1, "set_handlers: epoll_create"); 190 | } 191 | 192 | if (si->read_handler || si->write_handler) { 193 | was_monitored = 1; 194 | } 195 | si->read_handler = read_handler; 196 | si->write_handler = write_handler; 197 | set_ev.events = 0; 198 | if (si->read_handler) 199 | set_ev.events |= EPOLLIN; 200 | if (si->write_handler) 201 | set_ev.events |= EPOLLOUT; 202 | if (was_monitored) { 203 | if (set_ev.events) 204 | op = EPOLL_CTL_MOD; 205 | else 206 | op = EPOLL_CTL_DEL; 207 | } else { 208 | if (set_ev.events) 209 | op = EPOLL_CTL_ADD; 210 | else 211 | return; 212 | } 213 | 214 | set_ev.data.fd = si->fd; 215 | if (epoll_ctl(ep, op, si->fd, &set_ev) < 0) 216 | croak(1, "set_handlers: epoll_ctl"); 217 | } 218 | #endif 219 | 220 | void 221 | binary_dump(FILE *f, void *buf, int len) 222 | { 223 | unsigned char *s = buf; 224 | int i; 225 | 226 | for (i = 0; i < len; i++) { 227 | fprintf(f, "%02x ", (unsigned)s[i]); 228 | if (i % 16 == 15 && i < len-1) { 229 | int j; 230 | fprintf(f, " "); 231 | for (j = i - 16; j <= i; j++) { 232 | fprintf(f, "%c", isprint(s[j]) ? s[j] : '.'); 233 | } 234 | fprintf(f, "\n"); 235 | } 236 | } 237 | fprintf(f, "\n"); 238 | } 239 | 240 | void 241 | on_eof(struct socket_info *si, void (*eof_handler)(struct socket_info *si)) 242 | { 243 | si->eof_handler = eof_handler; 244 | } 245 | 246 | void 247 | on_read(struct socket_info *si, void (*read_handler)(struct socket_info *si)) 248 | { 249 | #ifdef WITH_KQUEUE 250 | si->read_handler = read_handler; 251 | if (kq < 0) { 252 | if ( (kq = kqueue()) < 0) 253 | croak(1, "on_read: kqueue"); 254 | } 255 | { 256 | struct kevent set_ke, get_ke; 257 | int nev; 258 | unsigned flags = EV_ADD | EV_RECEIPT; 259 | 260 | flags |= read_handler ? EV_ENABLE : EV_DISABLE; 261 | EV_SET(&set_ke, si->fd, EVFILT_READ, flags, 0, 0, 0); 262 | 263 | nev = kevent(kq, &set_ke, 1, &get_ke, 1, NULL); 264 | if (nev < 0) 265 | croak(1, "on_read: kevent"); 266 | if (nev != 1) 267 | croakx(1, "on_read: unexpected nev %d", nev); 268 | if ((get_ke.flags & EV_ERROR) == 0) 269 | croakx(1, "on_read: unexpectedly EV_ERROR is not set"); 270 | if (get_ke.data != 0) { 271 | errno = get_ke.data; 272 | croak(1, "on_read: kevent (error in data)"); 273 | } 274 | } 275 | #endif 276 | #ifdef WITH_EPOLL 277 | set_handlers(si, read_handler, si->write_handler); 278 | #endif 279 | } 280 | 281 | void 282 | on_write(struct socket_info *si, void (*write_handler)(struct socket_info *si)) 283 | { 284 | si->write_handler = write_handler; 285 | #ifdef WITH_KQUEUE 286 | if (kq < 0) { 287 | if ( (kq = kqueue()) < 0) 288 | croak(1, "on_write: kqueue"); 289 | } 290 | { 291 | struct kevent set_ke, get_ke; 292 | int nev; 293 | unsigned flags = EV_ADD | EV_RECEIPT; 294 | 295 | flags |= write_handler ? EV_ENABLE : EV_DISABLE; 296 | EV_SET(&set_ke, si->fd, EVFILT_WRITE, flags, 0, 0, 0); 297 | 298 | nev = kevent(kq, &set_ke, 1, &get_ke, 1, NULL); 299 | if (nev < 0) 300 | croak(1, "on_write: kevent"); 301 | if (nev != 1) 302 | croakx(1, "on_write: unexpected nev %d", nev); 303 | if ((get_ke.flags & EV_ERROR) == 0) 304 | croakx(1, "on_write: unexpectedly EV_ERROR is not set"); 305 | if (get_ke.data != 0) { 306 | errno = get_ke.data; 307 | croak(1, "on_write: kevent (error in data)"); 308 | } 309 | } 310 | #endif 311 | #ifdef WITH_EPOLL 312 | set_handlers(si, si->read_handler, write_handler); 313 | #endif 314 | } 315 | 316 | #ifdef WITH_KQUEUE 317 | void 318 | event_loop(void) 319 | { 320 | struct kevent ke[10]; 321 | int nev, i, ms; 322 | struct timespec to; 323 | while (1) { 324 | ms = ms_to_next_timer(); 325 | to.tv_sec = ms / 1000; 326 | to.tv_nsec = (ms % 1000)*1000000; 327 | nev = kevent(kq, NULL, 0, ke, 10, &to); 328 | if (nev < 0) 329 | croak(1, "event_loop: kevent"); 330 | for (i = 0; i < nev; i++) { 331 | struct socket_info *si, **slot; 332 | 333 | if (ke[i].filter == EVFILT_READ) { 334 | JLG(slot, socks, ke[i].ident); 335 | if (slot && *slot) { 336 | si = *slot; 337 | if (ke[i].flags & EV_EOF) { 338 | if (si->eof_handler) { 339 | si->eof_handler(si); 340 | } else { 341 | fprintf(stderr, "event_loop: EVFILT_READ: ident %u - socket does not have an eof handler\n", (unsigned)ke[i].ident); 342 | } 343 | } else { 344 | if (si->read_handler) { 345 | si->read_handler(si); 346 | } else { 347 | fprintf(stderr, "event_loop: EVFILT_READ: ident %u - socket does not have a read handler\n", (unsigned)ke[i].ident); 348 | } 349 | } 350 | } 351 | } else if (ke[i].filter == EVFILT_WRITE) { 352 | JLG(slot, socks, ke[i].ident); 353 | if (slot && *slot) { 354 | si = *slot; 355 | if (ke[i].flags & EV_EOF) { 356 | if (si->eof_handler) { 357 | si->eof_handler(si); 358 | } else { 359 | fprintf(stderr, "event_loop: EVFILT_WRITE: ident %u - socket does not have an eof handler\n", (unsigned)ke[i].ident); 360 | } 361 | } else { 362 | if (si->write_handler) { 363 | si->write_handler(si); 364 | } else { 365 | fprintf(stderr, "event_loop: EVFILT_WRITE: ident %u - socket does not have a write handler\n", (unsigned)ke[i].ident); 366 | } 367 | } 368 | } 369 | } else { 370 | fprintf(stderr, "event_loop: unexpected filter value %d, ident %u\n", ke[i].filter, (unsigned)ke[i].ident); 371 | } 372 | } 373 | trigger_timers(); 374 | } 375 | } 376 | #endif 377 | #ifdef WITH_EPOLL 378 | void 379 | event_loop(void) 380 | { 381 | struct epoll_event ev[10]; 382 | int nev, i, ms; 383 | while (1) { 384 | ms = ms_to_next_timer(); 385 | nev = epoll_wait(ep, ev, 10, ms); 386 | if (nev < 0) 387 | croak(1, "event_loop: epoll_wait"); 388 | for (i = 0; i < nev; i++) { 389 | struct socket_info *si, **slot; 390 | 391 | if ((ev[i].events & EPOLLIN)) { 392 | JLG(slot, socks, ev[i].data.fd); 393 | if (slot && *slot) { 394 | si = *slot; 395 | if (si->read_handler) { 396 | si->read_handler(si); 397 | } else { 398 | fprintf(stderr, "event_loop: EPOLLIN: fd %u - socket does not have a read handler\n", (unsigned)ev[i].data.fd); 399 | } 400 | } 401 | } 402 | if ((ev[i].events & EPOLLOUT)) { 403 | JLG(slot, socks, ev[i].data.fd); 404 | if (slot && *slot) { 405 | si = *slot; 406 | if (si->write_handler) { 407 | si->write_handler(si); 408 | } else { 409 | fprintf(stderr, "event_loop: EPOLLOUT: fd %u - socket does not have a write handler\n", (unsigned)ev[i].data.fd); 410 | } 411 | } 412 | } 413 | if (!(ev[i].events & (EPOLLIN|EPOLLOUT))) { 414 | fprintf(stderr, "event_loop: unexpected event 0x%x, fd %u\n", ev[i].events, (unsigned)ev[i].data.fd); 415 | } 416 | } 417 | trigger_timers(); 418 | } 419 | } 420 | #endif 421 | -------------------------------------------------------------------------------- /sid_info.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2014, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | struct sid_info * 12 | new_sid_info(struct client_requests_info *cri) 13 | { 14 | struct sid_info *si, **si_slot; 15 | unsigned sid; 16 | struct destination *dest; 17 | 18 | sid = next_sid(); 19 | dest = cri->dest; 20 | JLI(si_slot, dest->sid_info, sid); 21 | if (si_slot == PJERR) 22 | croak(2, "new_sid_info: JLI(sid_info) failed"); 23 | if (*si_slot) 24 | croak(2, "new_sid_info: sid_info must not be there"); 25 | si = malloc(sizeof(*si)); 26 | if (!si) 27 | croak(2, "new_sid_info: malloc(sid_info)"); 28 | 29 | bzero(si, sizeof(*si)); 30 | si->sid = sid; 31 | si->cri = cri; 32 | si->retries_left = cri->retries; 33 | si->version = cri->version; 34 | TAILQ_INIT(&si->oids_being_queried); 35 | if (start_snmp_packet(&si->pb, si->version, cri->community, sid) < 0) 36 | croak(2, "new_sid_info: start_snmp_get_packet"); 37 | *si_slot = si; 38 | TAILQ_INSERT_TAIL(&cri->sid_infos, si, sid_list); 39 | 40 | PS.active_sid_infos++; 41 | PS.total_sid_infos++; 42 | cri->si->PS.active_sid_infos++; 43 | cri->si->PS.total_sid_infos++; 44 | return si; 45 | } 46 | 47 | struct sid_info * 48 | find_sid_info(struct destination *dest, unsigned sid) 49 | { 50 | struct sid_info **si_slot; 51 | 52 | JLG(si_slot, dest->sid_info, sid); 53 | if (si_slot == PJERR || !si_slot) return NULL; 54 | return *si_slot; 55 | } 56 | 57 | void 58 | build_snmp_query(struct client_requests_info *cri) 59 | { 60 | struct oid_info *oi, *oi_temp; 61 | struct sid_info *si = NULL; 62 | struct destination *dest; 63 | struct cid_info *ci; 64 | int extra_size; 65 | 66 | if (TAILQ_EMPTY(&cri->oids_to_query)) return; /* XXX */ 67 | 68 | dest = cri->dest; 69 | si = new_sid_info(cri); 70 | 71 | oi = TAILQ_FIRST(&cri->oids_to_query); 72 | if (oi->last_known_table_entry) { 73 | /* GETTABLE */ 74 | if (add_encoded_oid_to_snmp_packet(&si->pb, &oi->last_known_table_entry->oid) < 0) 75 | croak(2, "build_snmp_query: add_encoded_oid_to_snmp_packet"); 76 | TAILQ_REMOVE(&cri->oids_to_query, oi, oid_list); 77 | oi->sid = si->sid; 78 | ci = get_cid_info(cri, oi->cid); 79 | if (!ci || ci->n_oids == 0) 80 | croakx(2, "build_snmp_query: cid_info unexpectedly missing for table oid"); 81 | ci->n_oids_being_queried++; 82 | 83 | si->table_oid = oi; 84 | PS.oids_requested++; 85 | cri->si->PS.oids_requested++; 86 | 87 | if ( (si->sid_offset_in_a_packet = 88 | finalize_snmp_packet(&si->pb, &si->packet, 89 | cri->version == 0 ? PDU_GET_NEXT_REQUEST : PDU_GET_BULK_REQUEST, 90 | si->table_oid->max_repetitions)) < 0) 91 | croak(2, "build_snmp_query: finalize_snmp_packet"); 92 | } else { 93 | int oids_in_request = 0; 94 | int estimated_values_size = 0; 95 | 96 | TAILQ_FOREACH_SAFE(oi, &cri->oids_to_query, oid_list, oi_temp) { 97 | if (oi->last_known_table_entry) continue; /* Skip GETTABLE requests */ 98 | extra_size = 4; 99 | oids_in_request++; 100 | estimated_values_size += dest->estimated_value_size; 101 | 102 | if (oi->oid.len >= 128) extra_size++; 103 | if (si->pb.e.len + oi->oid.len + extra_size >= dest->max_request_packet_size) 104 | break; 105 | if (oids_in_request > dest->max_oids_per_request) 106 | break; 107 | if (si->pb.e.len + oi->oid.len + extra_size + estimated_values_size >= dest->max_reply_packet_size) 108 | break; 109 | 110 | PS.oids_requested++; 111 | cri->si->PS.oids_requested++; 112 | if (add_encoded_oid_to_snmp_packet(&si->pb, &oi->oid) < 0) 113 | croak(2, "build_snmp_query: add_encoded_oid_to_snmp_packet"); 114 | TAILQ_REMOVE(&cri->oids_to_query, oi, oid_list); 115 | oi->sid = si->sid; 116 | ci = get_cid_info(cri, oi->cid); 117 | if (!ci || ci->n_oids == 0) 118 | croakx(2, "build_snmp_query: cid_info unexpectedly missing"); 119 | ci->n_oids_being_queried++; 120 | TAILQ_INSERT_TAIL(&si->oids_being_queried, oi, oid_list); 121 | } 122 | if ( (si->sid_offset_in_a_packet = finalize_snmp_packet(&si->pb, &si->packet, PDU_GET_REQUEST, 0)) < 0) 123 | croak(2, "build_snmp_query: finalize_snmp_packet"); 124 | } 125 | sid_start_timing(si); 126 | si->retries_left--; 127 | 128 | PS.snmp_sends++; 129 | si->cri->si->PS.snmp_sends++; 130 | if (si->version == 0) { 131 | PS.snmp_v1_sends++; 132 | si->cri->si->PS.snmp_v1_sends++; 133 | } else { 134 | PS.snmp_v2c_sends++; 135 | si->cri->si->PS.snmp_v2c_sends++; 136 | } 137 | snmp_send(dest, &si->packet); 138 | } 139 | 140 | void 141 | sid_start_timing(struct sid_info *si) 142 | { 143 | struct timer *t; 144 | 145 | set_timeout(&si->will_timeout_at, si->cri->timeout); 146 | t = new_timer(&si->will_timeout_at); 147 | TAILQ_INSERT_TAIL(&t->timed_out_sids, si, timer_chain); 148 | } 149 | 150 | void 151 | sid_stop_timing(struct sid_info *si) 152 | { 153 | struct timer *t; 154 | 155 | t = find_timer(&si->will_timeout_at); 156 | if (t) TAILQ_REMOVE(&t->timed_out_sids, si, timer_chain); 157 | bzero(&si->will_timeout_at, sizeof(si->will_timeout_at)); 158 | } 159 | 160 | void 161 | free_sid_info(struct sid_info *si) 162 | { 163 | Word_t rc; 164 | /* The equivalent of 165 | * TAILQ_REMOVE(&si->cri->sid_infos, si, sid_list); 166 | * should be done by the caller. 167 | * Reason: free_client_request_info() does it more 168 | * efficiently and thus does not need to TAILQ_REMOVE. 169 | */ 170 | PS.active_sid_infos--; 171 | si->cri->si->PS.active_sid_infos--; 172 | 173 | TAILQ_REMOVE(&si->cri->sid_infos, si, sid_list); 174 | JLD(rc, si->cri->dest->sid_info, si->sid); 175 | sid_stop_timing(si); 176 | free(si->packet.buf); 177 | free_oid_info_list(&si->oids_being_queried); 178 | if (si->table_oid) 179 | free_oid_info(si->table_oid); 180 | free(si); 181 | } 182 | 183 | void resend_query_with_new_sid(struct sid_info *si) 184 | { 185 | struct sid_info **si_slot; 186 | struct oid_info *oi; 187 | Word_t rc; 188 | 189 | JLD(rc, si->cri->dest->sid_info, si->sid); 190 | si->sid = next_sid(); 191 | si->packet.buf[si->sid_offset_in_a_packet+0] = (si->sid >> 24) & 0xff; 192 | si->packet.buf[si->sid_offset_in_a_packet+1] = (si->sid >> 16) & 0xff; 193 | si->packet.buf[si->sid_offset_in_a_packet+2] = (si->sid >> 8) & 0xff; 194 | si->packet.buf[si->sid_offset_in_a_packet+3] = si->sid & 0xff; 195 | 196 | TAILQ_FOREACH(oi, &si->oids_being_queried, oid_list) { 197 | oi->sid = si->sid; 198 | } 199 | 200 | JLI(si_slot, si->cri->dest->sid_info, si->sid); 201 | if (si_slot == PJERR) 202 | croak(2, "resend_query_with_new_sid: JLI(sid_info) failed"); 203 | if (*si_slot) 204 | croak(2, "resend_query_with_new_sid: sid_info must not be there"); 205 | *si_slot = si; 206 | 207 | sid_start_timing(si); 208 | si->retries_left--; 209 | 210 | PS.snmp_sends++; 211 | si->cri->si->PS.snmp_sends++; 212 | if (si->version == 0) { 213 | PS.snmp_v1_sends++; 214 | si->cri->si->PS.snmp_v1_sends++; 215 | } else { 216 | PS.snmp_v2c_sends++; 217 | si->cri->si->PS.snmp_v2c_sends++; 218 | } 219 | PS.snmp_retries++; 220 | si->cri->si->PS.snmp_retries++; 221 | snmp_send(si->cri->dest, &si->packet); 222 | } 223 | 224 | void 225 | sid_timer(struct sid_info *si) 226 | { 227 | struct destination *dest; 228 | PS.udp_timeouts++; 229 | si->cri->si->PS.udp_timeouts++; 230 | si->cri->dest->packets_on_the_wire--; 231 | if (si->cri->dest->packets_on_the_wire < 0) 232 | si->cri->dest->packets_on_the_wire = 0; 233 | PS.packets_on_the_wire--; 234 | if (PS.packets_on_the_wire < 0) 235 | PS.packets_on_the_wire = 0; 236 | // fprintf(stderr, "%s: sid_timer->(%d)\n", inet_ntoa(si->cri->dest->ip), si->cri->dest->packets_on_the_wire); 237 | sid_stop_timing(si); 238 | if (si->retries_left > 0) { 239 | resend_query_with_new_sid(si); 240 | return; 241 | } 242 | PS.snmp_timeouts++; 243 | si->cri->si->PS.snmp_timeouts++; 244 | 245 | if (si->table_oid) { 246 | oid_done(si, si->table_oid, &BER_TIMEOUT, RT_GETTABLE, 0); 247 | si->table_oid = NULL; 248 | } else { 249 | all_oids_done(si, &BER_TIMEOUT); 250 | } 251 | dest = si->cri->dest; 252 | free_sid_info(si); 253 | dest->timeouts_in_a_row++; 254 | maybe_query_destination(dest); 255 | } 256 | 257 | void 258 | oid_done(struct sid_info *si, struct oid_info *oi, struct ber *val, int op, int error_status) 259 | { 260 | struct client_requests_info *cri; 261 | struct cid_info *ci; 262 | 263 | cri = si->cri; 264 | ci = get_cid_info(cri, oi->cid); 265 | if (!ci || ci->n_oids == 0) 266 | croakx(2, "oid_done: cid_info unexpectedly missing"); 267 | /* XXX free old value? */ 268 | if (ber_is_null(val) && error_status) 269 | oi->value = ber_error_status(error_status); 270 | else 271 | oi->value = ber_rewind(ber_dup(val)); 272 | oi->sid = 0; 273 | if (op != RT_GETTABLE) 274 | TAILQ_REMOVE(&si->oids_being_queried, oi, oid_list); 275 | TAILQ_INSERT_TAIL(&ci->oids_done, oi, oid_list); 276 | if (val == &BER_IGNORED) PS.oids_ignored++; 277 | ci->n_oids_being_queried--; 278 | ci->n_oids_done++; 279 | if (ci->n_oids_done == ci->n_oids) 280 | cid_reply(ci, op); 281 | } 282 | 283 | void 284 | got_table_oid(struct sid_info *si, struct oid_info *table_oi, struct ber *oid, struct ber *val, int error_status) 285 | { 286 | struct client_requests_info *cri; 287 | struct cid_info *ci; 288 | struct oid_info *oi; 289 | 290 | cri = si->cri; 291 | ci = get_cid_info(cri, table_oi->cid); 292 | if (!ci || ci->n_oids == 0) 293 | croakx(2, "got_table_oid: cid_info unexpectedly missing"); 294 | 295 | oi = malloc(sizeof(*oi)); 296 | if (!oi) 297 | croak(2, "got_table_oid: malloc(oid_info)"); 298 | bzero(oi, sizeof(*oi)); 299 | oi->cid = table_oi->cid; 300 | oi->fd = ci->fd; 301 | oi->oid = ber_dup(oid); 302 | if (ber_is_null(val) && error_status) 303 | oi->value = ber_error_status(error_status); 304 | else 305 | oi->value = ber_rewind(ber_dup(val)); 306 | oi->sid = 0; 307 | table_oi->last_known_table_entry = oi; 308 | 309 | PS.active_oid_infos++; 310 | PS.total_oid_infos++; 311 | 312 | TAILQ_INSERT_TAIL(&ci->oids_done, oi, oid_list); 313 | ci->n_oids_done++; 314 | ci->n_oids++; 315 | } 316 | 317 | void 318 | all_oids_done(struct sid_info *si, struct ber *val) 319 | { 320 | struct oid_info *oi, *oi_temp; 321 | /* XXX handle si->table_oid stuff as well */ 322 | TAILQ_FOREACH_SAFE(oi, &si->oids_being_queried, oid_list, oi_temp) { 323 | oid_done(si, oi, val, RT_GET, 0); 324 | } 325 | } 326 | 327 | int 328 | process_sid_info_response(struct sid_info *si, struct ber *e) 329 | { 330 | unsigned error_status; 331 | unsigned error_index; 332 | char *trace; 333 | int oids_stop; 334 | struct ber oid, val; 335 | struct oid_info *oi; 336 | int table_done = 0; 337 | struct cid_info *ci; 338 | struct client_requests_info *cri; 339 | 340 | /* SNMP packet must be positioned past request id field */ 341 | 342 | cri = si->cri; 343 | 344 | #define CHECK(prob, val) if ((val) < 0) { trace = prob; goto bad_snmp_packet; } 345 | CHECK("decoding error status", decode_integer(e, -1, &error_status)); 346 | CHECK("decoding error index", decode_integer(e, -1, &error_index)); 347 | CHECK("oids sequence", decode_sequence(e, &oids_stop)); 348 | while (inside_sequence(e, oids_stop)) { 349 | CHECK("bindvar", decode_sequence(e, NULL)); 350 | CHECK("oid", decode_oid(e, &oid)); 351 | CHECK("value", decode_any(e, &val)); 352 | PS.oids_returned_from_snmp++; 353 | cri->si->PS.oids_returned_from_snmp++; 354 | if (si->table_oid) { 355 | if (oid_belongs_to_table(&oid, &si->table_oid->oid)) { 356 | if (si->table_oid->last_known_table_entry && 357 | oid_compare(&oid, &si->table_oid->last_known_table_entry->oid) < 0) 358 | { 359 | got_table_oid(si, si->table_oid, &oid, &BER_NON_INCREASING, error_status); 360 | PS.oids_non_increasing++; 361 | cri->si->PS.oids_non_increasing++; 362 | table_done = 1; 363 | break; 364 | } else { 365 | got_table_oid(si, si->table_oid, &oid, &val, error_status); 366 | } 367 | } else { 368 | table_done = 1; 369 | break; 370 | } 371 | } else { 372 | TAILQ_FOREACH(oi, &si->oids_being_queried, oid_list) { 373 | if (ber_equal(&oid, &oi->oid)) { 374 | oid_done(si, oi, &val, RT_GET, error_status); 375 | break; 376 | } 377 | } 378 | } 379 | } 380 | if (si->table_oid) { 381 | ci = get_cid_info(cri, si->table_oid->cid); 382 | ci->n_oids_being_queried--; 383 | if (table_done) { 384 | free_oid_info(si->table_oid); 385 | si->table_oid = NULL; 386 | ci->n_oids--; 387 | if (ci->n_oids_done == ci->n_oids) 388 | cid_reply(ci, RT_GETTABLE); 389 | } else { 390 | TAILQ_INSERT_TAIL(&cri->oids_to_query, si->table_oid, oid_list); 391 | si->table_oid = NULL; 392 | } 393 | } else { 394 | if (!TAILQ_EMPTY(&si->oids_being_queried)) { 395 | fprintf(stderr, "SID %u: unexpectedly, not all oids are accounted for!\n", si->sid); 396 | all_oids_done(si, &BER_MISSING); 397 | } 398 | } 399 | PS.good_snmp_responses++; 400 | cri->si->PS.good_snmp_responses++; 401 | cri->dest->timeouts_in_a_row = 0; 402 | bzero(&cri->dest->ignore_until, sizeof(cri->dest->ignore_until)); 403 | #undef CHECK 404 | 405 | return 1; 406 | bad_snmp_packet: 407 | PS.bad_snmp_responses++; 408 | fprintf(stderr, "sid %u: bad SNMP packet, ignoring: %s\n", si->sid, trace); 409 | return 0; 410 | } 411 | 412 | void 413 | dump_sid_info(msgpack_packer *pk, struct sid_info *si) 414 | { 415 | char buf[512]; 416 | Word_t n_oids; 417 | struct oid_info *oi; 418 | 419 | #define PACK msgpack_pack_string(pk, buf) 420 | #define DUMPi(field) msgpack_pack_named_int(pk, #field, si->field) 421 | #define DUMPs(field) msgpack_pack_named_string(pk, #field, si->field) 422 | snprintf(buf, 512, "SID(%d)", si->sid); PACK; 423 | msgpack_pack_map(pk, 7); 424 | 425 | msgpack_pack_string(pk, "cri"); 426 | snprintf(buf, 512, "CRI(%s:%d->%d)", inet_ntoa(si->cri->dest->ip), si->cri->dest->port, si->cri->fd); PACK; 427 | 428 | DUMPi(retries_left); 429 | DUMPi(version); 430 | msgpack_pack_string(pk, "table_oid"); 431 | if (si->table_oid) { 432 | dump_oid_info(pk, si->table_oid); 433 | } else { 434 | msgpack_pack_nil(pk); 435 | } 436 | msgpack_pack_string(pk, "last_known_table_oid"); 437 | if (si->table_oid && si->table_oid->last_known_table_entry) { 438 | dump_oid_info(pk, si->table_oid->last_known_table_entry); 439 | } else { 440 | msgpack_pack_nil(pk); 441 | } 442 | 443 | n_oids = 0; 444 | TAILQ_FOREACH(oi, &si->oids_being_queried, oid_list) { 445 | n_oids++; 446 | } 447 | msgpack_pack_named_int(pk, "#OID", n_oids); 448 | 449 | msgpack_pack_string(pk, "@OID"); 450 | msgpack_pack_array(pk, n_oids); 451 | TAILQ_FOREACH(oi, &si->oids_being_queried, oid_list) { 452 | dump_oid_info(pk, oi); 453 | } 454 | 455 | #undef DUMPi 456 | #undef DUMPs 457 | #undef PACK 458 | } 459 | -------------------------------------------------------------------------------- /sqe.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2014, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #ifndef _COMMON_H 10 | #define _COMMON_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #define __USE_XOPEN 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #if defined(__FreeBSD__) || defined(__APPLE__) 30 | #include 31 | #define WITH_KQUEUE 1 32 | #endif 33 | #if defined(__linux__) 34 | #include 35 | #define WITH_EPOLL 1 36 | #endif 37 | 38 | #include 39 | #include 40 | 41 | #if MSGPACK_VERSION_MAJOR == 0 && MSGPACK_VERSION_MINOR < 6 42 | #error libmsgpackc is too old, at least v0.6 needed 43 | #endif 44 | 45 | #include "bsdqueue.h" 46 | 47 | #define RT_UNKNOWN 0 48 | #define RT_SETOPT 1 49 | #define RT_GETOPT 2 50 | #define RT_INFO 3 51 | #define RT_GET 4 52 | #define RT_GETTABLE 5 53 | #define RT_DEST_INFO 6 54 | 55 | #define RT_REPLY 0x10 /* ORed with RT type */ 56 | #define RT_ERROR 0x20 /* ORed with RT type */ 57 | 58 | #define RI_TYPE 0 59 | #define RI_CID 1 60 | 61 | #define RI_GETOPT_IP 2 62 | #define RI_GETOPT_PORT 3 63 | 64 | #define RI_SETOPT_IP 2 65 | #define RI_SETOPT_PORT 3 66 | #define RI_SETOPT_OPT 4 67 | 68 | #define RI_GET_IP 2 69 | #define RI_GET_PORT 3 70 | #define RI_GET_OIDS 4 71 | 72 | #define RI_GETTABLE_IP 2 73 | #define RI_GETTABLE_PORT 3 74 | #define RI_GETTABLE_OID 4 75 | #define RI_GETTABLE_MREP 5 76 | 77 | #define RI_DEST_INFO_IP 2 78 | #define RI_DEST_INFO_PORT 3 79 | 80 | #define AT_INTEGER 2 81 | #define AT_STRING 4 82 | #define AT_NULL 5 83 | #define AT_OID 6 84 | #define AT_SEQUENCE 0x30 85 | 86 | #define AT_IP_ADDRESS 0x40 87 | #define AT_COUNTER 0x41 88 | #define AT_UNSIGNED 0x42 89 | #define AT_TIMETICKS 0x43 90 | #define AT_OPAQUE 0x44 91 | #define AT_COUNTER64 0x46 92 | 93 | #define AT_NO_SUCH_OBJECT 0x80 94 | #define AT_NO_SUCH_INSTANCE 0x81 95 | #define AT_END_OF_MIB_VIEW 0x82 96 | 97 | #define PDU_GET_REQUEST 0xa0 98 | #define PDU_GET_NEXT_REQUEST 0xa1 99 | #define PDU_GET_RESPONSE 0xa2 100 | #define PDU_SET_REQUEST 0xa3 101 | #define PDU_TRAP 0xa4 102 | #define PDU_GET_BULK_REQUEST 0xa5 103 | #define PDU_INFORM_REQUEST 0xa6 104 | #define PDU_SNMPV2_TRAP 0xa7 105 | #define PDU_REPORT 0xa8 106 | 107 | /* Our own "ASN" types, used internally */ 108 | #define VAL_TIMEOUT 0x8a 109 | #define VAL_MISSING 0x8b 110 | #define VAL_UNSUPPORTED 0x8c 111 | #define VAL_DECODE_ERROR 0x8d 112 | #define VAL_IGNORED 0x8e 113 | #define VAL_NON_INCREASING 0x8f 114 | #define VAL_STRING_ERROR 0x90 115 | 116 | #define MAX_OID 4294967295u /* 2^35-1 to fit into 5 bytes, but we limit that to 2^32-1 */ 117 | 118 | typedef void* JudyL; 119 | 120 | struct program_stats 121 | { 122 | int64_t active_client_connections; 123 | int64_t total_client_connections; 124 | 125 | int64_t client_requests; 126 | int64_t invalid_requests; 127 | int64_t setopt_requests; 128 | int64_t getopt_requests; 129 | int64_t info_requests; 130 | int64_t get_requests; 131 | int64_t gettable_requests; 132 | int64_t dest_info_requests; 133 | 134 | int64_t snmp_sends; 135 | int64_t snmp_v1_sends; 136 | int64_t snmp_v2c_sends; 137 | int64_t snmp_retries; 138 | int64_t snmp_timeouts; 139 | int64_t udp_timeouts; 140 | int64_t good_snmp_responses; 141 | int64_t bad_snmp_responses; 142 | int64_t oids_non_increasing; 143 | int64_t oids_requested; 144 | int64_t oids_returned_from_snmp; 145 | int64_t oids_returned_to_client; 146 | int64_t oids_ignored; 147 | 148 | int64_t octets_received; 149 | int64_t octets_sent; 150 | 151 | int64_t active_timers_sec; 152 | int64_t active_timers_usec; 153 | int64_t total_timers_sec; 154 | int64_t total_timers_usec; 155 | int64_t uptime; /* in msec */ 156 | int64_t program_version; /* such as 2013072200 */ 157 | 158 | int64_t active_sid_infos; 159 | int64_t total_sid_infos; 160 | int64_t active_oid_infos; 161 | int64_t total_oid_infos; 162 | int64_t active_cid_infos; 163 | int64_t total_cid_infos; 164 | int64_t active_cr_infos; 165 | int64_t total_cr_infos; 166 | 167 | int64_t destination_throttles; /* due to max_packets_on_the_wire limit */ 168 | int64_t destination_ignores; 169 | 170 | int64_t udp_receive_buffer_size; 171 | int64_t udp_send_buffer_size; 172 | int64_t udp_send_buffer_overflow; 173 | int64_t packets_on_the_wire; 174 | int64_t max_packets_on_the_wire; 175 | int64_t global_throttles; 176 | }; 177 | 178 | extern struct timeval prog_start; 179 | extern struct program_stats PS; 180 | 181 | struct ber 182 | { 183 | unsigned char *buf; 184 | unsigned char *b; 185 | int len; 186 | int max_len; 187 | }; 188 | 189 | extern struct ber BER_NULL; 190 | extern struct ber BER_TIMEOUT; 191 | extern struct ber BER_MISSING; 192 | extern struct ber BER_IGNORED; 193 | extern struct ber BER_NON_INCREASING; 194 | 195 | struct packet_builder 196 | { 197 | unsigned char *packet_sequence; 198 | unsigned char *pdu; 199 | unsigned char *oid_sequence; 200 | unsigned char *max_repetitions; 201 | int sid_offset; 202 | struct ber e; 203 | }; 204 | 205 | struct send_buf 206 | { 207 | TAILQ_ENTRY(send_buf) send_list; 208 | unsigned char *buf; 209 | int size; 210 | int buf_size; 211 | int offset; 212 | }; 213 | 214 | struct socket_info 215 | { 216 | int fd; 217 | struct timeval created; 218 | void *udata; 219 | void (*read_handler)(struct socket_info *si); 220 | void (*write_handler)(struct socket_info *si); 221 | void (*eof_handler)(struct socket_info *si); 222 | int n_send_bufs; 223 | TAILQ_HEAD(send_buf_head, send_buf) send_bufs; 224 | struct program_stats PS; 225 | }; 226 | 227 | struct client_connection 228 | { 229 | msgpack_unpacker unpacker; 230 | msgpack_unpacked input; 231 | }; 232 | 233 | #define DEFAULT_MAX_PACKETS_ON_THE_WIRE 3 234 | #define DEFAULT_MAX_REQUEST_PACKET_SIZE 1400 235 | #define DEFAULT_MAX_REPLY_PACKET_SIZE 1472 236 | #define DEFAULT_ESTIMATED_VALUE_SIZE 9 237 | #define DEFAULT_MAX_OIDS_PER_REQUEST 64 238 | #define DEFAULT_TIMEOUT 2000 239 | #define DEFAULT_RETRIES 3 240 | #define DEFAULT_MIN_INTERVAL 10 241 | #define DEFAULT_MAX_REPETITIONS 10 242 | #define DEFAULT_IGNORE_THRESHOLD 0 243 | #define DEFAULT_IGNORE_DURATION 300000 244 | 245 | TAILQ_HEAD(oid_info_head, oid_info); 246 | TAILQ_HEAD(sid_info_head, sid_info); 247 | TAILQ_HEAD(client_requests_info_head, client_requests_info); 248 | TAILQ_HEAD(destination_head, destination); 249 | 250 | struct destination 251 | { 252 | TAILQ_ENTRY(destination) timer_chain; 253 | struct in_addr ip; 254 | unsigned port; 255 | struct sockaddr_in dest_addr; 256 | 257 | int packets_on_the_wire; 258 | struct timeval can_query_at; 259 | int fd_of_last_query; 260 | JudyL client_requests_info; /* JudyL of struct client_requests_info indexed by fd */ 261 | JudyL sid_info; /* JudyL of struct sid_info indexed by sid */ 262 | int timeouts_in_a_row; 263 | struct timeval ignore_until; 264 | 265 | /* setopt-controlled parameters */ 266 | int max_packets_on_the_wire; 267 | int max_request_packet_size; 268 | int max_reply_packet_size; 269 | int estimated_value_size; 270 | int max_oids_per_request; 271 | int min_interval; 272 | int max_repetitions; 273 | int ignore_threshold; 274 | int ignore_duration; 275 | 276 | /* destination-specific stats */ 277 | int64_t octets_received; 278 | int64_t octets_sent; 279 | }; 280 | 281 | struct client_requests_info 282 | { 283 | struct destination *dest; 284 | struct socket_info *si; 285 | int fd; 286 | JudyL cid_info; /* JudyL of struct cid_info ("cid" = client id) indexed by cid */ 287 | struct oid_info_head oids_to_query; 288 | struct sid_info_head sid_infos; 289 | 290 | /* setopt-controlled parameters */ 291 | unsigned version; 292 | char community[256]; 293 | int timeout; 294 | int retries; 295 | }; 296 | 297 | struct cid_info 298 | { 299 | unsigned cid; 300 | int fd; 301 | struct client_requests_info *cri; 302 | int n_oids; 303 | int n_oids_being_queried; 304 | int n_oids_done; 305 | struct oid_info_head oids_done; 306 | }; 307 | 308 | struct timer 309 | { 310 | struct timeval when; 311 | struct sid_info_head timed_out_sids; 312 | struct destination_head throttled_destinations; 313 | }; 314 | 315 | struct sid_info 316 | { 317 | TAILQ_ENTRY(sid_info) sid_list; 318 | TAILQ_ENTRY(sid_info) timer_chain; 319 | unsigned sid; 320 | struct client_requests_info *cri; 321 | struct timeval will_timeout_at; 322 | int retries_left; 323 | unsigned version; 324 | 325 | struct packet_builder pb; 326 | struct ber packet; 327 | int sid_offset_in_a_packet; 328 | /* For a given SNMP request, either table_oid is not NULL, 329 | * or oids_being_queried is non-empty. This distinguishes 330 | * between table walks and normal gets. */ 331 | struct oid_info *table_oid; /* starting oid for GETTABLE */ 332 | struct oid_info_head oids_being_queried; 333 | }; 334 | 335 | struct oid_info 336 | { 337 | TAILQ_ENTRY(oid_info) oid_list; 338 | unsigned sid; 339 | unsigned cid; 340 | int fd; 341 | /* If last_known_table_entry is not NULL, this structure 342 | * represents the requested table. If it is NULL, 343 | * then this structure is just a normal OID being 344 | * requested. */ 345 | struct oid_info *last_known_table_entry; 346 | int max_repetitions; /* only applicable when last_known_table_entry is not NULL */ 347 | struct ber oid; 348 | struct ber value; 349 | }; 350 | 351 | extern int opt_quiet; 352 | 353 | /* ber.c */ 354 | extern struct ber ber_init(void *buf, int size); 355 | extern struct ber ber_dup(struct ber *e); 356 | extern struct ber ber_rewind(struct ber o); 357 | extern void ber_dump(FILE *f, struct ber *e); 358 | extern int ber_equal(struct ber *b1, struct ber *b2); 359 | extern int ber_is_null(struct ber *ber); 360 | extern struct ber ber_error_status(int error_status); 361 | 362 | extern int encode_type_len(unsigned char type, unsigned i, struct ber *e); 363 | extern int encode_integer(unsigned i, struct ber *e, int force_size); 364 | extern int encode_string(const char *s, struct ber *e); 365 | extern int encode_string_oid(const char *oid, int oid_len, struct ber *e); 366 | extern int encode_store_length(struct ber *e, unsigned char *s); 367 | 368 | extern int decode_type_len(struct ber *e, unsigned char *type, unsigned *len); 369 | extern int decode_integer(struct ber *e, int int_len, unsigned *value); 370 | extern int decode_ipv4_address(struct ber *e, int l, struct in_addr *ip); 371 | extern int decode_counter64(struct ber *e, int int_len, unsigned long long *value); 372 | extern int decode_timeticks(struct ber *e, int int_len, unsigned long long *value); 373 | extern unsigned char *decode_string_oid(unsigned char *s, int l, char *buf, int buf_size); 374 | extern int decode_composite(struct ber *e, unsigned char comp_type, int *composite_end_pos); 375 | #define decode_sequence(e,seq_end_pos) decode_composite(e,AT_SEQUENCE,seq_end_pos) 376 | #define inside_composite(e,s) ((e)->len < s) 377 | #define inside_sequence(e,s) ((e)->len < s) 378 | 379 | /* In decode_any() and decode_oid(), 380 | * the dst buffer will point inside the src buffer, 381 | * so if you are going to use it past the life of the src 382 | * buffer, do not forget to encode_dup(dst) afterwards. 383 | */ 384 | extern int decode_any(struct ber *src, struct ber *dst); 385 | extern int decode_oid(struct ber *src, struct ber *dst); 386 | 387 | extern int build_get_request_packet(int version, const char *community, 388 | const char *oid_list, 389 | unsigned request_id, struct ber *e); 390 | extern int start_snmp_packet(struct packet_builder *pb, int version, const char *community, 391 | unsigned request_id); 392 | extern int add_encoded_oid_to_snmp_packet(struct packet_builder *pb, struct ber *oid); 393 | extern int finalize_snmp_packet(struct packet_builder *pb, struct ber *encoded_packet, unsigned char type, int max_repetitions); 394 | extern int oid_belongs_to_table(struct ber *oid, struct ber *table); 395 | extern int oid_compare(struct ber *aa, struct ber *bb); 396 | /* returns -9999 if there is a problem, 397 | * returns < 0 if a precedes b, 398 | * returns > 0 if a follows b, 399 | * return 0 if a is b 400 | */ 401 | 402 | /* other locations */ 403 | const char *thisprogname(void); 404 | void croak(int exit_code, const char *fmt, ...); 405 | void croakx(int exit_code, const char *fmt, ...); 406 | 407 | /* event_loop.c */ 408 | struct socket_info *new_socket_info(int fd); 409 | void delete_socket_info(struct socket_info *si); 410 | void on_eof(struct socket_info *si, void (*eof_handler)(struct socket_info *si)); 411 | void on_read(struct socket_info *si, void (*read_handler)(struct socket_info *si)); 412 | void on_write(struct socket_info *si, void (*write_handler)(struct socket_info *si)); 413 | void event_loop(void); 414 | void tcp_send(struct socket_info *si, void *buf, int size); 415 | 416 | /* timers.c */ 417 | extern struct timer *new_timer(struct timeval *when); 418 | extern struct timer *find_timer(struct timeval *when); 419 | extern int cleanup_timer(struct timer *t); /* 0 - not cleaned, 1 - cleaned */ 420 | extern struct timer *next_timer(void); 421 | extern int ms_to_next_timer(void); 422 | extern void trigger_timers(void); 423 | extern void set_timeout(struct timeval *tv, int timeout); /* timeout in ms */ 424 | extern int64_t ms_passed_since(struct timeval *tv); 425 | 426 | /* client_listen.c */ 427 | extern void create_listening_socket(int port); 428 | 429 | /* snmp.c */ 430 | extern void create_snmp_socket(void); 431 | extern void snmp_send(struct destination *dest, struct ber *packet); 432 | 433 | /* client_input.c */ 434 | extern void new_client_connection(int fd); 435 | 436 | /* util.c */ 437 | extern char *object_strdup(msgpack_object *o); 438 | extern char *object2string(msgpack_object *o, char s[], int bufsize); 439 | extern int object_string_eq(msgpack_object *o, char *s); 440 | extern int object2ip(msgpack_object *o, struct in_addr *ip); /* 1 = success, 0 = failure */ 441 | extern unsigned next_sid(void); 442 | extern void dump_buf(FILE *f, void *buf, int len); 443 | extern char *oid2str(struct ber o); 444 | 445 | /* destination.c */ 446 | /* get_destination() cannot return NULL, it would rather die */ 447 | extern struct destination *get_destination(struct in_addr *ip, unsigned port); 448 | extern struct destination *find_destination(struct in_addr *ip, unsigned port); 449 | extern void maybe_query_destination(struct destination *dest); 450 | extern void destination_start_timing(struct destination *dest); 451 | extern void destination_stop_timing(struct destination *dest); 452 | extern void destination_timer(struct destination *dest); 453 | extern void dump_all_destinations(msgpack_packer *pk); 454 | extern void unclog_all_destinations(void); 455 | 456 | /* client_requests_info.c */ 457 | extern struct client_requests_info *get_client_requests_info(struct in_addr *ip, unsigned port, struct socket_info *si); 458 | extern int free_client_request_info(struct client_requests_info *cri); 459 | extern int free_all_client_request_info_for_fd(int fd); 460 | extern void dump_client_request_info(msgpack_packer *pk, struct client_requests_info *cri); 461 | 462 | /* cid_info.c */ 463 | extern struct cid_info *get_cid_info(struct client_requests_info *cri, unsigned cid); 464 | extern int free_cid_info(struct cid_info *ci); 465 | extern void cid_reply(struct cid_info *ci, int type); 466 | extern void dump_cid_info(msgpack_packer *pk, struct cid_info *ci); 467 | 468 | /* sid_info.c */ 469 | extern struct sid_info *new_sid_info(struct client_requests_info *cri); 470 | extern struct sid_info *find_sid_info(struct destination *dest, unsigned sid); 471 | extern void free_sid_info(struct sid_info *si); 472 | extern void build_snmp_query(struct client_requests_info *cri); 473 | extern void sid_start_timing(struct sid_info *si); 474 | extern void sid_stop_timing(struct sid_info *si); 475 | extern void check_timed_out_requests(void); 476 | extern int process_sid_info_response(struct sid_info *si, struct ber *e); 477 | extern void oid_done(struct sid_info *si, struct oid_info *oi, struct ber *val, int op, int packet_error_status); 478 | extern void all_oids_done(struct sid_info *si, struct ber *val); 479 | extern void got_table_oid(struct sid_info *si, struct oid_info *table_oi, struct ber *oid, struct ber *val, int packet_error_status); 480 | extern void sid_timer(struct sid_info *si); 481 | extern void dump_sid_info(msgpack_packer *pk, struct sid_info *si); 482 | 483 | /* oid_info.c */ 484 | extern int allocate_oid_info_list(struct oid_info_head *oi, msgpack_object *o, struct cid_info *ci); 485 | extern struct oid_info *allocate_oid_info(msgpack_object *o, struct cid_info *ci); 486 | extern int free_oid_info_list(struct oid_info_head *list); 487 | extern int free_oid_info(struct oid_info *oi); /* use only for oid_infos outside any lists */ 488 | extern void dump_oid_info(msgpack_packer *pk, struct oid_info *oi); 489 | 490 | /* request_common.c */ 491 | extern int error_reply(struct socket_info *si, unsigned code, unsigned cid, char *error); 492 | extern int msgpack_pack_string(msgpack_packer *pk, char *s); 493 | extern int msgpack_pack_named_int(msgpack_packer *pk, char *name, int64_t val); 494 | extern int msgpack_pack_named_string(msgpack_packer *pk, char *name, char *val); 495 | extern int msgpack_pack_options(msgpack_packer *pk, struct client_requests_info *cri); 496 | extern void msgpack_pack_oid(struct msgpack_packer *pk, struct ber oid); 497 | extern void msgpack_pack_ber(struct msgpack_packer *pk, struct ber value); 498 | 499 | /* request_setopt.c */ 500 | extern int handle_setopt_request(struct socket_info *si, unsigned cid, msgpack_object *o); 501 | 502 | /* request_getopt.c */ 503 | extern int handle_getopt_request(struct socket_info *si, unsigned cid, msgpack_object *o); 504 | 505 | /* request_info.c */ 506 | extern int handle_info_request(struct socket_info *si, unsigned cid, msgpack_object *o); 507 | extern int handle_dest_info_request(struct socket_info *si, unsigned cid, msgpack_object *o); 508 | 509 | /* request_get.c */ 510 | extern int handle_get_request(struct socket_info *si, unsigned cid, msgpack_object *o); 511 | 512 | /* request_gettable.c */ 513 | extern int handle_gettable_request(struct socket_info *si, unsigned cid, msgpack_object *o); 514 | 515 | #endif 516 | -------------------------------------------------------------------------------- /manual.mdwn: -------------------------------------------------------------------------------- 1 | % SNMP-QUERY-ENGINE(1) 2 | % Anton Berezin 3 | % May 2012 4 | 5 | # NAME 6 | 7 | snmp-query-engine - multiplexing SNMP query engine 8 | 9 | # VERSION 10 | 11 | This document describes snmp-query-engine version 2014052300. 12 | 13 | # SYNOPSIS 14 | 15 | snmp-query-engine 16 | 17 | # DESCRIPTION 18 | 19 | The `snmp-query-engine` daemon accepts multiple 20 | client connections and performs SNMP queries 21 | towards multiple destinations on behalf of its clients, 22 | taking care of multiplexing and throttling the requests. 23 | This allows querying large number of devices for 24 | large amounts of SNMP information quickly, 25 | while controlling the load on the devices 26 | induced by multiple SNMP queries. 27 | 28 | Clients communicate with the `snmp-query-engine` 29 | daemon via TCP. Both requests and responses 30 | are encoded using MessagePack format, 31 | http://msgpack.org/. 32 | 33 | # OPTIONS 34 | 35 | -h 36 | : produce usage text and quit 37 | 38 | -p **prt** 39 | : listen on port **prt** instead of default **7667** 40 | 41 | -q 42 | : quiet operation 43 | 44 | # REQUESTS 45 | 46 | All requests are arrays of at least two elements. 47 | 48 | parameter 0 49 | : The first element must be a request type 50 | represented by a small positive integer: 51 | 1 (**SETOPT**), 2 (**GETOPT**), 3 (**INFO**), 4 (**GET**), 5 (**GETTABLE**), 6 (**DEST INFO**). 52 | 53 | parameter 1 54 | : The second element must be a **request id**, 55 | represented by a positive integer. 56 | It is a good idea for every request within 57 | a single client session to have a unique 58 | request id, although request ids may 59 | be reused - provided that the previous 60 | request with the same id is no longer 61 | active (that is, the client already 62 | got a response). 63 | 64 | The rest of request parameters are 65 | request type-specific, and are described below. 66 | 67 | All responses will be an array 68 | of at least two elements. The **first 69 | element** will be the request type ORed 70 | with either 0x10 to indicate a successfull 71 | reply, or with 0x20 to indicate an error. 72 | The **second element** will be the **request id**. 73 | In case of an error, the third, and last, 74 | element will be a textual description 75 | of the error. In case of a successfull 76 | reply, the rest of response array 77 | elements are request type-specific, 78 | and are described below. 79 | 80 | ## SETOPT 81 | 82 | The **SETOPT** request allows the client 83 | to change several per-destination 84 | and per-destination/client options. 85 | 86 | Please note that setting values for options that are specific 87 | for a destination, and not for a destination/client 88 | session, will affect other clients 89 | making requests towards the same destination. 90 | 91 | SETOPT parameters: 92 | 93 | parameter 2 94 | : destination IP (v4 only) address in a dot-decimal notation 95 | 96 | parameter 3 97 | : destination port number 98 | 99 | parameter 4 100 | : a map of options 101 | 102 | The map of options may contain any 103 | number of supported options, including zero, 104 | in which case SETOPT is exactly equivalent 105 | to GETOPT. 106 | 107 | Supported options: 108 | 109 | **`version`** 110 | : SNMP version, 1 for SNMP 1, 2 for SNMP 2c; default is **2**. 111 | This is a per destination/client option which does 112 | not affect other clients. 113 | 114 | **`community`** 115 | : SNMP community; default is **"public"**. 116 | This is a per destination/client option which does 117 | not affect other clients. 118 | 119 | **`global_max_packets`** 120 | : Maximum global number of SNMP packets being "on the wire" 121 | at any given time. 122 | This is a **global** option which affects all destinations 123 | and all clients. 124 | See also `max_packets_on_the_wire` statistic for the **INFO** 125 | request. 126 | 127 | **`max_packets`** 128 | : Maximum number of SNMP packets to the destination being "on the wire" 129 | at any given time. The `snmp-query-engine` will avoid sending 130 | any new SNMP requests exceeding this number 131 | until getting a reply or a timeout. The default is **3**. 132 | This is a per destination option which affects other clients. 133 | 134 | **`max_req_size`** 135 | : Maximum SNMP request packet size in bytes, 136 | not counting IP and UDP headers. Any request 137 | which would exceed this size will be split 138 | into smaller pieces. Conversely, if there 139 | are pending several small requests from the same 140 | client towards the same destination, they may 141 | be combined into a single SNMP request. 142 | The default is **1400**. 143 | This is a per destination option which affects other clients. 144 | 145 | **`max_reply_size`** 146 | : Maximum SNMP reply packet size in bytes, 147 | not counting IP and UDP headers. 148 | When composing a request, the query engine 149 | estimates the expected size of the reply 150 | using `estimated_value_size` option. 151 | If the estimated reply size exceeds 152 | `max_reply_size`, the request will be 153 | split into smaller pieces. 154 | This option is only relevant for `get* requests. 155 | The default is **1472**. 156 | This is a per destination option which affects other clients. 157 | 158 | **`estimated_value_size`** 159 | : The expected size of a returned value in bytes. 160 | The default is **9**, which corresponds to the 161 | largest possible 64-bit counter. 162 | This is a per destination option which affects other clients. 163 | 164 | **`max_oids_per_request`** 165 | : Never ask for more than this number of oids 166 | in a single SNMP get query. 167 | The default is **64**. 168 | This is a per destination option which affects other clients. 169 | 170 | **`timeout`** 171 | : Time, in milliseconds, after which the SNMP 172 | request will be re-sent (up to a configurable 173 | number of retries). The default is **2000** (which is probably too little). 174 | This is a per destination/client option which does 175 | not affect other clients. 176 | 177 | **`retries`** 178 | : Number of times a request will be re-sent (including the first 179 | send), after which a timeout will be returned to the client. 180 | The default is **3**. 181 | This is a per destination/client option which does 182 | not affect other clients. 183 | 184 | **`min_interval`** 185 | : Time, in milliseconds, that must pass between 186 | subsequent SNMP queries towards the destination. 187 | The default is **10**. 188 | This is a per destination option which affects other clients. 189 | 190 | **`max_repetitions`** 191 | : How many OIDs shall the destination return 192 | in a single reply when requesting a table 193 | using SNMP 2c. This can be overridden 194 | by individual GETTABLE requests. The default is **10**. 195 | This is a per destination option which affects other clients. 196 | 197 | **`ignore_threshold`** 198 | : All queries of the destination will immediately 199 | return with an "ignored" error for `ignore_duration` 200 | milliseconds if number of "hard timeouts" will 201 | exceed `ignore_threshold` in a row (without any 202 | successfull replies between timed out queries). 203 | If `ignore_threshold` is 0, no queries will be 204 | ignored. The default is **0**. 205 | This is a per destination option which affects other clients. 206 | 207 | **`ignore_duration`** 208 | : See `ignore_threshold` above. 209 | The default is **300000** milliseconds (5 minutes). 210 | This is a per destination option which affects other clients. 211 | 212 | **SETOPT** reply will consist of a single map 213 | with all current option values 214 | for a given destination and client. 215 | 216 | ## GETOPT 217 | 218 | The **GETOPT** request allows the client 219 | to query per-destination and per-destination/client 220 | options. 221 | 222 | GETOPT parameters: 223 | 224 | parameter 2 225 | : destination IP (v4 only) address in a dot-decimal notation 226 | 227 | parameter 3 228 | : destination port number 229 | 230 | **GETOPT** reply will consist of a single map 231 | with all current options for a given destination 232 | (see **SETOPT** parameters for options description). 233 | 234 | ## INFO 235 | 236 | The **INFO** request returns global and connection statistics. 237 | It has no extra parameters. 238 | 239 | **INFO** reply will consist of a map with two keys, **connection** 240 | and **global**. The values associated with those keys are 241 | themselves maps with, respectively, connection and global 242 | stats. 243 | 244 | The following statistics are available *for connection*: 245 | 246 | **`active_cid_infos`** 247 | : number of GET and GETTABLE requests in progress for this connection 248 | 249 | **`active_cr_infos`** 250 | : number of destinations queried during this connection 251 | 252 | **`active_sid_infos`** 253 | : number of active SNMP requests for this connection 254 | 255 | **`client_requests`** 256 | : number of all requests made during this connection 257 | 258 | **`dest_info_requests`** 259 | : number of DEST INFO requests made during this connection 260 | 261 | **`get_requests`** 262 | : number of GET requests made during this connection 263 | 264 | **`getopt_requests`** 265 | : number of GETOPT requests made during this connection 266 | 267 | **`gettable_requests`** 268 | : number of GETTABLE requests made during this connection 269 | 270 | **`good_snmp_responses`** 271 | : number of good SNMP responses received during this connection 272 | 273 | **`info_requests`** 274 | : number of INFO requests made during this connection 275 | 276 | **`invalid_requests`** 277 | : number of invalid requests made during this connection 278 | 279 | **`oids_non_increasing`** 280 | : number of table terminations due to non-increasing oids 281 | 282 | **`oids_requested`** 283 | : number of oids requested as part of GET and GETTABLE processing 284 | during this connection 285 | 286 | **`oids_returned_from_snmp`** 287 | : number of oids got with SNMP responses 288 | during this connection 289 | 290 | **`oids_returned_to_client`** 291 | : number of oids returned back to client 292 | during this connection 293 | 294 | **`setopt_requests`** 295 | : number of SETOPT requests made during this connection 296 | 297 | **`snmp_retries`** 298 | : number of times an SNMP query was retried due to UDP timeout 299 | during this connection 300 | 301 | **`snmp_sends`** 302 | : number of SNMP packets sent 303 | during this connection 304 | 305 | **`snmp_timeouts`** 306 | : number of times timeout was returned back to the client 307 | during this connection; this represents "hard timeouts", 308 | that is not getting any response after configured number 309 | of retries 310 | 311 | **`snmp_v1_sends`** 312 | : number of SNMP version 1 packets sent 313 | during this connection 314 | 315 | **`snmp_v2c_sends`** 316 | : number of SNMP version 2c packets sent 317 | during this connection 318 | 319 | **`total_cid_infos`** 320 | : number of GET and GETTABLE requests made during this connection 321 | 322 | **`total_cr_infos`** 323 | : number of destinations queried during this connection; 324 | this will always be the same as `active_cr_infos` due 325 | to the way the daemon is implemented 326 | 327 | **`total_sid_infos`** 328 | : number of SNMP requests performed 329 | during this connection 330 | 331 | **`udp_timeouts`** 332 | : number of "soft" timeouts 333 | during this connection 334 | 335 | **`uptime`** 336 | : the duration of the connection in milliseconds 337 | 338 | The following *global* statistics are available: 339 | 340 | **`active_cid_infos`** 341 | : number of GET and GETTABLE requests in progress 342 | 343 | **`active_client_connections`** 344 | : number of active client connections 345 | 346 | **`active_cr_infos`** 347 | : sum of a number of destinations queried by each active client connection 348 | 349 | **`active_oid_infos`** 350 | : number of oids being requested plus number of oids 351 | pending return to a client 352 | 353 | **`active_sid_infos`** 354 | : number of active SNMP requests 355 | 356 | **`active_timers_sec`** 357 | : number of active timer slots with a second resolution 358 | 359 | **`active_timers_usec`** 360 | : number of active timers 361 | 362 | **`bad_snmp_responses`** 363 | : number of bad SNMP responses (the responses 364 | which were not valid SNMP or for which 365 | a corresponding request could not be found) 366 | 367 | **`client_requests`** 368 | : total number of all client requests 369 | 370 | **`dest_info_requests`** 371 | : total number of DEST INFO requests 372 | 373 | **`destination_throttles`** 374 | : number of times an SNMP query was postponed 375 | due to `min_interval` and `max_packets` settings 376 | 377 | **`destination_ignores`** 378 | : number of times a destination was put into "ignore" 379 | mode via `ignore_threshold` and `ignore_duration` mechanism 380 | 381 | **`get_requests`** 382 | : total number of GET requests 383 | 384 | **`getopt_requests`** 385 | : total number of GETOPT requests 386 | 387 | **`gettable_requests`** 388 | : total number of GETTABLE requests 389 | 390 | **`global_throttles`** 391 | : number of times a request was postponed because 392 | `packets_on_the_wire` reached `max_packets_on_the_wire` 393 | 394 | **`good_snmp_responses`** 395 | : total number of good SNMP responses received 396 | 397 | **`info_requests`** 398 | : total number of INFO requests 399 | 400 | **`invalid_requests`** 401 | : total number of invalid client requests 402 | 403 | **`max_packets_on_the_wire`** 404 | : configured global maximum of SNMP requests "in progress"; 405 | when `packets_on_the_wire` reaches this number, no new 406 | SNMP requests will be sent until `packets_on_the_wire` 407 | falls below `max_packets_on_the_wire` again; 408 | see `global_max_packets` `SETOPT` parameter 409 | 410 | **`oids_non_increasing`** 411 | : number of table terminations due to non-increasing oids 412 | 413 | **`oids_requested`** 414 | : total number of oids requested as part of GET and GETTABLE processing 415 | 416 | **`oids_returned_from_snmp`** 417 | : total number of oids got with SNMP responses 418 | 419 | **`oids_returned_to_client`** 420 | : total number of oids returned back to clients 421 | 422 | **`oids_ignored`** 423 | : total number of oids ignored via `ignore_threshold` and `ignore_duration` 424 | mechanism 425 | 426 | **`octets_received`** 427 | : total number of payload octets received via SNMP 428 | 429 | **`octets_sent`** 430 | : total number of payload octets sent to SNMP destinations 431 | 432 | **`packets_on_the_wire`** 433 | : number of SNMP requests "in progress" 434 | 435 | **`setopt_requests`** 436 | : total number of SETOPT requests 437 | 438 | **`snmp_retries`** 439 | : total number of times an SNMP query was retried due to UDP timeout 440 | 441 | **`snmp_sends`** 442 | : total number of SNMP packets sent 443 | 444 | **`snmp_timeouts`** 445 | : total number of times a timeout was returned back to a client; 446 | this represents "hard timeouts", 447 | that is not getting any response after configured number 448 | of retries 449 | 450 | **`snmp_v1_sends`** 451 | : total number of SNMP version 1 packets sent 452 | 453 | **`snmp_v2c_sends`** 454 | : total number of SNMP version 2c packets sent 455 | 456 | **`total_cid_infos`** 457 | : total number of GET and GETTABLE requests made 458 | 459 | **`total_client_connections`** 460 | : total number of client connections 461 | 462 | **`total_cr_infos`** 463 | : total sum of a number of destinations queried by each client connection 464 | 465 | **`total_oid_infos`** 466 | : total number of oids requested and returned to a client 467 | 468 | **`total_sid_infos`** 469 | : total number of SNMP requests performed 470 | 471 | **`total_timers_sec`** 472 | : total number of timer slots with a second resolution 473 | 474 | **`total_timers_usec`** 475 | : total number of timers 476 | 477 | **`udp_receive_buffer_size`** 478 | : the size of UDP receive buffer for SNMP socket 479 | 480 | **`udp_send_buffer_size`** 481 | : the size of UDP send buffer for SNMP socket 482 | 483 | **`udp_send_buffer_overflow`** 484 | : number of times a UDP packet could not be sent due to a send buffer overflow 485 | 486 | **`udp_timeouts`** 487 | : total number of "soft" timeouts 488 | 489 | **`uptime`** 490 | : daemon uptime in milliseconds 491 | 492 | **`program_version`** 493 | : version of the engine program 494 | 495 | 496 | ## GET 497 | 498 | Clients should use the **GET** request 499 | to obtain one or more oids from a destination. 500 | A single **GET** request will correspond 501 | to one or more SNMP gets, depending on the 502 | values of *`max_req_size`*, *`max_reply_size`*, 503 | *`estimated_value_size`*, and *`max_oids_per_request`* 504 | options. 505 | 506 | GET parameters: 507 | 508 | parameter 2 509 | : destination IP (v4 only) address in a dot-decimal notation 510 | 511 | parameter 3 512 | : destination port number 513 | 514 | parameter 4 515 | : an array of oids to get 516 | 517 | **GET** reply consists of an array, each element 518 | of which corresponds to a single requested oid. 519 | Each such element is itself a two-element array. 520 | The first element will be oid itself. 521 | The second element will either be a value, 522 | or an array with a single element. If it is 523 | an array, its only element will be an error 524 | description. Possible errors are: 525 | 526 | no-such-object 527 | : SNMP reply returned "no such object" for this oid 528 | 529 | no-such-instance 530 | : SNMP reply returned "no such instance" for this oid 531 | 532 | end-of-mib 533 | : SNMP reply returned "end-of-mib" for this oid 534 | 535 | timeout 536 | : there was a timeout; for **GET** requests 537 | with a small number of oids, in case of timeout 538 | all oids will generally return this error; 539 | for larger **GET** requests, it is perfectly 540 | possible to get this error for only some 541 | of the oids 542 | 543 | ignored 544 | : the oid was requested during time when its 545 | destination was in the "ignore" state 546 | (see `ignore_threshold` and `ignore_duration` 547 | options for details) 548 | 549 | missing 550 | : the oid not found in the reply 551 | 552 | decode-error 553 | : there was an error decoding the value 554 | 555 | unsupported type 0xHH 556 | : the `snmp-query-engine` does not support 557 | values of this type (yet) 558 | 559 | Example request: 560 | 561 | [GET, $id, "127.0.0.1", 161, 562 | [ "1.3.6.1.2.1.1.5.0", 563 | "1.3.6.1.2.1.25.1.1.0", 564 | "1.3.66" ] 565 | ] 566 | 567 | Example reply: 568 | 569 | [GET|0x10, $id, [ 570 | ["1.3.6.1.2.1.1.5.0", "my.host.name"], 571 | ["1.3.6.1.2.1.25.1.1.0", 215485727], 572 | ["1.3.66", ["no-such-object"]], 573 | ], 574 | ] 575 | 576 | ## GETTABLE 577 | 578 | Clients should use the **GETTABLE** request 579 | to obtain a table of oids from a destination. 580 | A single **GETTABLE** request will correspond 581 | to one or more SNMP get-next (for SNMP version 1) 582 | or get-bulk (for SNMP version 2) queries. 583 | 584 | GETTABLE parameters: 585 | 586 | parameter 2 587 | : destination IP (v4 only) address in a dot-decimal notation 588 | 589 | parameter 3 590 | : destination port number 591 | 592 | parameter 4 593 | : an oid of a table to get 594 | 595 | **GETTABLE** reply consists of an array, each element 596 | of which corresponds to a single oid from the requested table. 597 | Each such element is itself a two-element array. 598 | The first element will be oid itself. 599 | The second element will either be a value, 600 | or an array with a single element. If it is 601 | an array, its only element will be an error 602 | description. Possible errors are the same as in **GET** 603 | request, plus the following error specific to **GETTABLE**: 604 | 605 | non-increasing 606 | : this oid is less than or equal than the 607 | previous oid in the table; there will be 608 | no further attempts to continue iterating 609 | the table 610 | 611 | The requested table oid is never present in the reply itself 612 | if there were no errors. 613 | 614 | Example request 1: 615 | 616 | [GETTABLE, $id, "127.0.0.1", 161, "1.3.6.1.2.1.1.5"] 617 | 618 | Example reply 1: 619 | 620 | [GETTABLE|0x10, $id, [["1.3.6.1.2.1.1.5.0", "my.host.name"]]] 621 | 622 | Example request 2: 623 | 624 | [GETTABLE, $id, "127.0.0.1", 161, "1.3.6.1.2.1.1.5.0"] 625 | 626 | Example reply 2 ("empty table"): 627 | 628 | [GETTABLE|0x10, $id, []] 629 | 630 | Example request 3: 631 | 632 | [GETTABLE, $id, "1.1.1.1", 161, "1.3.6.1.2.1.1.5"] 633 | 634 | Example reply 3 ("table oid itself in error reply"): 635 | 636 | [GETTABLE|0x10, 41, [["1.3.6.1.2.1.1.5", ["timeout"]]]] 637 | 638 | Example request 4: 639 | 640 | [GETTABLE, $id, "ip-of-some-misbehaving-host", 161, "1.3.6.1.2.1.1.5"] 641 | 642 | Example reply 4: 643 | 644 | [GETTABLE|0x10, $id, [ 645 | ["1.3.6.1.2.1.1.5.0", "my.host.name"], 646 | ["1.3.6.1.2.1.1.5.0", ["non-increasing"]], 647 | ]] 648 | 649 | ## DEST INFO 650 | 651 | The **DEST INFO** request allows the client 652 | to request per-destination statistics. 653 | 654 | DEST INFO parameters: 655 | 656 | parameter 2 657 | : destination IP (v4 only) address in a dot-decimal notation 658 | 659 | parameter 3 660 | : destination port number 661 | 662 | **DEST INFO** reply will consist of a map per-destination 663 | statistics. 664 | 665 | The following statistics are available: 666 | 667 | **`octets_received`** 668 | : number of payload octets received from the destination 669 | 670 | **`octets_sent`** 671 | : number of payload octets sent to the destination 672 | 673 | # SEE ALSO 674 | 675 | There is a Perl module which serves as 676 | a client to `snmp-query-engine`, 677 | **Net::SNMP::QueryEngine::AnyEvent**. 678 | It can be found on CPAN 679 | and on github. 680 | 681 | # ACKNOWLEDGEMENTS 682 | 683 | This work is in part sponsored by Telia Denmark. 684 | 685 | Thanks to Henrik Andersen and Lars Thegler for discussions 686 | and insights. 687 | 688 | -------------------------------------------------------------------------------- /t/queries.t: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | 6 | use Data::MessagePack; 7 | use IO::Socket::INET; 8 | use Data::Dump qw(dd pp); 9 | use Time::HiRes; 10 | use FindBin; 11 | use Socket ':all'; 12 | use Test::More; 13 | use Sys::Hostname; 14 | 15 | use constant RT_SETOPT => 1; 16 | use constant RT_GETOPT => 2; 17 | use constant RT_INFO => 3; 18 | use constant RT_GET => 4; 19 | use constant RT_GETTABLE => 5; 20 | use constant RT_DEST_INFO => 6; 21 | use constant RT_REPLY => 0x10; 22 | use constant RT_ERROR => 0x20; 23 | 24 | sub THERE () { return bless \my $dummy, 't::Present' } 25 | our $NUMBER = qr/^\d+$/; 26 | our $NON_ZERO = qr/^[1-9]\d*$/; 27 | 28 | my @GLOBAL_STATS = qw( 29 | active_cid_infos 30 | active_client_connections 31 | active_cr_infos 32 | active_oid_infos 33 | active_sid_infos 34 | active_timers_sec 35 | active_timers_usec 36 | bad_snmp_responses 37 | client_requests 38 | destination_ignores 39 | destination_throttles 40 | get_requests 41 | getopt_requests 42 | gettable_requests 43 | global_throttles 44 | good_snmp_responses 45 | info_requests 46 | invalid_requests 47 | max_packets_on_the_wire 48 | oids_ignored 49 | oids_non_increasing 50 | oids_requested 51 | oids_returned_from_snmp 52 | oids_returned_to_client 53 | octets_received 54 | octets_sent 55 | packets_on_the_wire 56 | setopt_requests 57 | snmp_retries 58 | snmp_sends 59 | snmp_timeouts 60 | snmp_v1_sends 61 | snmp_v2c_sends 62 | total_cid_infos 63 | total_client_connections 64 | total_cr_infos 65 | total_oid_infos 66 | total_sid_infos 67 | total_timers_sec 68 | total_timers_usec 69 | udp_receive_buffer_size 70 | udp_send_buffer_size 71 | udp_send_buffer_overflow 72 | udp_timeouts 73 | uptime 74 | program_version 75 | ); 76 | 77 | my @CLIENT_STATS = qw( 78 | active_cid_infos 79 | active_cr_infos 80 | active_sid_infos 81 | client_requests 82 | get_requests 83 | getopt_requests 84 | gettable_requests 85 | good_snmp_responses 86 | info_requests 87 | invalid_requests 88 | oids_non_increasing 89 | oids_requested 90 | oids_returned_from_snmp 91 | oids_returned_to_client 92 | setopt_requests 93 | snmp_retries 94 | snmp_sends 95 | snmp_timeouts 96 | snmp_v1_sends 97 | snmp_v2c_sends 98 | total_cid_infos 99 | total_cr_infos 100 | total_sid_infos 101 | udp_timeouts 102 | uptime 103 | ); 104 | my %CLIENT_STATS = map { $_ => $NUMBER } @CLIENT_STATS; 105 | my %GLOBAL_STATS = map { $_ => $NUMBER } @GLOBAL_STATS; 106 | $CLIENT_STATS{oids_non_increasing} = 0; 107 | $GLOBAL_STATS{oids_non_increasing} = 0; 108 | 109 | my $daemon_pid; 110 | if (!($daemon_pid = fork)) { 111 | exec("$FindBin::Bin/../snmp-query-engine", "-p7668", "-q"); 112 | exit; # unreach 113 | } 114 | 115 | Time::HiRes::sleep(0.2); 116 | our $mp = Data::MessagePack->new()->prefer_integer; 117 | our $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:7668", Proto => "tcp") 118 | or die "cannot connect to snmp-query-engine daemon: $!\n"; 119 | 120 | $mp->utf8(1); 121 | request_match("defaults via getopt", [RT_GETOPT,2000,"127.0.0.1",161], [RT_GETOPT|RT_REPLY,2000, 122 | {ip=>"127.0.0.1", port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 2000, retries => 3, min_interval => 10, max_repetitions => 10, ignore_threshold => 0, ignore_duration => 300000, max_reply_size => 1472, estimated_value_size => 9, max_oids_per_request => 64 }]); 123 | $mp->utf8(0); 124 | request_match("defaults via setopt", [RT_SETOPT,2001,"127.0.0.1",161, {}], [RT_SETOPT|RT_REPLY,2001, 125 | {ip=>"127.0.0.1", port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 2000, retries => 3, min_interval => 10, max_repetitions => 10, ignore_threshold => 0, ignore_duration => 300000, max_reply_size => 1472, estimated_value_size => 9, max_oids_per_request => 64 }]); 126 | request_match("setopt bad length", [RT_SETOPT,2002,"127.0.0.1",161], [RT_SETOPT|RT_ERROR,2002,qr/bad request length/]); 127 | request_match("setopt bad port 1", [RT_SETOPT,2003,"127.0.0.1","x",{}], [RT_SETOPT|RT_ERROR,2003,qr/bad port number/]); 128 | request_match("setopt bad port 2", [RT_SETOPT,2004,"127.0.0.1",80000,{}], [RT_SETOPT|RT_ERROR,2004,qr/bad port number/]); 129 | request_match("setopt bad IP", [RT_SETOPT,2005,"127.260.0.1",161,{}], [RT_SETOPT|RT_ERROR,2005,qr/bad IP/]); 130 | request_match("setopt opt not map 1", [RT_SETOPT,2006,"127.0.0.1",161,[]], [RT_SETOPT|RT_ERROR,2006,qr/not a map/]); 131 | request_match("setopt opt not map 2", [RT_SETOPT,2007,"127.0.0.1",161,42], [RT_SETOPT|RT_ERROR,2007,qr/not a map/]); 132 | request_match("setopt bad option key", [RT_SETOPT,2008,"127.0.0.1",161,{meow=>1}], [RT_SETOPT|RT_ERROR,2008,qr/bad option key/]); 133 | request_match("setopt bad version 1", [RT_SETOPT,2009,"127.0.0.1",161,{version=>42}], [RT_SETOPT|RT_ERROR,2009,qr/invalid SNMP version/]); 134 | request_match("setopt bad version 2", [RT_SETOPT,2010,"127.0.0.1",161,{version=>"x"}], [RT_SETOPT|RT_ERROR,2010,qr/invalid SNMP version/]); 135 | request_match("setopt bad community", [RT_SETOPT,2011,"127.0.0.1",161,{community=>[]}], [RT_SETOPT|RT_ERROR,2011,qr/invalid SNMP community/]); 136 | request_match("setopt bad max_packets 1", [RT_SETOPT,2012,"127.0.0.1",161,{max_packets=>"meow"}], [RT_SETOPT|RT_ERROR,2012,qr/invalid max packets/]); 137 | request_match("setopt bad max_packets 2", [RT_SETOPT,2013,"127.0.0.1",161,{max_packets=>0}], [RT_SETOPT|RT_ERROR,2013,qr/invalid max packets/]); 138 | request_match("setopt bad max_packets 3", [RT_SETOPT,2014,"127.0.0.1",161,{max_packets=>30000}], [RT_SETOPT|RT_ERROR,2014,qr/invalid max packets/]); 139 | request_match("setopt bad global_max_packets 1", [RT_SETOPT,42012,"127.0.0.1",161,{global_max_packets=>"meow"}], [RT_SETOPT|RT_ERROR,42012,qr/invalid global max packets/]); 140 | request_match("setopt bad global_max_packets 2", [RT_SETOPT,42013,"127.0.0.1",161,{global_max_packets=>0}], [RT_SETOPT|RT_ERROR,42013,qr/invalid global max packets/]); 141 | request_match("setopt bad global_max_packets 3", [RT_SETOPT,42014,"127.0.0.1",161,{global_max_packets=>3000000}], [RT_SETOPT|RT_ERROR,42014,qr/invalid global max packets/]); 142 | request_match("setopt bad max req size 1", [RT_SETOPT,2015,"127.0.0.1",161,{max_req_size=>"foo"}], [RT_SETOPT|RT_ERROR,2015,qr/invalid max request size/]); 143 | request_match("setopt bad max req size 2", [RT_SETOPT,2016,"127.0.0.1",161,{max_req_size=>480}], [RT_SETOPT|RT_ERROR,2016,qr/invalid max request size/]); 144 | request_match("setopt bad max req size 3", [RT_SETOPT,2017,"127.0.0.1",161,{max_req_size=>52000}], [RT_SETOPT|RT_ERROR,2017,qr/invalid max request size/]); 145 | request_match("setopt bad timeout 1", [RT_SETOPT,2018,"127.0.0.1",161,{timeout=>"st"}], [RT_SETOPT|RT_ERROR,2018,qr/invalid timeout/]); 146 | request_match("setopt bad timeout 2", [RT_SETOPT,2019,"127.0.0.1",161,{timeout=>31000}], [RT_SETOPT|RT_ERROR,2019,qr/invalid timeout/]); 147 | request_match("setopt bad retries 1", [RT_SETOPT,2020,"127.0.0.1",161,{retries=>"foo"}], [RT_SETOPT|RT_ERROR,2020,qr/invalid retries/]); 148 | request_match("setopt bad retries 2", [RT_SETOPT,2021,"127.0.0.1",161,{retries=>0}], [RT_SETOPT|RT_ERROR,2021,qr/invalid retries/]); 149 | request_match("setopt bad retries 3", [RT_SETOPT,2022,"127.0.0.1",161,{retries=>12}], [RT_SETOPT|RT_ERROR,2022,qr/invalid retries/]); 150 | request_match("setopt bad min interval 1", [RT_SETOPT,2120,"127.0.0.1",161,{min_interval=>"foo"}], [RT_SETOPT|RT_ERROR,2120,qr/invalid min interval/]); 151 | request_match("setopt bad min interval 2", [RT_SETOPT,2122,"127.0.0.1",161,{min_interval=>10002}], [RT_SETOPT|RT_ERROR,2122,qr/invalid min interval/]); 152 | request_match("setopt bad max repetitions 1", [RT_SETOPT,2220,"127.0.0.1",161,{max_repetitions=>"foo"}], [RT_SETOPT|RT_ERROR,2220,qr/invalid max repetitions/]); 153 | request_match("setopt bad max repetitions 2", [RT_SETOPT,2221,"127.0.0.1",161,{max_repetitions=>0}], [RT_SETOPT|RT_ERROR,2221,qr/invalid max repetitions/]); 154 | request_match("setopt bad max repetitions 3", [RT_SETOPT,2222,"127.0.0.1",161,{max_repetitions=>256}], [RT_SETOPT|RT_ERROR,2222,qr/invalid max repetitions/]); 155 | request_match("defaults unchanged", [RT_SETOPT,2023,"127.0.0.1",161, {}], [RT_SETOPT|RT_REPLY,2023, 156 | {ip=>"127.0.0.1", port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 2000, retries => 3, min_interval => 10, max_repetitions => 10, }]); 157 | request_match("change timeout", [RT_SETOPT,2024,"127.0.0.1",161, {timeout=>1500}], [RT_SETOPT|RT_REPLY,2024, 158 | {ip=>"127.0.0.1", port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 1500, retries => 3, min_interval => 10, max_repetitions => 10, }]); 159 | request_match("correct timeout via getopt", [RT_GETOPT,2025,"127.0.0.1",161], [RT_GETOPT|RT_REPLY,2025, 160 | {ip=>"127.0.0.1", port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 1500, retries => 3, min_interval => 10, max_repetitions => 10, }]); 161 | 162 | request_match("bad request: not an array 1", {x=>1}, [RT_ERROR,0,qr/not an array/]); 163 | request_match("bad request: not an array 2", 55, [RT_ERROR,0,qr/not an array/]); 164 | request_match("bad request: not an array 3", "hello", [RT_ERROR,0,qr/not an array/]); 165 | request_match("bad request: empty array", [], [RT_ERROR,0,qr/empty array/]); 166 | request_match("bad request: no id", [RT_GET], [RT_ERROR,0,qr/without an id/]); 167 | request_match("bad request: bad id 1", [RT_GET,-1], [RT_ERROR,0,qr/id is not a positive integer/]); 168 | request_match("bad request: bad id 2", [RT_GET,"heps"], [RT_ERROR,0,qr/id is not a positive integer/]); 169 | request_match("bad request: bad type 1", [-1,12], [RT_ERROR,12,qr/type is not a positive integer/]); 170 | request_match("bad request: bad type 2", ["heps",13], [RT_ERROR,13,qr/type is not a positive integer/]); 171 | request_match("bad request: unknown type", [9,14], [RT_ERROR|9,14,qr/unknown request type/i]); 172 | request_match("bad request length 1", [RT_GET,15,"127.0.0.1",161, 2, "public"], [RT_GET|RT_ERROR,15,qr/bad request length/i]); 173 | request_match("bad request length 2", [RT_GET,16,"127.0.0.1",161, 2, "public", ["1.3.6.1.2.1.1.5.0"], "heh", "heh"], 174 | [RT_GET|RT_ERROR,16,qr/bad request length/i]); 175 | request_match("bad port number #1", [RT_GET,17,"127.0.0.1",-2, ["1.3.6.1.2.1.1.5.0"]], [RT_GET|RT_ERROR,17,qr/bad port number/i]); 176 | request_match("bad port number #2", [RT_GET,18,"127.0.0.1",[], ["1.3.6.1.2.1.1.5.0"]], [RT_GET|RT_ERROR,18,qr/bad port number/i]); 177 | request_match("bad port number #3", [RT_GET,19,"127.0.0.1",66666, ["1.3.6.1.2.1.1.5.0"]], [RT_GET|RT_ERROR,19,qr/bad port number/i]); 178 | request_match("bad IP 1", [RT_GET,21,666,161, ["1.3.6.1.2.1.1.5.0"]], [RT_GET|RT_ERROR,21,qr/bad IP/i]); 179 | request_match("bad IP 2", [RT_GET,22,[],161, ["1.3.6.1.2.1.1.5.0"]], [RT_GET|RT_ERROR,22,qr/bad IP/i]); 180 | request_match("bad IP 3", [RT_GET,23,"257.12.22.13",161, ["1.3.6.1.2.1.1.5.0"]], [RT_GET|RT_ERROR,23,qr/bad IP/i]); 181 | request_match("oids is not an array 1", [RT_GET,24,"127.0.0.1",161, 42], [RT_GET|RT_ERROR,24,qr/oids must be an array/i]); 182 | request_match("oids is not an array 2", [RT_GET,25,"127.0.0.1",161, {}], [RT_GET|RT_ERROR,25,qr/oids must be an array/i]); 183 | request_match("oids is not an array 3", [RT_GET,26,"127.0.0.1",161, "oids"], [RT_GET|RT_ERROR,26,qr/oids must be an array/i]); 184 | request_match("oids is an empty array", [RT_GET,27,"127.0.0.1",161, []], [RT_GET|RT_ERROR,27,qr/oids is an empty array/i]); 185 | 186 | request_match("destinfo length 1", [RT_DEST_INFO,6600], [RT_DEST_INFO|RT_ERROR, 6600, qr/bad request length/i]); 187 | request_match("destinfo length 2", [RT_DEST_INFO,6600,"127.0.0.1"], [RT_DEST_INFO|RT_ERROR, 6600, qr/bad request length/i]); 188 | request_match("destinfo port 1", [RT_DEST_INFO,6601,"127.0.0.1",-2], [RT_DEST_INFO|RT_ERROR, 6601, qr/bad port number/i]); 189 | request_match("destinfo port 2", [RT_DEST_INFO,6602,"127.0.0.1",[]], [RT_DEST_INFO|RT_ERROR, 6602, qr/bad port number/i]); 190 | request_match("destinfo port 3", [RT_DEST_INFO,6603,"127.0.0.1",66666], [RT_DEST_INFO|RT_ERROR, 6603, qr/bad port number/i]); 191 | request_match("destinfo ip 1", [RT_DEST_INFO,6611,666,161], [RT_DEST_INFO|RT_ERROR, 6611, qr/bad IP/i]); 192 | request_match("destinfo ip 2", [RT_DEST_INFO,6612,[],161], [RT_DEST_INFO|RT_ERROR, 6612, qr/bad IP/i]); 193 | request_match("destinfo ip 3", [RT_DEST_INFO,6613,"257.12.22.13",161], [RT_DEST_INFO|RT_ERROR, 6613, qr/bad IP/i]); 194 | 195 | request_match("destinfo zero", [RT_DEST_INFO,6620,"127.0.0.1",161], [RT_DEST_INFO|RT_REPLY, 6620, 196 | { octets_received => 0, octets_sent => 0}]); 197 | 198 | my $target = "127.0.0.1"; 199 | my $hostname = hostname; 200 | my $uptime = qr/^\d+$/; 201 | my $r; 202 | 203 | $r = request([RT_GET,33,$target,161, ["1.3.6.1.2.1.1.5.0"]]); 204 | if ($r->[0] != (RT_GET|RT_REPLY) || ref $r->[2][0][1]) { 205 | print STDERR "\n\n=====\n=====\n"; 206 | print STDERR "=====> Skipping remaining tests, need running local snmpd on port 161\n"; 207 | print STDERR "=====> with \"public\" community.\n"; 208 | print STDERR "=====\n=====\n\n"; 209 | goto bailout; 210 | } 211 | 212 | $r = request_match("change community to a bad one", [RT_SETOPT,3000,$target,161, {community=>1234, ignore_threshold => 1, timeout => 1500, retries => 2, ignore_duration => 2000}], [RT_SETOPT|RT_REPLY,3000, 213 | {ip=>$target, port=>161, community=>1234, version=>2, max_packets => 3, max_req_size => 1400, timeout => 1500, retries => 2, min_interval => 10, max_repetitions => 10, ignore_threshold => 1, ignore_duration => 2000 }]); 214 | 215 | $r = request([RT_INFO,2252]); 216 | is($r->[2]{global}{destination_ignores}, 0, "ignored destinations 0"); 217 | is($r->[2]{global}{oids_ignored}, 0, "ignored oids 0"); 218 | is($r->[2]{global}{max_packets_on_the_wire}, 1_000_000, "default global max packets"); 219 | 220 | $r = request([RT_SETOPT,42016,"127.0.0.1",161,{global_max_packets=>100_000}]); 221 | 222 | request_match("times out", [RT_GET,41,$target,161, ["1.3.6.1.2.1.1.5.0"]], 223 | [RT_GET|RT_REPLY,41,[["1.3.6.1.2.1.1.5.0",["timeout"]]]]); 224 | for my $id (2241..2250) { 225 | $mp->utf8(!$mp->get_utf8); 226 | request_match("ignored $id", [RT_GET,$id,$target,161, ["1.3.6.1.2.1.1.5.0"]], 227 | [RT_GET|RT_REPLY,$id,[["1.3.6.1.2.1.1.5.0",["ignored"]]]]); 228 | } 229 | $mp->utf8(0); 230 | $r = request([RT_INFO,2251]); 231 | is($r->[2]{global}{destination_ignores}, 1, "ignored destinations"); 232 | is($r->[2]{global}{oids_ignored}, 10, "ignored oids"); 233 | is($r->[2]{global}{max_packets_on_the_wire}, 100_000, "global max packets changed ok"); 234 | 235 | request_match("change community to a good one", [RT_SETOPT,2253,$target,161, {community=>"public"}], [RT_SETOPT|RT_REPLY,2253, 236 | {ip=>$target, port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 1500, retries => 2, min_interval => 10, max_repetitions => 10, ignore_threshold => 1, ignore_duration => 2000}]); 237 | 238 | sleep 2; 239 | 240 | request_match("past ignore interval", [RT_GET,2254,$target,161, ["1.3.6.1.2.1.1.5.0", ".1.3.6.1.2.1.25.1.1.0", "1.3.66"]], 241 | [RT_GET|RT_REPLY,2254,[ 242 | ["1.3.6.1.2.1.1.5.0",$hostname], 243 | ["1.3.6.1.2.1.25.1.1.0",$uptime], 244 | ["1.3.66",["no-such-object"]]]]); 245 | 246 | $r = request([RT_INFO,2255]); 247 | is($r->[2]{global}{destination_ignores}, 1, "ignored destinations did not change"); 248 | is($r->[2]{global}{oids_ignored}, 10, "ignored oids did not change"); 249 | 250 | request_match("switch off ignoring", [RT_SETOPT,3001,$target,161, {ignore_threshold => 0}], [RT_SETOPT|RT_REPLY,3001, 251 | {ip=>$target, port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 1500, retries => 2, min_interval => 10, max_repetitions => 10, ignore_threshold => 0, ignore_duration => 2000}]); 252 | 253 | request_match("all is good", [RT_GET,42,$target,161, ["1.3.6.1.2.1.1.5.0", ".1.3.6.1.2.1.25.1.1.0", "1.3.66"]], 254 | [RT_GET|RT_REPLY,42,[ 255 | ["1.3.6.1.2.1.1.5.0",$hostname], 256 | ["1.3.6.1.2.1.25.1.1.0",$uptime], 257 | ["1.3.66",["no-such-object"]]]]); 258 | 259 | request_match("3rd time lucky", [RT_GET,110,$target,161, ["1.3.6.1.2.1.1.5.0", "1.3.6.1.2.1.1.5.0", "1.3.6.1.2.1.1.5.0"]], 260 | [RT_GET|RT_REPLY,110,[ 261 | ["1.3.6.1.2.1.1.5.0",$hostname], 262 | ["1.3.6.1.2.1.1.5.0",$hostname], 263 | ["1.3.6.1.2.1.1.5.0",$hostname], 264 | ]]); 265 | 266 | request_match("change version to SNMP v1", [RT_SETOPT,3002,$target,161, {version=>1}], [RT_SETOPT|RT_REPLY,3002, 267 | {ip=>$target, port=>161, community=>"public", version=>1, max_packets => 3, max_req_size => 1400, timeout => 1500, retries => 2, min_interval => 10, max_repetitions => 10, }]); 268 | 269 | $r = request_match("try request SNMP v1", [RT_GET,43,$target,161, ["1.3.6.1.2.1.1.5.0", ".1.3.6.1.2.1.25.1.1.0", "1.3.66"]], 270 | [RT_GET|RT_REPLY,43,[ 271 | ["1.3.6.1.2.1.1.5.0",["noSuchName"]], 272 | ["1.3.6.1.2.1.25.1.1.0",["noSuchName"]], 273 | ["1.3.66",["noSuchName"]]]]); 274 | 275 | $r = request_match("ifDescr SNMPv1 table", [RT_GETTABLE,555,$target,161,"1.3.6.1.2.1.2.2.1.2"], [RT_GETTABLE|RT_REPLY,555,THERE]); 276 | 277 | request_match("change version back to SNMP v2", [RT_SETOPT,3003,$target,161, {version=>2}], [RT_SETOPT|RT_REPLY,3003, 278 | {ip=>$target, port=>161, community=>"public", version=>2, max_packets => 3, max_req_size => 1400, timeout => 1500, retries => 2, min_interval => 10, max_repetitions => 10, }]); 279 | 280 | $r = request_match("ifDescr SNMPv2c table", [RT_GETTABLE,3200,$target,161,"1.3.6.1.2.1.2.2.1.2"], [RT_GETTABLE|RT_REPLY,3200,THERE]); 281 | my $first_ifindex = $r->[2][0][0]; $first_ifindex =~ s/.*\.(\d+)$/$1/; 282 | 283 | my $rr = request_match("ifDescr table small reps", [RT_GETTABLE,3201,$target,161,"1.3.6.1.2.1.2.2.1.2",4], [RT_GETTABLE|RT_REPLY,3201,THERE]); 284 | match("small reps same", $r->[2], $rr->[2]); 285 | $rr = request_match("ifDescr table large reps", [RT_GETTABLE,3202,$target,161,"1.3.6.1.2.1.2.2.1.2",20], [RT_GETTABLE|RT_REPLY,3202,THERE]); 286 | match("large reps same", $r->[2], $rr->[2]); 287 | 288 | lone_request([RT_GET,3500,$target,161, ["1.3.6.1.2.1.1.5.0"]]); 289 | lone_request([RT_GET,3501,$target,161, [".1.3.6.1.2.1.25.1.1.0"]]); 290 | Time::HiRes::sleep(0.5); 291 | my ($r1,$r2) = bulk_response(); 292 | if ($r1->[1] == 3501) { 293 | ($r1, $r2) = ($r2, $r1); 294 | } 295 | match("combined req1", $r1, [RT_GET|RT_REPLY,3500,[["1.3.6.1.2.1.1.5.0",$hostname]]]); 296 | match("combined req2", $r2, [RT_GET|RT_REPLY,3501,[["1.3.6.1.2.1.25.1.1.0",$uptime]]]); 297 | 298 | $r = request([RT_INFO,3555]); 299 | my $snmp_sends = $r->[2]{global}{snmp_sends}; 300 | multi_request( 301 | [RT_GET,3502,$target,161, ["1.3.6.1.2.1.1.5.0"]], 302 | [RT_GET,3503,$target,161, [".1.3.6.1.2.1.25.1.1.0"]], 303 | [RT_GET,3504,$target,161, ["1.3.6.1.2.1.2.1.0"]], 304 | [RT_GET,3505,$target,161, ["1.3.6.1.2.1.2.2.1.1.$first_ifindex"]], 305 | ); 306 | Time::HiRes::sleep(0.5); 307 | my @r = sort { $a->[1] <=> $b->[1] } bulk_response(); 308 | match("multi combined req1", $r[0], [RT_GET|RT_REPLY,3502,[["1.3.6.1.2.1.1.5.0",$hostname]]]); 309 | match("multi combined req2", $r[1], [RT_GET|RT_REPLY,3503,[["1.3.6.1.2.1.25.1.1.0",$uptime]]]); 310 | match("multi combined req3", $r[2], [RT_GET|RT_REPLY,3504,[["1.3.6.1.2.1.2.1.0",$NUMBER]]]); 311 | match("multi combined req4", $r[3], [RT_GET|RT_REPLY,3505,[["1.3.6.1.2.1.2.2.1.1.$first_ifindex",$first_ifindex]]]); 312 | $r = request([RT_INFO,3556]); 313 | #print STDERR ">>>> SENDS 4 clients, ", $r->[2]{global}{snmp_sends}-$snmp_sends, " SNMP, $r->[2]{global}{udp_timeouts}($r->[2]{global}{snmp_timeouts}) timeouts in total\n"; 314 | # TODO is($r->[2]{global}{snmp_sends}-$snmp_sends, 2, "4 client requests in 2 SNMP requests"); 315 | 316 | $r = request_match("stats", [RT_INFO,5000], [RT_INFO|RT_REPLY,5000, 317 | { connection => \%CLIENT_STATS, 318 | global => \%GLOBAL_STATS}]); 319 | #print STDERR "OIDS requested: $r->[2]{connection}{oids_requested}\n"; 320 | 321 | request_match("destinfo non-zero", [RT_DEST_INFO,6630,"127.0.0.1",161], [RT_DEST_INFO|RT_REPLY, 6630, 322 | { octets_received => $NON_ZERO, octets_sent => $NON_ZERO}]); 323 | 324 | bailout: 325 | Time::HiRes::sleep(0.2); 326 | close $conn; 327 | Time::HiRes::sleep(0.2); 328 | END { kill 15, $daemon_pid if $daemon_pid }; 329 | 330 | done_testing; 331 | 332 | sub request_match 333 | { 334 | my ($t, $req, $mat) = @_; 335 | my $res = request($req); 336 | match($t, $res, $mat); 337 | return $res; 338 | } 339 | 340 | sub request 341 | { 342 | my $d = shift; 343 | my $p = $mp->pack($d); 344 | $conn->syswrite($p); 345 | my $reply = ""; 346 | $conn->sysread($reply, 65536); 347 | $mp->unpack($reply); 348 | } 349 | 350 | sub lone_request 351 | { 352 | my $d = shift; 353 | my $p = $mp->pack($d); 354 | $conn->syswrite($p); 355 | } 356 | 357 | sub multi_request 358 | { 359 | my @d = @_; 360 | my $p = ""; 361 | for my $d (@d) { 362 | $p .= $mp->pack($d); 363 | } 364 | $conn->syswrite($p); 365 | } 366 | 367 | sub bulk_response 368 | { 369 | my $reply; 370 | $conn->sysread($reply, 65536); 371 | my $up = Data::MessagePack::Unpacker->new; 372 | my $offset = 0; 373 | my @r; 374 | while( $offset < length($reply) ) { 375 | $offset = $up->execute($reply, $offset); 376 | push @r, $up->data; 377 | $up->reset; 378 | $reply = substr($reply, $offset); 379 | $offset = 0; 380 | } 381 | return @r; 382 | } 383 | 384 | sub match 385 | { 386 | my ($t, $result, $template) = @_; 387 | if (!ref $result && !ref $template) { 388 | is($result, $template, "$t: matches"); 389 | return; 390 | } 391 | if (ref $template && ref $template eq "Regexp") { 392 | like($result, $template, "$t: matches"); 393 | return; 394 | } 395 | if (ref $template && ref $template eq "Test::Deep::Regexp") { 396 | like($result, $template->{val}, "$t: matches"); 397 | return; 398 | } 399 | if (ref $template && ref $template eq "t::Present") { 400 | # ok if we got that far 401 | return; 402 | } 403 | unless (ref $result && ref $template) { 404 | fail("$t: apples to oranges"); 405 | return; 406 | } 407 | my $tt = $t; 408 | $tt .= ": " unless $tt =~ /[\]}]$/; 409 | if (is(ref($result), ref($template), "$t: same reftype")) { 410 | if (UNIVERSAL::isa($result, "HASH")) { 411 | for my $k (keys %$template) { 412 | if (ok(exists $result->{$k}, "$t: \"$k\" exists")) { 413 | match("$tt\{$k}", $result->{$k}, $template->{$k}); 414 | } 415 | } 416 | } elsif (UNIVERSAL::isa($result, "ARRAY")) { 417 | if (ok(@$result == @$template, "$t: array size matches")) { 418 | for my $i (0..$#$result) { 419 | match("$tt\[$i]", $result->[$i], $template->[$i]); 420 | } 421 | } 422 | } 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /ber.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of `snmp-query-engine`. 3 | * 4 | * Copyright 2012-2013, Anton Berezin 5 | * Modified BSD license. 6 | * (See LICENSE file in the distribution.) 7 | * 8 | */ 9 | #include "sqe.h" 10 | 11 | #define SPACECHECK(bytes) if (e->len + (bytes) > e->max_len) { errno = EMSGSIZE; return -1; } 12 | #define SPACECHECK2 SPACECHECK(2) 13 | #define EXTEND(bytes) { int sz = (bytes); e->len += sz; e->b += sz; } 14 | #define EXTEND2 EXTEND(2) 15 | #define OENDCHECK if (o >= oend) { errno = EINVAL; return -1; } 16 | 17 | static unsigned char BUF_NULL[] = "\x05"; 18 | struct ber BER_NULL = { BUF_NULL, BUF_NULL+2, 2, 2 }; 19 | static unsigned char BUF_TIMEOUT[] = "\x8a"; 20 | struct ber BER_TIMEOUT = { BUF_TIMEOUT, BUF_TIMEOUT+2, 2, 2 }; 21 | static unsigned char BUF_MISSING[] = "\x8b"; 22 | struct ber BER_MISSING = { BUF_MISSING, BUF_MISSING+2, 2, 2 }; 23 | static unsigned char BUF_IGNORED[] = "\x8e"; 24 | struct ber BER_IGNORED = { BUF_IGNORED, BUF_IGNORED+2, 2, 2 }; 25 | static unsigned char BUF_NON_INCREASING[] = "\x8f"; 26 | struct ber BER_NON_INCREASING = { BUF_NON_INCREASING, BUF_NON_INCREASING+2, 2, 2 }; 27 | 28 | int 29 | ber_is_null(struct ber *ber) 30 | { 31 | return ber->len >= 2 && ber->buf[0] == 5 && ber->buf[1] == 0; 32 | } 33 | 34 | struct ber ber_init(void *buf, int size) 35 | { 36 | struct ber e; 37 | e.buf = e.b = buf; 38 | e.len = 0; 39 | e.max_len = size; 40 | return e; 41 | } 42 | 43 | struct ber ber_dup(struct ber *eo) 44 | { 45 | char *buf = malloc(eo->len); 46 | struct ber en; 47 | 48 | if (!buf) 49 | croak(2, "encode_dup: malloc(buf(%d))", eo->len); 50 | en = ber_init(buf, eo->len); 51 | memcpy(buf, eo->buf, eo->len); 52 | en.len = eo->len; 53 | en.b += eo->len; 54 | return en; 55 | } 56 | 57 | struct ber ber_rewind(struct ber o) 58 | { 59 | o.b = o.buf; 60 | o.len = 0; 61 | return o; 62 | } 63 | 64 | int 65 | ber_equal(struct ber *b1, struct ber *b2) 66 | { 67 | if (b1->len != b2->len) 68 | return 0; 69 | return memcmp(b1->buf, b2->buf, b1->len) == 0; 70 | } 71 | 72 | struct ber 73 | ber_error_status(int error_status) 74 | { 75 | char error_string[40]; 76 | char buf[64]; 77 | struct ber b; 78 | 79 | switch (error_status) { 80 | case 0: 81 | strcpy(error_string, "noError"); 82 | break; 83 | case 1: 84 | strcpy(error_string, "tooBig"); 85 | break; 86 | case 2: 87 | strcpy(error_string, "noSuchName"); 88 | break; 89 | case 3: 90 | strcpy(error_string, "badValue"); 91 | break; 92 | case 4: 93 | strcpy(error_string, "readOnly"); 94 | break; 95 | case 5: 96 | strcpy(error_string, "genErr"); 97 | break; 98 | case 6: 99 | strcpy(error_string, "noAccess"); 100 | break; 101 | case 7: 102 | strcpy(error_string, "wrongType"); 103 | break; 104 | case 8: 105 | strcpy(error_string, "wrongLength"); 106 | break; 107 | case 9: 108 | strcpy(error_string, "wrongEncoding"); 109 | break; 110 | case 10: 111 | strcpy(error_string, "wrongValue"); 112 | break; 113 | case 11: 114 | strcpy(error_string, "noCreation"); 115 | break; 116 | case 12: 117 | strcpy(error_string, "inconsistentValue"); 118 | break; 119 | case 13: 120 | strcpy(error_string, "resourceUnavailable"); 121 | break; 122 | case 14: 123 | strcpy(error_string, "commitFailed"); 124 | break; 125 | case 15: 126 | strcpy(error_string, "undoFailed"); 127 | break; 128 | case 16: 129 | strcpy(error_string, "authorizationError"); 130 | break; 131 | case 17: 132 | strcpy(error_string, "notWritable"); 133 | break; 134 | case 18: 135 | strcpy(error_string, "inconsistentName"); 136 | break; 137 | default: 138 | sprintf(error_string, "error-status %d", error_status); 139 | } 140 | b = ber_init(buf, 64); 141 | encode_string(error_string, &b); 142 | b.buf[0] = VAL_STRING_ERROR; 143 | return ber_rewind(ber_dup(&b)); 144 | } 145 | 146 | int 147 | build_get_request_packet(int version, const char *community, 148 | const char *oid_list, 149 | unsigned request_id, struct ber *e) 150 | { 151 | unsigned char *packet_sequence; 152 | unsigned char *pdu; 153 | unsigned char *oid_sequence; 154 | 155 | SPACECHECK2; 156 | packet_sequence = e->b; 157 | packet_sequence[0] = AT_SEQUENCE; 158 | EXTEND2; 159 | 160 | if (version < 0 || version > 1) { 161 | errno = EINVAL; 162 | return -1; 163 | } 164 | if (encode_integer((unsigned)version, e, 0) < 0) return -1; 165 | if (encode_string(community, e) < 0) return -1; 166 | 167 | SPACECHECK2; 168 | pdu = e->b; 169 | pdu[0] = PDU_GET_REQUEST; 170 | EXTEND2; 171 | 172 | if (encode_integer(request_id, e, 4) < 0) return -1; 173 | if (encode_integer(0, e, 0) < 0) return -1; /* error-status */ 174 | if (encode_integer(0, e, 0) < 0) return -1; /* error-index */ 175 | 176 | SPACECHECK2; 177 | oid_sequence = e->b; 178 | oid_sequence[0] = AT_SEQUENCE; 179 | EXTEND2; 180 | 181 | while (*oid_list) { 182 | unsigned char *seq; 183 | int l; 184 | 185 | SPACECHECK2; 186 | seq = e->b; 187 | seq[0] = AT_SEQUENCE; 188 | EXTEND2; 189 | l = encode_string_oid(oid_list, -1, e); 190 | if (l < 0) return -1; 191 | SPACECHECK2; 192 | e->b[0] = AT_NULL; 193 | e->b[1] = 0; 194 | EXTEND2; 195 | if (encode_store_length(e, seq) < 0) return -1; 196 | oid_list += l+1; 197 | } 198 | 199 | if (encode_store_length(e, oid_sequence) < 0) return -1; 200 | if (encode_store_length(e, pdu) < 0) return -1; 201 | if (encode_store_length(e, packet_sequence) < 0) return -1; 202 | return 0; 203 | } 204 | 205 | int 206 | start_snmp_packet(struct packet_builder *pb, int version, const char *community, 207 | unsigned request_id) 208 | { 209 | unsigned char *packet_buf; 210 | struct ber *e; 211 | 212 | bzero(pb, sizeof(*pb)); 213 | packet_buf = malloc(65000); 214 | if (!packet_buf) 215 | croak(2, "start_get_request_packet: malloc(packet_buf)"); 216 | pb->e = ber_init(packet_buf, 65000); 217 | e = &pb->e; 218 | 219 | SPACECHECK2; 220 | pb->packet_sequence = e->b; 221 | pb->packet_sequence[0] = AT_SEQUENCE; 222 | EXTEND2; 223 | 224 | if (version < 0 || version > 1) { 225 | errno = EINVAL; 226 | return -1; 227 | } 228 | if (encode_integer((unsigned)version, e, 0) < 0) return -1; 229 | if (encode_string(community, e) < 0) return -1; 230 | 231 | SPACECHECK2; 232 | pb->pdu = e->b; 233 | pb->pdu[0] = PDU_GET_REQUEST; 234 | EXTEND2; 235 | 236 | if (encode_integer(request_id, e, 4) < 0) return -1; 237 | pb->sid_offset = e->b - e->buf - 4; 238 | if (encode_integer(0, e, 0) < 0) return -1; /* error-status */ 239 | if (encode_integer(0, e, 0) < 0) return -1; /* error-index */ 240 | pb->max_repetitions = e->b - 1; 241 | 242 | SPACECHECK2; 243 | pb->oid_sequence = e->b; 244 | pb->oid_sequence[0] = AT_SEQUENCE; 245 | EXTEND2; 246 | 247 | return 0; 248 | } 249 | 250 | int 251 | add_encoded_oid_to_snmp_packet(struct packet_builder *pb, struct ber *oid) 252 | { 253 | struct ber *e; 254 | unsigned char *seq; 255 | 256 | e = &pb->e; 257 | 258 | SPACECHECK2; 259 | seq = e->b; 260 | seq[0] = AT_SEQUENCE; 261 | EXTEND2; 262 | SPACECHECK(oid->len); 263 | memcpy(e->b, oid->buf, oid->len); 264 | EXTEND(oid->len); 265 | SPACECHECK2; 266 | e->b[0] = AT_NULL; 267 | e->b[1] = 0; 268 | EXTEND2; 269 | if (encode_store_length(e, seq) < 0) return -1; 270 | return 0; 271 | } 272 | 273 | int 274 | finalize_snmp_packet(struct packet_builder *pb, struct ber *encoded_packet, unsigned char type, int max_repetitions) 275 | { 276 | struct ber *e; 277 | int l; 278 | e = &pb->e; 279 | 280 | if (type == PDU_GET_BULK_REQUEST) { 281 | if (max_repetitions <= 0) 282 | max_repetitions = 10; 283 | if (max_repetitions > 255) 284 | max_repetitions = 255; 285 | pb->max_repetitions[0] = (unsigned char)max_repetitions; 286 | } 287 | if ( (l = encode_store_length(e, pb->oid_sequence)) < 0) return -1; 288 | if ( (l = encode_store_length(e, pb->pdu)) < 0) return -1; 289 | pb->sid_offset += l; 290 | pb->pdu[0] = type; 291 | if ( (l = encode_store_length(e, pb->packet_sequence)) < 0) return -1; 292 | pb->sid_offset += l; 293 | *encoded_packet = ber_dup(e); 294 | free(e->buf); 295 | return pb->sid_offset; 296 | } 297 | 298 | int 299 | encode_store_length(struct ber *e, unsigned char *s) 300 | { 301 | int n = e->b - s - 2; 302 | 303 | /* assert(s >= e->buf); */ 304 | /* assert(s + 2 <= e->b); */ 305 | if (n <= 127) { 306 | s[1] = n & 0x7f; 307 | return 0; 308 | } else if ( n <= 255) { 309 | SPACECHECK(1); 310 | memmove(s+3, s+2, n); 311 | s[1] = 0x81; 312 | s[2] = n & 0xff; 313 | EXTEND(1); 314 | return 1; 315 | } else if ( n <= 65535) { 316 | SPACECHECK(2); 317 | memmove(s+4, s+2, n); 318 | s[1] = 0x82; 319 | s[2] = (n >> 8) & 0xff; 320 | s[3] = n & 0xff; 321 | EXTEND(2); 322 | return 2; 323 | } else { 324 | /* XXX larger sizes are possible */ 325 | errno = EMSGSIZE; 326 | return -1; 327 | } 328 | return 0; 329 | } 330 | 331 | int 332 | encode_string(const char *s, struct ber *e) 333 | { 334 | int i = strlen(s); 335 | if (encode_type_len(AT_STRING, i, e) < 0) return -1; 336 | if (e->len + i > e->max_len) { 337 | errno = EMSGSIZE; 338 | return -1; 339 | } 340 | memmove(e->b, s, i); 341 | e->b += i; 342 | e->len += i; 343 | return 0; 344 | } 345 | 346 | int 347 | encode_integer(unsigned i, struct ber *e, int force_size) 348 | { 349 | int l; 350 | if (i <= 255) 351 | l = 1; 352 | else if (i <= 65535) 353 | l = 2; 354 | else if (i <= 16777215) 355 | l = 3; 356 | else if (i <= 4294967295u) 357 | l = 4; 358 | else { 359 | errno = ERANGE; 360 | return -1; 361 | } 362 | if (force_size) 363 | l = force_size; 364 | if (encode_type_len(AT_INTEGER, l, e) < 0) return -1; 365 | SPACECHECK(l); 366 | switch (l) { 367 | case 4: 368 | e->b[l-4] = (i >> 24) & 0xff; 369 | case 3: 370 | e->b[l-3] = (i >> 16) & 0xff; 371 | case 2: 372 | e->b[l-2] = (i >> 8) & 0xff; 373 | case 1: 374 | e->b[l-1] = i & 0xff; 375 | } 376 | EXTEND(l); 377 | return 0; 378 | } 379 | 380 | int 381 | decode_composite(struct ber *e, unsigned char comp_type, int *composite_end_pos) 382 | { 383 | unsigned char t; 384 | unsigned len; 385 | 386 | if (decode_type_len(e, &t, &len) < 0) return -1; 387 | if (t != comp_type) { 388 | errno = EINVAL; 389 | return -1; 390 | } 391 | if (len + (unsigned)e->len > INT_MAX) { 392 | errno = ERANGE; 393 | return -1; 394 | } 395 | /* SPACECHECK(len); -- done already by decode_type_len() */ 396 | if (composite_end_pos) 397 | *composite_end_pos = ((int)len) + e->len; 398 | return 0; 399 | } 400 | 401 | int 402 | decode_ipv4_address(struct ber *e, int l, struct in_addr *ip) 403 | { 404 | unsigned char t; 405 | unsigned len; 406 | unsigned int_ip; 407 | if (l < 0) { 408 | if (decode_type_len(e, &t, &len) < 0) return -1; 409 | if (t != AT_IP_ADDRESS) { 410 | errno = EINVAL; 411 | return -1; 412 | } 413 | if (len > INT_MAX) { 414 | errno = ERANGE; 415 | return -1; 416 | } 417 | l = (int)len; 418 | } 419 | if (decode_integer(e, l, &int_ip) < 0) 420 | return -1; 421 | ip->s_addr = htonl(int_ip); 422 | return 0; 423 | } 424 | 425 | int 426 | decode_integer(struct ber *e, int l, unsigned *value) 427 | { 428 | unsigned char t; 429 | unsigned len; 430 | if (l < 0) { 431 | if (decode_type_len(e, &t, &len) < 0) return -1; 432 | if (t != AT_INTEGER) { 433 | errno = EINVAL; 434 | return -1; 435 | } 436 | if (len > INT_MAX) { 437 | errno = ERANGE; 438 | return -1; 439 | } 440 | l = (int)len; 441 | } 442 | SPACECHECK(l); 443 | if (!value) { 444 | EXTEND(l); 445 | return 0; 446 | } 447 | *value = 0; 448 | while (l) { 449 | *value = *value << 8 | e->b[0]; 450 | EXTEND(1); 451 | l--; 452 | } 453 | return 0; 454 | } 455 | 456 | int 457 | decode_timeticks(struct ber *e, int l, unsigned long long *value) 458 | { 459 | unsigned char t; 460 | unsigned len; 461 | if (l < 0) { 462 | if (decode_type_len(e, &t, &len) < 0) return -1; 463 | if (t != AT_TIMETICKS) { 464 | errno = EINVAL; 465 | return -1; 466 | } 467 | if (len > INT_MAX) { 468 | errno = ERANGE; 469 | return -1; 470 | } 471 | l = (int)len; 472 | } 473 | return decode_counter64(e, l, value); 474 | } 475 | 476 | int 477 | decode_counter64(struct ber *e, int l, unsigned long long *value) 478 | { 479 | unsigned char t; 480 | unsigned len; 481 | if (l < 0) { 482 | if (decode_type_len(e, &t, &len) < 0) return -1; 483 | if (t != AT_COUNTER64) { 484 | errno = EINVAL; 485 | return -1; 486 | } 487 | if (len > INT_MAX) { 488 | errno = ERANGE; 489 | return -1; 490 | } 491 | l = (int)len; 492 | } 493 | SPACECHECK(l); 494 | if (!value) { 495 | EXTEND(l); 496 | return 0; 497 | } 498 | *value = 0; 499 | while (l) { 500 | *value = *value << 8 | e->b[0]; 501 | EXTEND(1); 502 | l--; 503 | } 504 | return 0; 505 | } 506 | 507 | int 508 | decode_oid(struct ber *e, struct ber *dst) 509 | { 510 | unsigned char t; 511 | unsigned len; 512 | 513 | if (dst) 514 | dst->buf = e->b; 515 | if (decode_type_len(e, &t, &len) < 0) return -1; 516 | if (t != AT_OID) { 517 | errno = EINVAL; 518 | return -1; 519 | } 520 | EXTEND(len); 521 | if (dst) { 522 | dst->max_len = dst->len = e->b - dst->buf; 523 | dst->b = e->b; 524 | } 525 | return 0; 526 | } 527 | 528 | int 529 | decode_any(struct ber *e, struct ber *dst) 530 | { 531 | unsigned char t; 532 | unsigned len; 533 | 534 | if (dst) 535 | dst->buf = e->b; 536 | if (decode_type_len(e, &t, &len) < 0) return -1; 537 | EXTEND(len); 538 | if (dst) { 539 | dst->max_len = dst->len = e->b - dst->buf; 540 | dst->b = e->b; 541 | } 542 | return 0; 543 | } 544 | 545 | int 546 | decode_type_len(struct ber *e, unsigned char *type, unsigned *len) 547 | { 548 | unsigned l; 549 | 550 | SPACECHECK(2); 551 | *type = e->b[0]; 552 | EXTEND(1); 553 | l = e->b[0]; 554 | EXTEND(1); 555 | if (l <= 127) { 556 | /* do nothing */ 557 | } else if (l == 0x81) { 558 | SPACECHECK(1); 559 | l = e->b[0]; 560 | EXTEND(1); 561 | } else if (l == 0x82) { 562 | SPACECHECK(2); 563 | l = (e->b[0] << 8) | e->b[1]; 564 | EXTEND(2); 565 | } else if (l == 0x83) { 566 | SPACECHECK(3); 567 | l = ((e->b[0] << 8) | e->b[1]) << 8 | e->b[2]; 568 | EXTEND(3); 569 | } else if (l == 0x84) { 570 | SPACECHECK(4); 571 | l = (((e->b[0] << 8) | e->b[1]) << 8 | e->b[2]) << 8 | e->b[3]; 572 | EXTEND(4); 573 | } else { 574 | errno = ERANGE; 575 | return -1; 576 | } 577 | SPACECHECK(l); 578 | *len = l; 579 | return 0; 580 | } 581 | 582 | int 583 | encode_type_len(unsigned char type, unsigned i, struct ber *e) 584 | { 585 | int l; 586 | if (i <= 127) { 587 | l = 2; 588 | SPACECHECK(l); 589 | e->b[1] = i & 0x7f; 590 | } else if (i <= 255) { 591 | l = 3; 592 | SPACECHECK(l); 593 | e->b[1] = 0x81; 594 | e->b[2] = i & 0xff; 595 | } else if (i <= 65535) { 596 | l = 4; 597 | SPACECHECK(l); 598 | e->b[1] = 0x82; 599 | e->b[2] = (i >> 8) & 0xff; 600 | e->b[3] = i & 0xff; 601 | } else if (i <= 16777215) { 602 | l = 5; 603 | SPACECHECK(l); 604 | e->b[1] = 0x83; 605 | e->b[2] = (i >> 16) & 0xff; 606 | e->b[3] = (i >> 8) & 0xff; 607 | e->b[4] = i & 0xff; 608 | } else if (i <= 4294967295u) { 609 | l = 6; 610 | SPACECHECK(l); 611 | e->b[1] = 0x84; 612 | e->b[2] = (i >> 24) & 0xff; 613 | e->b[3] = (i >> 16) & 0xff; 614 | e->b[4] = (i >> 8) & 0xff; 615 | e->b[5] = i & 0xff; 616 | } else { 617 | errno = ERANGE; 618 | return -1; 619 | } 620 | e->b[0] = type; 621 | EXTEND(l); 622 | return 0; 623 | } 624 | 625 | unsigned char * 626 | decode_string_oid(unsigned char *s, int l, char *buf, int buf_size) 627 | { 628 | int n, n_bytes, printed; 629 | unsigned x, x2 = 0; 630 | int first = 1; 631 | 632 | if (l < 1 || *s != AT_OID) { 633 | errno = EINVAL; 634 | return NULL; 635 | } 636 | s++; l--; 637 | if (l < 1) { 638 | errno = EINVAL; 639 | return NULL; 640 | } 641 | n = *s; 642 | s++; l--; 643 | if (n <= 127) { 644 | /* ok */; 645 | } else if (n == 0x81 && l >= 1) { 646 | n = *s; 647 | s++; l--; 648 | } else if (n == 0x82 && l >= 2) { 649 | n = *s * 256; 650 | s++; l--; 651 | n += *s; 652 | s++; l--; 653 | } else { 654 | errno = EINVAL; 655 | return NULL; 656 | } 657 | if (n > l) { 658 | errno = EINVAL; 659 | return NULL; 660 | } 661 | 662 | l = n; /* so that garbage at the end is ok */ 663 | while (l > 0) { 664 | x = 0; n_bytes = 0; 665 | while (*s >= 0x80 && l > 0) { 666 | x <<= 7; 667 | x |= *s & 0x7f; 668 | s++; l--; 669 | n_bytes++; 670 | } 671 | if (l <= 0) { 672 | errno = EINVAL; 673 | return NULL; 674 | } 675 | x <<= 7; 676 | x |= *s & 0x7f; 677 | s++; l--; 678 | if (n_bytes > 4) { 679 | errno = EINVAL; 680 | return NULL; 681 | } 682 | 683 | if (first) { 684 | x2 = x % 40; 685 | x /= 40; 686 | goto print_number; 687 | second_number: 688 | x = x2; 689 | first = 0; 690 | } 691 | print_number: 692 | if (!first && buf_size >= 1) { 693 | *buf++ = '.'; 694 | buf_size--; 695 | } 696 | // XXX we probably want to replace snprintf() with something faster 697 | if ( (printed = snprintf(buf, buf_size, "%u", x)) >= buf_size) { 698 | errno = EMSGSIZE; 699 | return NULL; 700 | } 701 | buf += printed; 702 | buf_size -= printed; 703 | if (first) goto second_number; 704 | } 705 | if (buf_size >= 1) { 706 | *buf = '\0'; 707 | return s; 708 | } 709 | 710 | errno = EMSGSIZE; 711 | return NULL; 712 | } 713 | 714 | int 715 | encode_string_oid(const char *oid, int oid_len, struct ber *e) 716 | { 717 | const char *o; 718 | int l = 0; 719 | unsigned char *s = e->b; 720 | unsigned n; 721 | unsigned n2 = 0; 722 | const char *oend; 723 | 724 | if (oid_len < 0) 725 | oid_len = strlen(oid); 726 | oend = oid + oid_len; 727 | 728 | if (e->len + 2 > e->max_len) { 729 | errno = EMSGSIZE; 730 | return -1; 731 | } 732 | *s++ = AT_OID; 733 | s++; 734 | l += 2; 735 | 736 | o = oid; 737 | OENDCHECK; 738 | if (*o == '.') o++; 739 | 740 | n = 0; 741 | OENDCHECK; 742 | while (isdigit(*o)) { 743 | n = 10*n + *o++ - '0'; 744 | OENDCHECK; 745 | } 746 | if (*o++ != '.') { 747 | errno = EINVAL; 748 | return -1; 749 | } 750 | OENDCHECK; 751 | while (isdigit(*o) && o < oend) { 752 | n2 = 10*n2 + *o++ - '0'; 753 | } 754 | if (n2 >= 40) { 755 | errno = EINVAL; 756 | return -1; 757 | } 758 | n = 40*n + n2; 759 | 760 | while (1) { 761 | if (n > MAX_OID) { 762 | errno = EINVAL; 763 | return -1; 764 | } 765 | if (n <= 127) { 766 | if (e->len + l + 1 > e->max_len) { 767 | errno = EMSGSIZE; 768 | return -1; 769 | } 770 | *s++ = n; 771 | l++; 772 | } else if (n <= 16383) { 773 | if (e->len + l + 2 > e->max_len) { 774 | errno = EMSGSIZE; 775 | return -1; 776 | } 777 | *s++ = 0x80 | (n >> 7); 778 | *s++ = n & 0x7f; 779 | l += 2; 780 | } else if (n <= 2097151) { 781 | if (e->len + l + 3 > e->max_len) { 782 | errno = EMSGSIZE; 783 | return -1; 784 | } 785 | *s++ = 0x80 | (n >> 14); 786 | *s++ = 0x80 | ((n >> 7) & 0x7f); 787 | *s++ = n & 0x7f; 788 | l += 3; 789 | } else if (n <= 268435456) { 790 | if (e->len + l + 4 > e->max_len) { 791 | errno = EMSGSIZE; 792 | return -1; 793 | } 794 | *s++ = 0x80 | (n >> 21); 795 | *s++ = 0x80 | ((n >> 14) & 0x7f); 796 | *s++ = 0x80 | ((n >> 7) & 0x7f); 797 | *s++ = n & 0x7f; 798 | l += 4; 799 | } else { 800 | if (e->len + l + 5 > e->max_len) { 801 | errno = EMSGSIZE; 802 | return -1; 803 | } 804 | *s++ = 0x80 | (n >> 28); 805 | *s++ = 0x80 | ((n >> 21) & 0x7f); 806 | *s++ = 0x80 | ((n >> 14) & 0x7f); 807 | *s++ = 0x80 | ((n >> 7) & 0x7f); 808 | *s++ = n & 0x7f; 809 | l += 5; 810 | } 811 | if (o == oend) break; 812 | if (*o++ != '.') { 813 | errno = EINVAL; 814 | return -1; 815 | } 816 | n = 0; 817 | OENDCHECK; 818 | while (isdigit(*o) && o < oend) { 819 | n = 10*n + *o++ - '0'; 820 | } 821 | } 822 | n = l - 2; 823 | if (n <= 127) { 824 | e->b[1] = n & 0x7f; 825 | e->b += l; 826 | e->len += l; 827 | } else if ( n <= 255) { 828 | if (e->len + l + 1 > e->max_len) { 829 | errno = EMSGSIZE; 830 | return -1; 831 | } 832 | memmove(e->b+3, e->b+2, n); 833 | e->b[1] = 0x81; 834 | e->b[2] = n & 0xff; 835 | e->b += l+1; 836 | e->len += l+1; 837 | } else if ( n <= 65535) { 838 | if (e->len + l + 2 > e->max_len) { 839 | errno = EMSGSIZE; 840 | return -1; 841 | } 842 | memmove(e->b+4, e->b+2, n); 843 | e->b[1] = 0x82; 844 | e->b[2] = (n >> 8) & 0xff; 845 | e->b[3] = n & 0xff; 846 | e->b += l+2; 847 | e->len += l+2; 848 | } else { 849 | errno = EMSGSIZE; 850 | return -1; 851 | } 852 | return o-oid; 853 | } 854 | 855 | void 856 | ber_dump(FILE *f, struct ber *e) 857 | { 858 | dump_buf(f, e->buf, e->len); 859 | } 860 | 861 | int 862 | oid_belongs_to_table(struct ber *oo, struct ber *tt) 863 | { 864 | unsigned char otype, ttype; 865 | unsigned olen, tlen; 866 | struct ber o = ber_init(oo->buf, oo->max_len); 867 | struct ber t = ber_init(tt->buf, tt->max_len); 868 | 869 | if (decode_type_len(&o, &otype, &olen) < 0) return 0; 870 | if (decode_type_len(&t, &ttype, &tlen) < 0) return 0; 871 | 872 | if (olen <= tlen) return 0; 873 | if (otype != AT_OID || ttype != AT_OID) return 0; 874 | 875 | if (memcmp(o.b, t.b, tlen) != 0) return 0; 876 | return 1; 877 | } 878 | 879 | #define PROBLEM (-9999) 880 | int 881 | oid_compare(struct ber *aa, struct ber *bb) 882 | { 883 | unsigned char atype, btype; 884 | unsigned alen, blen; 885 | struct ber a = ber_init(aa->buf, aa->max_len); 886 | struct ber b = ber_init(bb->buf, bb->max_len); 887 | unsigned char *as, *bs; 888 | unsigned ax, bx, abytes, bbytes; 889 | 890 | if (decode_type_len(&a, &atype, &alen) < 0) return PROBLEM; 891 | if (decode_type_len(&b, &btype, &blen) < 0) return PROBLEM; 892 | if (atype != AT_OID || btype != AT_OID) return PROBLEM; 893 | 894 | as = a.b; 895 | bs = b.b; 896 | 897 | while (alen && blen) { 898 | ax = 0; abytes = 0; 899 | while (*as >= 0x80 && alen > 0) { 900 | ax <<= 7; 901 | ax |= *as & 0x7f; 902 | as++; alen--; 903 | abytes++; 904 | } 905 | if (alen <= 0) { 906 | errno = EINVAL; 907 | return PROBLEM; 908 | } 909 | ax <<= 7; 910 | ax |= *as & 0x7f; 911 | as++; alen--; 912 | if (abytes > 4) { 913 | errno = EINVAL; 914 | return PROBLEM; 915 | } 916 | 917 | bx = 0; bbytes = 0; 918 | while (*bs >= 0x80 && blen > 0) { 919 | bx <<= 7; 920 | bx |= *bs & 0x7f; 921 | bs++; blen--; 922 | bbytes++; 923 | } 924 | if (blen <= 0) { 925 | errno = EINVAL; 926 | return PROBLEM; 927 | } 928 | bx <<= 7; 929 | bx |= *bs & 0x7f; 930 | bs++; blen--; 931 | if (bbytes > 4) { 932 | errno = EINVAL; 933 | return PROBLEM; 934 | } 935 | 936 | if (ax < bx) 937 | return -1; 938 | else if (ax > bx) 939 | return 1; 940 | } 941 | if (alen) 942 | return 1; 943 | if (blen) 944 | return -1; 945 | return 0; 946 | } 947 | -------------------------------------------------------------------------------- /bsdqueue.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 1991, 1993 3 | * The Regents of the University of California. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 4. Neither the name of the University nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | * 29 | * @(#)queue.h 8.5 (Berkeley) 8/20/94 30 | * $FreeBSD: head/sys/sys/queue.h 221843 2011-05-13 15:49:23Z mdf $ 31 | */ 32 | 33 | #ifndef _SYS_QUEUE_H_ 34 | #define _SYS_QUEUE_H_ 35 | 36 | #include 37 | 38 | /* 39 | * This file defines four types of data structures: singly-linked lists, 40 | * singly-linked tail queues, lists and tail queues. 41 | * 42 | * A singly-linked list is headed by a single forward pointer. The elements 43 | * are singly linked for minimum space and pointer manipulation overhead at 44 | * the expense of O(n) removal for arbitrary elements. New elements can be 45 | * added to the list after an existing element or at the head of the list. 46 | * Elements being removed from the head of the list should use the explicit 47 | * macro for this purpose for optimum efficiency. A singly-linked list may 48 | * only be traversed in the forward direction. Singly-linked lists are ideal 49 | * for applications with large datasets and few or no removals or for 50 | * implementing a LIFO queue. 51 | * 52 | * A singly-linked tail queue is headed by a pair of pointers, one to the 53 | * head of the list and the other to the tail of the list. The elements are 54 | * singly linked for minimum space and pointer manipulation overhead at the 55 | * expense of O(n) removal for arbitrary elements. New elements can be added 56 | * to the list after an existing element, at the head of the list, or at the 57 | * end of the list. Elements being removed from the head of the tail queue 58 | * should use the explicit macro for this purpose for optimum efficiency. 59 | * A singly-linked tail queue may only be traversed in the forward direction. 60 | * Singly-linked tail queues are ideal for applications with large datasets 61 | * and few or no removals or for implementing a FIFO queue. 62 | * 63 | * A list is headed by a single forward pointer (or an array of forward 64 | * pointers for a hash table header). The elements are doubly linked 65 | * so that an arbitrary element can be removed without a need to 66 | * traverse the list. New elements can be added to the list before 67 | * or after an existing element or at the head of the list. A list 68 | * may only be traversed in the forward direction. 69 | * 70 | * A tail queue is headed by a pair of pointers, one to the head of the 71 | * list and the other to the tail of the list. The elements are doubly 72 | * linked so that an arbitrary element can be removed without a need to 73 | * traverse the list. New elements can be added to the list before or 74 | * after an existing element, at the head of the list, or at the end of 75 | * the list. A tail queue may be traversed in either direction. 76 | * 77 | * For details on the use of these macros, see the queue(3) manual page. 78 | * 79 | * 80 | * SLIST LIST STAILQ TAILQ 81 | * _HEAD + + + + 82 | * _HEAD_INITIALIZER + + + + 83 | * _ENTRY + + + + 84 | * _INIT + + + + 85 | * _EMPTY + + + + 86 | * _FIRST + + + + 87 | * _NEXT + + + + 88 | * _PREV - - - + 89 | * _LAST - - + + 90 | * _FOREACH + + + + 91 | * _FOREACH_SAFE + + + + 92 | * _FOREACH_REVERSE - - - + 93 | * _FOREACH_REVERSE_SAFE - - - + 94 | * _INSERT_HEAD + + + + 95 | * _INSERT_BEFORE - + - + 96 | * _INSERT_AFTER + + + + 97 | * _INSERT_TAIL - - + + 98 | * _CONCAT - - + + 99 | * _REMOVE_AFTER + - + - 100 | * _REMOVE_HEAD + - + - 101 | * _REMOVE + + + + 102 | * _SWAP + + + + 103 | * 104 | */ 105 | #ifdef QUEUE_MACRO_DEBUG 106 | /* Store the last 2 places the queue element or head was altered */ 107 | struct qm_trace { 108 | char * lastfile; 109 | int lastline; 110 | char * prevfile; 111 | int prevline; 112 | }; 113 | 114 | #define TRACEBUF struct qm_trace trace; 115 | #define TRASHIT(x) do {(x) = (void *)-1;} while (0) 116 | #define QMD_SAVELINK(name, link) void **name = (void *)&(link) 117 | 118 | #define QMD_TRACE_HEAD(head) do { \ 119 | (head)->trace.prevline = (head)->trace.lastline; \ 120 | (head)->trace.prevfile = (head)->trace.lastfile; \ 121 | (head)->trace.lastline = __LINE__; \ 122 | (head)->trace.lastfile = __FILE__; \ 123 | } while (0) 124 | 125 | #define QMD_TRACE_ELEM(elem) do { \ 126 | (elem)->trace.prevline = (elem)->trace.lastline; \ 127 | (elem)->trace.prevfile = (elem)->trace.lastfile; \ 128 | (elem)->trace.lastline = __LINE__; \ 129 | (elem)->trace.lastfile = __FILE__; \ 130 | } while (0) 131 | 132 | #else 133 | #define QMD_TRACE_ELEM(elem) 134 | #define QMD_TRACE_HEAD(head) 135 | #define QMD_SAVELINK(name, link) 136 | #define TRACEBUF 137 | #define TRASHIT(x) 138 | #endif /* QUEUE_MACRO_DEBUG */ 139 | 140 | /* 141 | * Singly-linked List declarations. 142 | */ 143 | #define SLIST_HEAD(name, type) \ 144 | struct name { \ 145 | struct type *slh_first; /* first element */ \ 146 | } 147 | 148 | #define SLIST_HEAD_INITIALIZER(head) \ 149 | { NULL } 150 | 151 | #define SLIST_ENTRY(type) \ 152 | struct { \ 153 | struct type *sle_next; /* next element */ \ 154 | } 155 | 156 | /* 157 | * Singly-linked List functions. 158 | */ 159 | #define SLIST_EMPTY(head) ((head)->slh_first == NULL) 160 | 161 | #define SLIST_FIRST(head) ((head)->slh_first) 162 | 163 | #define SLIST_FOREACH(var, head, field) \ 164 | for ((var) = SLIST_FIRST((head)); \ 165 | (var); \ 166 | (var) = SLIST_NEXT((var), field)) 167 | 168 | #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ 169 | for ((var) = SLIST_FIRST((head)); \ 170 | (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ 171 | (var) = (tvar)) 172 | 173 | #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ 174 | for ((varp) = &SLIST_FIRST((head)); \ 175 | ((var) = *(varp)) != NULL; \ 176 | (varp) = &SLIST_NEXT((var), field)) 177 | 178 | #define SLIST_INIT(head) do { \ 179 | SLIST_FIRST((head)) = NULL; \ 180 | } while (0) 181 | 182 | #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ 183 | SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ 184 | SLIST_NEXT((slistelm), field) = (elm); \ 185 | } while (0) 186 | 187 | #define SLIST_INSERT_HEAD(head, elm, field) do { \ 188 | SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ 189 | SLIST_FIRST((head)) = (elm); \ 190 | } while (0) 191 | 192 | #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) 193 | 194 | #define SLIST_REMOVE(head, elm, type, field) do { \ 195 | QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ 196 | if (SLIST_FIRST((head)) == (elm)) { \ 197 | SLIST_REMOVE_HEAD((head), field); \ 198 | } \ 199 | else { \ 200 | struct type *curelm = SLIST_FIRST((head)); \ 201 | while (SLIST_NEXT(curelm, field) != (elm)) \ 202 | curelm = SLIST_NEXT(curelm, field); \ 203 | SLIST_REMOVE_AFTER(curelm, field); \ 204 | } \ 205 | TRASHIT(*oldnext); \ 206 | } while (0) 207 | 208 | #define SLIST_REMOVE_AFTER(elm, field) do { \ 209 | SLIST_NEXT(elm, field) = \ 210 | SLIST_NEXT(SLIST_NEXT(elm, field), field); \ 211 | } while (0) 212 | 213 | #define SLIST_REMOVE_HEAD(head, field) do { \ 214 | SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ 215 | } while (0) 216 | 217 | #define SLIST_SWAP(head1, head2, type) do { \ 218 | struct type *swap_first = SLIST_FIRST(head1); \ 219 | SLIST_FIRST(head1) = SLIST_FIRST(head2); \ 220 | SLIST_FIRST(head2) = swap_first; \ 221 | } while (0) 222 | 223 | /* 224 | * Singly-linked Tail queue declarations. 225 | */ 226 | #define STAILQ_HEAD(name, type) \ 227 | struct name { \ 228 | struct type *stqh_first;/* first element */ \ 229 | struct type **stqh_last;/* addr of last next element */ \ 230 | } 231 | 232 | #define STAILQ_HEAD_INITIALIZER(head) \ 233 | { NULL, &(head).stqh_first } 234 | 235 | #define STAILQ_ENTRY(type) \ 236 | struct { \ 237 | struct type *stqe_next; /* next element */ \ 238 | } 239 | 240 | /* 241 | * Singly-linked Tail queue functions. 242 | */ 243 | #define STAILQ_CONCAT(head1, head2) do { \ 244 | if (!STAILQ_EMPTY((head2))) { \ 245 | *(head1)->stqh_last = (head2)->stqh_first; \ 246 | (head1)->stqh_last = (head2)->stqh_last; \ 247 | STAILQ_INIT((head2)); \ 248 | } \ 249 | } while (0) 250 | 251 | #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) 252 | 253 | #define STAILQ_FIRST(head) ((head)->stqh_first) 254 | 255 | #define STAILQ_FOREACH(var, head, field) \ 256 | for((var) = STAILQ_FIRST((head)); \ 257 | (var); \ 258 | (var) = STAILQ_NEXT((var), field)) 259 | 260 | 261 | #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ 262 | for ((var) = STAILQ_FIRST((head)); \ 263 | (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ 264 | (var) = (tvar)) 265 | 266 | #define STAILQ_INIT(head) do { \ 267 | STAILQ_FIRST((head)) = NULL; \ 268 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 269 | } while (0) 270 | 271 | #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ 272 | if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ 273 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 274 | STAILQ_NEXT((tqelm), field) = (elm); \ 275 | } while (0) 276 | 277 | #define STAILQ_INSERT_HEAD(head, elm, field) do { \ 278 | if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ 279 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 280 | STAILQ_FIRST((head)) = (elm); \ 281 | } while (0) 282 | 283 | #define STAILQ_INSERT_TAIL(head, elm, field) do { \ 284 | STAILQ_NEXT((elm), field) = NULL; \ 285 | *(head)->stqh_last = (elm); \ 286 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 287 | } while (0) 288 | 289 | #define STAILQ_LAST(head, type, field) \ 290 | (STAILQ_EMPTY((head)) ? \ 291 | NULL : \ 292 | ((struct type *)(void *) \ 293 | ((char *)((head)->stqh_last) - __offsetof(struct type, field)))) 294 | 295 | #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) 296 | 297 | #define STAILQ_REMOVE(head, elm, type, field) do { \ 298 | QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ 299 | if (STAILQ_FIRST((head)) == (elm)) { \ 300 | STAILQ_REMOVE_HEAD((head), field); \ 301 | } \ 302 | else { \ 303 | struct type *curelm = STAILQ_FIRST((head)); \ 304 | while (STAILQ_NEXT(curelm, field) != (elm)) \ 305 | curelm = STAILQ_NEXT(curelm, field); \ 306 | STAILQ_REMOVE_AFTER(head, curelm, field); \ 307 | } \ 308 | TRASHIT(*oldnext); \ 309 | } while (0) 310 | 311 | #define STAILQ_REMOVE_AFTER(head, elm, field) do { \ 312 | if ((STAILQ_NEXT(elm, field) = \ 313 | STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ 314 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 315 | } while (0) 316 | 317 | #define STAILQ_REMOVE_HEAD(head, field) do { \ 318 | if ((STAILQ_FIRST((head)) = \ 319 | STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ 320 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 321 | } while (0) 322 | 323 | #define STAILQ_SWAP(head1, head2, type) do { \ 324 | struct type *swap_first = STAILQ_FIRST(head1); \ 325 | struct type **swap_last = (head1)->stqh_last; \ 326 | STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ 327 | (head1)->stqh_last = (head2)->stqh_last; \ 328 | STAILQ_FIRST(head2) = swap_first; \ 329 | (head2)->stqh_last = swap_last; \ 330 | if (STAILQ_EMPTY(head1)) \ 331 | (head1)->stqh_last = &STAILQ_FIRST(head1); \ 332 | if (STAILQ_EMPTY(head2)) \ 333 | (head2)->stqh_last = &STAILQ_FIRST(head2); \ 334 | } while (0) 335 | 336 | 337 | /* 338 | * List declarations. 339 | */ 340 | #define LIST_HEAD(name, type) \ 341 | struct name { \ 342 | struct type *lh_first; /* first element */ \ 343 | } 344 | 345 | #define LIST_HEAD_INITIALIZER(head) \ 346 | { NULL } 347 | 348 | #define LIST_ENTRY(type) \ 349 | struct { \ 350 | struct type *le_next; /* next element */ \ 351 | struct type **le_prev; /* address of previous next element */ \ 352 | } 353 | 354 | /* 355 | * List functions. 356 | */ 357 | 358 | #if (defined(_KERNEL) && defined(INVARIANTS)) 359 | #define QMD_LIST_CHECK_HEAD(head, field) do { \ 360 | if (LIST_FIRST((head)) != NULL && \ 361 | LIST_FIRST((head))->field.le_prev != \ 362 | &LIST_FIRST((head))) \ 363 | panic("Bad list head %p first->prev != head", (head)); \ 364 | } while (0) 365 | 366 | #define QMD_LIST_CHECK_NEXT(elm, field) do { \ 367 | if (LIST_NEXT((elm), field) != NULL && \ 368 | LIST_NEXT((elm), field)->field.le_prev != \ 369 | &((elm)->field.le_next)) \ 370 | panic("Bad link elm %p next->prev != elm", (elm)); \ 371 | } while (0) 372 | 373 | #define QMD_LIST_CHECK_PREV(elm, field) do { \ 374 | if (*(elm)->field.le_prev != (elm)) \ 375 | panic("Bad link elm %p prev->next != elm", (elm)); \ 376 | } while (0) 377 | #else 378 | #define QMD_LIST_CHECK_HEAD(head, field) 379 | #define QMD_LIST_CHECK_NEXT(elm, field) 380 | #define QMD_LIST_CHECK_PREV(elm, field) 381 | #endif /* (_KERNEL && INVARIANTS) */ 382 | 383 | #define LIST_EMPTY(head) ((head)->lh_first == NULL) 384 | 385 | #define LIST_FIRST(head) ((head)->lh_first) 386 | 387 | #define LIST_FOREACH(var, head, field) \ 388 | for ((var) = LIST_FIRST((head)); \ 389 | (var); \ 390 | (var) = LIST_NEXT((var), field)) 391 | 392 | #define LIST_FOREACH_SAFE(var, head, field, tvar) \ 393 | for ((var) = LIST_FIRST((head)); \ 394 | (var) && ((tvar) = LIST_NEXT((var), field), 1); \ 395 | (var) = (tvar)) 396 | 397 | #define LIST_INIT(head) do { \ 398 | LIST_FIRST((head)) = NULL; \ 399 | } while (0) 400 | 401 | #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 402 | QMD_LIST_CHECK_NEXT(listelm, field); \ 403 | if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ 404 | LIST_NEXT((listelm), field)->field.le_prev = \ 405 | &LIST_NEXT((elm), field); \ 406 | LIST_NEXT((listelm), field) = (elm); \ 407 | (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ 408 | } while (0) 409 | 410 | #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 411 | QMD_LIST_CHECK_PREV(listelm, field); \ 412 | (elm)->field.le_prev = (listelm)->field.le_prev; \ 413 | LIST_NEXT((elm), field) = (listelm); \ 414 | *(listelm)->field.le_prev = (elm); \ 415 | (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ 416 | } while (0) 417 | 418 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 419 | QMD_LIST_CHECK_HEAD((head), field); \ 420 | if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ 421 | LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ 422 | LIST_FIRST((head)) = (elm); \ 423 | (elm)->field.le_prev = &LIST_FIRST((head)); \ 424 | } while (0) 425 | 426 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 427 | 428 | #define LIST_REMOVE(elm, field) do { \ 429 | QMD_SAVELINK(oldnext, (elm)->field.le_next); \ 430 | QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ 431 | QMD_LIST_CHECK_NEXT(elm, field); \ 432 | QMD_LIST_CHECK_PREV(elm, field); \ 433 | if (LIST_NEXT((elm), field) != NULL) \ 434 | LIST_NEXT((elm), field)->field.le_prev = \ 435 | (elm)->field.le_prev; \ 436 | *(elm)->field.le_prev = LIST_NEXT((elm), field); \ 437 | TRASHIT(*oldnext); \ 438 | TRASHIT(*oldprev); \ 439 | } while (0) 440 | 441 | #define LIST_SWAP(head1, head2, type, field) do { \ 442 | struct type *swap_tmp = LIST_FIRST((head1)); \ 443 | LIST_FIRST((head1)) = LIST_FIRST((head2)); \ 444 | LIST_FIRST((head2)) = swap_tmp; \ 445 | if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ 446 | swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ 447 | if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ 448 | swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ 449 | } while (0) 450 | 451 | /* 452 | * Tail queue declarations. 453 | */ 454 | #define TAILQ_HEAD(name, type) \ 455 | struct name { \ 456 | struct type *tqh_first; /* first element */ \ 457 | struct type **tqh_last; /* addr of last next element */ \ 458 | TRACEBUF \ 459 | } 460 | 461 | #define TAILQ_HEAD_INITIALIZER(head) \ 462 | { NULL, &(head).tqh_first } 463 | 464 | #define TAILQ_ENTRY(type) \ 465 | struct { \ 466 | struct type *tqe_next; /* next element */ \ 467 | struct type **tqe_prev; /* address of previous next element */ \ 468 | TRACEBUF \ 469 | } 470 | 471 | /* 472 | * Tail queue functions. 473 | */ 474 | #if (defined(_KERNEL) && defined(INVARIANTS)) 475 | #define QMD_TAILQ_CHECK_HEAD(head, field) do { \ 476 | if (!TAILQ_EMPTY(head) && \ 477 | TAILQ_FIRST((head))->field.tqe_prev != \ 478 | &TAILQ_FIRST((head))) \ 479 | panic("Bad tailq head %p first->prev != head", (head)); \ 480 | } while (0) 481 | 482 | #define QMD_TAILQ_CHECK_TAIL(head, field) do { \ 483 | if (*(head)->tqh_last != NULL) \ 484 | panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ 485 | } while (0) 486 | 487 | #define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ 488 | if (TAILQ_NEXT((elm), field) != NULL && \ 489 | TAILQ_NEXT((elm), field)->field.tqe_prev != \ 490 | &((elm)->field.tqe_next)) \ 491 | panic("Bad link elm %p next->prev != elm", (elm)); \ 492 | } while (0) 493 | 494 | #define QMD_TAILQ_CHECK_PREV(elm, field) do { \ 495 | if (*(elm)->field.tqe_prev != (elm)) \ 496 | panic("Bad link elm %p prev->next != elm", (elm)); \ 497 | } while (0) 498 | #else 499 | #define QMD_TAILQ_CHECK_HEAD(head, field) 500 | #define QMD_TAILQ_CHECK_TAIL(head, headname) 501 | #define QMD_TAILQ_CHECK_NEXT(elm, field) 502 | #define QMD_TAILQ_CHECK_PREV(elm, field) 503 | #endif /* (_KERNEL && INVARIANTS) */ 504 | 505 | #define TAILQ_CONCAT(head1, head2, field) do { \ 506 | if (!TAILQ_EMPTY(head2)) { \ 507 | *(head1)->tqh_last = (head2)->tqh_first; \ 508 | (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ 509 | (head1)->tqh_last = (head2)->tqh_last; \ 510 | TAILQ_INIT((head2)); \ 511 | QMD_TRACE_HEAD(head1); \ 512 | QMD_TRACE_HEAD(head2); \ 513 | } \ 514 | } while (0) 515 | 516 | #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) 517 | 518 | #define TAILQ_FIRST(head) ((head)->tqh_first) 519 | 520 | #define TAILQ_FOREACH(var, head, field) \ 521 | for ((var) = TAILQ_FIRST((head)); \ 522 | (var); \ 523 | (var) = TAILQ_NEXT((var), field)) 524 | 525 | #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ 526 | for ((var) = TAILQ_FIRST((head)); \ 527 | (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ 528 | (var) = (tvar)) 529 | 530 | #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ 531 | for ((var) = TAILQ_LAST((head), headname); \ 532 | (var); \ 533 | (var) = TAILQ_PREV((var), headname, field)) 534 | 535 | #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ 536 | for ((var) = TAILQ_LAST((head), headname); \ 537 | (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ 538 | (var) = (tvar)) 539 | 540 | #define TAILQ_INIT(head) do { \ 541 | TAILQ_FIRST((head)) = NULL; \ 542 | (head)->tqh_last = &TAILQ_FIRST((head)); \ 543 | QMD_TRACE_HEAD(head); \ 544 | } while (0) 545 | 546 | #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 547 | QMD_TAILQ_CHECK_NEXT(listelm, field); \ 548 | if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ 549 | TAILQ_NEXT((elm), field)->field.tqe_prev = \ 550 | &TAILQ_NEXT((elm), field); \ 551 | else { \ 552 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 553 | QMD_TRACE_HEAD(head); \ 554 | } \ 555 | TAILQ_NEXT((listelm), field) = (elm); \ 556 | (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ 557 | QMD_TRACE_ELEM(&(elm)->field); \ 558 | QMD_TRACE_ELEM(&listelm->field); \ 559 | } while (0) 560 | 561 | #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ 562 | QMD_TAILQ_CHECK_PREV(listelm, field); \ 563 | (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ 564 | TAILQ_NEXT((elm), field) = (listelm); \ 565 | *(listelm)->field.tqe_prev = (elm); \ 566 | (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ 567 | QMD_TRACE_ELEM(&(elm)->field); \ 568 | QMD_TRACE_ELEM(&listelm->field); \ 569 | } while (0) 570 | 571 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ 572 | QMD_TAILQ_CHECK_HEAD(head, field); \ 573 | if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ 574 | TAILQ_FIRST((head))->field.tqe_prev = \ 575 | &TAILQ_NEXT((elm), field); \ 576 | else \ 577 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 578 | TAILQ_FIRST((head)) = (elm); \ 579 | (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ 580 | QMD_TRACE_HEAD(head); \ 581 | QMD_TRACE_ELEM(&(elm)->field); \ 582 | } while (0) 583 | 584 | #define TAILQ_INSERT_TAIL(head, elm, field) do { \ 585 | QMD_TAILQ_CHECK_TAIL(head, field); \ 586 | TAILQ_NEXT((elm), field) = NULL; \ 587 | (elm)->field.tqe_prev = (head)->tqh_last; \ 588 | *(head)->tqh_last = (elm); \ 589 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 590 | QMD_TRACE_HEAD(head); \ 591 | QMD_TRACE_ELEM(&(elm)->field); \ 592 | } while (0) 593 | 594 | #define TAILQ_LAST(head, headname) \ 595 | (*(((struct headname *)((head)->tqh_last))->tqh_last)) 596 | 597 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) 598 | 599 | #define TAILQ_PREV(elm, headname, field) \ 600 | (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) 601 | 602 | #define TAILQ_REMOVE(head, elm, field) do { \ 603 | QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ 604 | QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ 605 | QMD_TAILQ_CHECK_NEXT(elm, field); \ 606 | QMD_TAILQ_CHECK_PREV(elm, field); \ 607 | if ((TAILQ_NEXT((elm), field)) != NULL) \ 608 | TAILQ_NEXT((elm), field)->field.tqe_prev = \ 609 | (elm)->field.tqe_prev; \ 610 | else { \ 611 | (head)->tqh_last = (elm)->field.tqe_prev; \ 612 | QMD_TRACE_HEAD(head); \ 613 | } \ 614 | *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ 615 | TRASHIT(*oldnext); \ 616 | TRASHIT(*oldprev); \ 617 | QMD_TRACE_ELEM(&(elm)->field); \ 618 | } while (0) 619 | 620 | #define TAILQ_SWAP(head1, head2, type, field) do { \ 621 | struct type *swap_first = (head1)->tqh_first; \ 622 | struct type **swap_last = (head1)->tqh_last; \ 623 | (head1)->tqh_first = (head2)->tqh_first; \ 624 | (head1)->tqh_last = (head2)->tqh_last; \ 625 | (head2)->tqh_first = swap_first; \ 626 | (head2)->tqh_last = swap_last; \ 627 | if ((swap_first = (head1)->tqh_first) != NULL) \ 628 | swap_first->field.tqe_prev = &(head1)->tqh_first; \ 629 | else \ 630 | (head1)->tqh_last = &(head1)->tqh_first; \ 631 | if ((swap_first = (head2)->tqh_first) != NULL) \ 632 | swap_first->field.tqe_prev = &(head2)->tqh_first; \ 633 | else \ 634 | (head2)->tqh_last = &(head2)->tqh_first; \ 635 | } while (0) 636 | 637 | #endif /* !_SYS_QUEUE_H_ */ 638 | --------------------------------------------------------------------------------