summaryrefslogtreecommitdiff
path: root/module
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2008-03-07 01:43:51 +0000
committerStef Walter <stef@memberwebs.com>2008-03-07 01:43:51 +0000
commit33370463f9553b485e2167de8c3025a43aca7c7a (patch)
tree79a77890c8e3fc0fdf0a697fb91b16bfbf9f6f8b /module
Initial import
Diffstat (limited to 'module')
-rw-r--r--module/Makefile.am19
-rw-r--r--module/bsnmp-pcap.c544
-rw-r--r--module/pcap-tree.def63
-rw-r--r--module/usuals.h68
4 files changed, 694 insertions, 0 deletions
diff --git a/module/Makefile.am b/module/Makefile.am
new file mode 100644
index 0000000..fcdd5cb
--- /dev/null
+++ b/module/Makefile.am
@@ -0,0 +1,19 @@
+
+INCLUDES = -DCONF_PREFIX=\"$(sysconfdir)\"
+
+moduledir = $(prefix)/lib
+module_LTLIBRARIES = snmp_pcap.la
+
+snmp_pcap_la_CFLAGS = -Wall -I$(top_srcdir)
+snmp_pcap_la_LDFLAGS = -module
+snmp_pcap_la_SOURCES = pcap_tree.c pcap_tree.h pcap_oid.h \
+ bsnmp-pcap.c usuals.h
+
+pcap_tree.c: pcap-tree.def
+ gensnmptree -e pcap > pcap_oid.h < $(srcdir)/pcap-tree.def
+ gensnmptree -p pcap_ < $(srcdir)/pcap-tree.def
+
+EXTRA_DIST = pcap-tree.def
+
+CLEANFILES = pcap_tree.* \
+ pcap_oid.h
diff --git a/module/bsnmp-pcap.c b/module/bsnmp-pcap.c
new file mode 100644
index 0000000..21224ca
--- /dev/null
+++ b/module/bsnmp-pcap.c
@@ -0,0 +1,544 @@
+/*
+ * Copyright (c) 2008, Stefan Walter
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ * * The names of contributors to this software may not be
+ * used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ *
+ * CONTRIBUTORS
+ * Stefan Walter <stef@memberwebs.com>
+ */
+
+#include "usuals.h"
+
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <sys/limits.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <syslog.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#include <bsnmp/snmpmod.h>
+#include <pcap.h>
+
+#include "pcap_tree.h"
+#include "pcap_oid.h"
+
+#define SNAP_LEN 48
+#define DEFAULT_FILTER "ip or ip6"
+
+/* our module handle */
+static struct lmodule *module;
+
+/* OIDs */
+static const struct asn_oid oid_pcap = OIDX_pcap;
+
+/* the Object Resource registration index */
+static u_int reg_index = 0;
+
+struct monitor {
+ uint32_t index;
+ TAILQ_ENTRY(monitor) link;
+
+ u_char *descr;
+ u_char *device;
+ pcap_t *handle;
+ void *watch;
+ u_char *filter;
+ struct bpf_program filter_bpf;
+ int filter_valid;
+
+ /* Stats gathered */
+ uint64_t seen_octets;
+ uint64_t seen_packets;
+};
+
+TAILQ_HEAD(monitor_list, monitor);
+
+/* list of monitor structures */
+static struct monitor_list monitors = TAILQ_HEAD_INITIALIZER (monitors);
+
+/* Number of monitors */
+static int monitor_count = 0;
+
+/* -----------------------------------------------------------------------------
+ * HELPERS
+ */
+
+static void
+emsg(const char *format, ...)
+{
+ va_list va;
+ va_start (va, format);
+ vsyslog (LOG_ERR, format, va);
+ va_end (va);
+}
+
+/* -----------------------------------------------------------------------------
+ * MONITORING
+ */
+
+#pragma pack(1)
+
+/* Ethernet header */
+struct ethhdr {
+ #define ETHER_ADDR_LEN 6
+ u_char dhost[ETHER_ADDR_LEN]; /* Destination host address */
+ u_char shost[ETHER_ADDR_LEN]; /* Source host address */
+ u_short type; /* IP? ARP? RARP? etc */
+};
+
+/* IP4 header */
+struct ip4hdr {
+ uint8_t vhl; /* version << 4 | header length >> 2 */
+ uint8_t tos; /* type of service */
+ uint16_t len; /* total length */
+ uint16_t id; /* identification */
+ uint16_t off; /* fragment offset field */
+ #define IP_RF 0x8000 /* reserved fragment flag */
+ #define IP_DF 0x4000 /* dont fragment flag */
+ #define IP_MF 0x2000 /* more fragments flag */
+ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
+ uint8_t ttl; /* time to live */
+ uint8_t proto; /* protocol */
+ uint16_t sum; /* checksum */
+ struct in_addr src, dst; /* source and dest address */
+};
+
+/* IP6 header */
+struct ip6hdr {
+ int32_t flow;
+ int16_t payload;
+ int8_t next;
+ int8_t hops;
+ struct in6_addr src, dst;
+};
+
+#pragma pack()
+
+static void
+monitor_packet (u_char *data, const struct pcap_pkthdr *hdr, const u_char *bytes)
+{
+ struct monitor *mon = (struct monitor*)data;
+ int octets;
+
+ /* Short packet, don't care */
+ if (hdr->len < sizeof (struct ethhdr))
+ return;
+
+ octets = hdr->len - sizeof (struct ethhdr);
+ mon->seen_octets += octets;
+ mon->seen_packets += 1;
+}
+
+static void
+monitor_io (int fd, void *data)
+{
+ struct monitor* mon = (struct monitor*)data;
+ int n_packets;
+
+ n_packets = pcap_dispatch (mon->handle, -1, monitor_packet, (u_char*)mon);
+ if (n_packets < 0)
+ emsg ("couldn't capture packets in monitor: %s", pcap_geterr (mon->handle));
+}
+
+static void
+monitor_free (struct monitor *mon)
+{
+ ASSERT (mon);
+
+ if (mon->descr)
+ free (mon->descr);
+ if (mon->device)
+ free (mon->device);
+ if (mon->watch)
+ fd_deselect (mon->watch);
+ if (mon->handle)
+ pcap_close (mon->handle);
+ if (mon->filter_valid)
+ pcap_freecode (&mon->filter_bpf);
+ if (mon->filter)
+ free (mon->filter);
+
+ TAILQ_REMOVE (&monitors, mon, link);
+ monitor_count--;
+ free (mon);
+}
+
+static struct monitor*
+monitor_alloc (int index)
+{
+ char errbuf[PCAP_ERRBUF_SIZE];
+ struct monitor* mon;
+
+ mon = calloc (1, sizeof (struct monitor));
+ if (!mon) {
+ emsg ("couldn't allocate monitor: out of memory");
+ return NULL;
+ }
+
+ mon->index = index;
+ INSERT_OBJECT_INT (mon, &monitors);
+ monitor_count++;
+
+ mon->device = pcap_lookupdev (errbuf);
+ if (!mon->device) {
+ mon->device = strdup ("any");
+ if (!mon->device) {
+ monitor_free (mon);
+ return NULL;
+ }
+ }
+
+ mon->filter = strdup (DEFAULT_FILTER);
+ if (!mon->filter) {
+ monitor_free (mon);
+ return NULL;
+ }
+
+ mon->descr = strdup ("");
+ if (!mon->descr) {
+ monitor_free (mon);
+ return NULL;
+ }
+
+ return mon;
+}
+
+static void
+monitor_start (struct monitor *mon)
+{
+ char errbuf[PCAP_ERRBUF_SIZE];
+ int fd;
+
+ ASSERT (mon->device);
+ ASSERT (mon->filter);
+
+ mon->handle = pcap_open_live (mon->device, SNAP_LEN, 0, 100, errbuf);
+ if (!mon->handle) {
+ emsg ("couldn't open monitor on %s: %s", mon->device, errbuf);
+ return;
+ }
+
+ if (pcap_compile (mon->handle, &mon->filter_bpf, mon->filter, 1, 0) < 0) {
+ emsg ("couldn't compile monitor expression: %s", pcap_geterr (mon->handle));
+ return;
+ }
+
+ mon->filter_valid = 1;
+ if (pcap_setfilter (mon->handle, &mon->filter_bpf) < 0) {
+ emsg ("couldn't setup monitor expression: %s", pcap_geterr (mon->handle));
+ return;
+ }
+
+ if (pcap_setnonblock (mon->handle, 1, errbuf) < 0) {
+ emsg ("couldn't set monitor in non-block mode: %s", errbuf);
+ return;
+ }
+
+ fd = pcap_get_selectable_fd (mon->handle);
+ if (fd < 0) {
+ emsg ("couldn't get selectable monitor: %s", pcap_geterr (mon->handle));
+ return;
+ }
+
+ mon->watch = fd_select (fd, monitor_io, mon, module);
+ if (!mon->watch) {
+ emsg ("couldn't listen to monitor: %s", strerror (errno));
+ return;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * CALLBACKS/CONFIG
+ */
+
+static int
+op_config (struct monitor *mon, struct snmp_context *ctx,
+ struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op)
+{
+ asn_subid_t which = value->var.subs[sub - 1];
+ int index, r;
+
+ /* Just return values, no creation */
+ if (op == SNMP_OP_GET || op == SNMP_OP_GETNEXT) {
+
+ if (!mon)
+ return SNMP_ERR_NOSUCHNAME;
+
+ switch (which) {
+ case LEAF_pcapIndex:
+ value->v.integer = mon->index;
+ return SNMP_ERR_NOERROR;
+ case LEAF_pcapDescr:
+ return string_get (value, mon->descr, -1);
+ case LEAF_pcapDevice:
+ return string_get (value, mon->device, -1);
+ case LEAF_pcapFilter:
+ return string_get (value, mon->filter, -1);
+ default:
+ ASSERT (0);
+ return SNMP_ERR_NOSUCHNAME;
+ }
+ }
+
+ /* Remainder only at initialization */
+ if (community != COMM_INITIALIZE)
+ return mon ? SNMP_ERR_NOT_WRITEABLE : SNMP_ERR_NO_CREATION;
+
+ /* No writing to pcapIndex */
+ if (which == LEAF_pcapIndex)
+ return SNMP_ERR_NOT_WRITEABLE;
+
+ if (index_decode (&value->var, sub, iidx, &index))
+ return SNMP_ERR_NO_CREATION;
+
+ /* Create it if necessary */
+ if (!mon) {
+ mon = monitor_alloc (index);
+ if (!mon) {
+ emsg ("out of memory");
+ return SNMP_ERR_GENERR;
+ }
+ }
+
+ switch (which) {
+
+ /* pcapDescr */
+ case LEAF_pcapDescr:
+ switch (op) {
+ case SNMP_OP_SET:
+ return string_save (value, ctx, -1, &mon->descr);
+ case SNMP_OP_ROLLBACK:
+ return SNMP_ERR_NOERROR;
+ case SNMP_OP_COMMIT:
+ return SNMP_ERR_NOERROR;
+ default:
+ ASSERT (0);
+ return SNMP_ERR_GENERR;
+ }
+ break;
+
+ /* pcapDevice */
+ case LEAF_pcapDevice:
+ switch (op) {
+ case SNMP_OP_SET:
+ return string_save (value, ctx, -1, &mon->device);
+ case SNMP_OP_ROLLBACK:
+ return SNMP_ERR_NOERROR;
+ case SNMP_OP_COMMIT:
+ return SNMP_ERR_NOERROR;
+ default:
+ ASSERT (0);
+ return SNMP_ERR_GENERR;
+ }
+ break;
+
+ /* pcapFilter */
+ case LEAF_pcapFilter:
+ switch (op) {
+ case SNMP_OP_SET:
+ r = string_save (value, ctx, -1, &mon->filter);
+ return r;
+ case SNMP_OP_ROLLBACK:
+ return SNMP_ERR_NOERROR;
+ case SNMP_OP_COMMIT:
+ return SNMP_ERR_NOERROR;
+ default:
+ ASSERT (0);
+ return SNMP_ERR_GENERR;
+ }
+ break;
+
+ /* Unknown OID */
+ default:
+ ASSERT (0);
+ return SNMP_ERR_NOSUCHNAME;
+ }
+}
+
+int
+op_pcapentry (struct snmp_context *ctx, struct snmp_value *value,
+ u_int sub, u_int iidx, enum snmp_op op)
+{
+ asn_subid_t which = value->var.subs[sub - 1];
+ struct monitor *mon = NULL;
+
+ switch (op) {
+ case SNMP_OP_GETNEXT:
+ mon = NEXT_OBJECT_INT (&monitors, &value->var, sub);
+ if (mon == NULL)
+ return SNMP_ERR_NOSUCHNAME;
+ value->var.len = sub + 1;
+ value->var.subs[sub] = mon->index;
+ break;
+
+ case SNMP_OP_GET:
+ mon = FIND_OBJECT_INT (&monitors, &value->var, sub);
+ if (mon == NULL)
+ return SNMP_ERR_NOSUCHNAME;
+ break;
+
+ default:
+ mon = FIND_OBJECT_INT (&monitors, &value->var, sub);
+ break;
+ };
+
+ /* Send configuration stuff off elsewhere */
+ switch (which) {
+ case LEAF_pcapIndex:
+ case LEAF_pcapDescr:
+ case LEAF_pcapDevice:
+ case LEAF_pcapFilter:
+ return op_config (mon, ctx, value, sub, iidx, op);
+ }
+
+ if (op != SNMP_OP_GET && op != SNMP_OP_GETNEXT)
+ return SNMP_ERR_NOT_WRITEABLE;
+
+ switch (which) {
+ case LEAF_pcapOctets:
+ value->v.counter64 = mon->seen_octets;
+ return SNMP_ERR_NOERROR;
+ case LEAF_pcapPackets:
+ value->v.counter64 = mon->seen_packets;
+ return SNMP_ERR_NOERROR;
+ default:
+ ASSERT (0);
+ return SNMP_ERR_NOSUCHNAME;
+ };
+}
+
+int
+op_pcap (struct snmp_context *ctx, struct snmp_value *value,
+ u_int sub, u_int iidx, enum snmp_op op)
+{
+ asn_subid_t which = value->var.subs[sub - 1];
+
+ switch (op) {
+ case SNMP_OP_GET:
+ break;
+
+ case SNMP_OP_SET:
+ return SNMP_ERR_NOT_WRITEABLE;
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ return SNMP_ERR_NOERROR;
+
+ default:
+ ASSERT(0);
+ break;
+ };
+
+ switch (which) {
+ case LEAF_pcapCount:
+ value->v.integer = monitor_count;
+ break;
+
+ default:
+ ASSERT(0);
+ break;
+ };
+
+ return SNMP_ERR_NOERROR;
+}
+
+
+/* -----------------------------------------------------------------------------
+ * MODULE
+ */
+
+/* Called, when the module is to be unloaded after it was successfully loaded */
+static int
+module_fini (void)
+{
+ struct monitor *mon;
+fprintf(stderr, "fini\n");
+
+ if (reg_index)
+ or_unregister (reg_index);
+
+ while ((mon = TAILQ_FIRST(&monitors)) != NULL)
+ monitor_free (mon);
+
+ return 0;
+}
+
+/* the initialisation function */
+static int
+module_init (struct lmodule *mod, int argc, char *argv[])
+{
+ module = mod;
+
+ if (argc != 0) {
+ syslog (LOG_ERR, "bad number of arguments for %s", __func__);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+/* Module is started */
+static void
+module_start (void)
+{
+ struct monitor *mon;
+fprintf(stderr, "start\n");
+ reg_index = or_register (&oid_pcap, "The MIB for pcap data.", module);
+
+ /* Start each monitor */
+ TAILQ_FOREACH (mon, &monitors, link) {
+ monitor_start (mon);
+ }
+}
+
+const struct snmp_module config = {
+ .comment = "This module implements SNMP pcap monitoring of traffic",
+ .init = module_init,
+ .start = module_start,
+ .fini = module_fini,
+ .tree = pcap_ctree,
+ .tree_size = pcap_CTREE_SIZE,
+};
+
diff --git a/module/pcap-tree.def b/module/pcap-tree.def
new file mode 100644
index 0000000..fb780c7
--- /dev/null
+++ b/module/pcap-tree.def
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2008, Stefan Walter
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the
+# following disclaimer.
+# * Redistributions in binary form must reproduce the
+# above copyright notice, this list of conditions and
+# the following disclaimer in the documentation and/or
+# other materials provided with the distribution.
+# * The names of contributors to this software may not be
+# used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+#
+#
+# CONTRIBUTORS
+# Stef Walter <stef@memberwebs.com>
+#
+
+(1 internet
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (1112 pcap
+ (1 pcapCount INTEGER op_pcap GET)
+ (2 pcapTable
+ (1 pcapEntry : INTEGER op_pcapentry
+ (0 pcapIndex INTEGER GET)
+
+ (1 pcapDescr OCTETSTRING GET SET)
+ (2 pcapDevice OCTETSTRING GET SET)
+ (3 pcapFilter OCTETSTRING GET SET)
+
+ (10 pcapOctets COUNTER64 GET)
+ (11 pcapPackets COUNTER64 GET)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+)
+
diff --git a/module/usuals.h b/module/usuals.h
new file mode 100644
index 0000000..e975dcd
--- /dev/null
+++ b/module/usuals.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2006, Stefan Walter
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ * * The names of contributors to this software may not be
+ * used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ *
+ * CONTRIBUTORS
+ * Stef Walter <stef@memberwebs.com>
+ */
+
+#ifndef __USUALS_H__
+#define __USUALS_H__
+
+#include <sys/types.h>
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef max
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#define countof(x) (sizeof(x) / sizeof(x[0]))
+
+#ifdef _DEBUG
+ #include "assert.h"
+ #define ASSERT(x) assert(x)
+#else
+ #define ASSERT(x)
+#endif
+
+#endif /* __USUALS_H__ */