/* chronyd/chronyc - Programs for keeping computer clocks accurate. ********************************************************************** * Copyright (C) Richard P. Curnow 1997-2003 * Copyright (C) Miroslav Lichvar 2009 * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ********************************************************************** ======================================================================= This module keeps a count of the number of successful accesses by clients, and the times of the last accesses. This can be used for status reporting, and (in the case of a server), if it needs to know which clients have made use of its data recently. */ #include "sysincl.h" #include "clientlog.h" #include "conf.h" #include "memory.h" #include "reports.h" #include "util.h" #include "logging.h" /* Number of bits of address per layer of the table. This value has been chosen on the basis that a server will predominantly be serving a lot of hosts in a few subnets, rather than a few hosts scattered across many subnets. */ #define NBITS 8 /* Number of entries in each subtable */ #define TABLE_SIZE (1UL<addr.in6[i * 4 + 0] << 24 | ip->addr.in6[i * 4 + 1] << 16 | ip->addr.in6[i * 4 + 2] << 8 | ip->addr.in6[i * 4 + 3]; } /* ================================================== */ inline static uint32_t get_subnet(uint32_t *addr, unsigned int where) { int off; off = where / 32; where %= 32; return (addr[off] >> (32 - NBITS - where)) & ((1UL << NBITS) - 1); } /* ================================================== */ static void clear_subnet(Subnet *subnet) { int i; for (i=0; ientry[i] = NULL; } } /* ================================================== */ static void clear_node(Node *node) { node->client_hits = 0; node->peer_hits = 0; node->cmd_hits_auth = 0; node->cmd_hits_normal = 0; node->cmd_hits_bad = 0; node->last_ntp_hit = (time_t) 0; node->last_cmd_hit = (time_t) 0; } /* ================================================== */ void CLG_Initialise(void) { clear_subnet(&top_subnet4); clear_subnet(&top_subnet6); if (CNF_GetNoClientLog()) { active = 0; } else { active = 1; } nodes = NULL; max_nodes = 0; n_nodes = 0; alloced = 0; alloc_limit = CNF_GetClientLogLimit(); alloc_limit_reached = 0; } /* ================================================== */ void CLG_Finalise(void) { return; } /* ================================================== */ static void check_alloc_limit() { if (alloc_limit_reached) return; if (alloced >= alloc_limit) { LOG(LOGS_WARN, LOGF_ClientLog, "Client log memory limit reached"); alloc_limit_reached = 1; } } /* ================================================== */ static void create_subnet(Subnet *parent_subnet, int the_entry) { parent_subnet->entry[the_entry] = (void *) MallocNew(Subnet); clear_subnet((Subnet *) parent_subnet->entry[the_entry]); alloced += sizeof (Subnet); check_alloc_limit(); } /* ================================================== */ static void create_node(Subnet *parent_subnet, int the_entry) { Node *new_node; new_node = MallocNew(Node); parent_subnet->entry[the_entry] = (void *) new_node; clear_node(new_node); alloced += sizeof (Node); if (n_nodes == max_nodes) { if (nodes) { assert(max_nodes > 0); max_nodes *= 2; nodes = ReallocArray(Node *, max_nodes, nodes); } else { assert(max_nodes == 0); max_nodes = 16; nodes = MallocArray(Node *, max_nodes); } alloced += sizeof (Node *) * (max_nodes - n_nodes); } nodes[n_nodes++] = (Node *) new_node; check_alloc_limit(); } /* ================================================== */ /* Recursively seek out the Node entry for a particular address, expanding subnet tables and node entries as we go if necessary. */ static void * find_subnet(Subnet *subnet, uint32_t *addr, int addr_len, int bits_consumed) { uint32_t this_subnet; this_subnet = get_subnet(addr, bits_consumed); bits_consumed += NBITS; if (bits_consumed < 32 * addr_len) { if (!subnet->entry[this_subnet]) { if (alloc_limit_reached) return NULL; create_subnet(subnet, this_subnet); } return find_subnet((Subnet *) subnet->entry[this_subnet], addr, addr_len, bits_consumed); } else { if (!subnet->entry[this_subnet]) { if (alloc_limit_reached) return NULL; create_node(subnet, this_subnet); } return subnet->entry[this_subnet]; } } /* ================================================== */ /* Search for the record for a particular subnet, but return NULL if one of the parents does not exist - never open a node out */ static void * find_subnet_dont_open(Subnet *subnet, uint32_t *addr, int addr_len, int bits_consumed) { uint32_t this_subnet; if (bits_consumed >= 32 * addr_len) { return subnet; } else { this_subnet = get_subnet(addr, bits_consumed); bits_consumed += NBITS; if (!subnet->entry[this_subnet]) { return NULL; } else { return find_subnet_dont_open((Subnet *) subnet->entry[this_subnet], addr, addr_len, bits_consumed); } } } /* ================================================== */ void CLG_LogNTPClientAccess (IPAddr *client, time_t now) { uint32_t ip6[4]; Node *node; if (active) { switch (client->family) { case IPADDR_INET4: node = (Node *) find_subnet(&top_subnet4, &client->addr.in4, 1, 0); break; case IPADDR_INET6: split_ip6(client, ip6); node = (Node *) find_subnet(&top_subnet6, ip6, 4, 0); break; default: assert(0); } if (node == NULL) return; node->ip_addr = *client; ++node->client_hits; node->last_ntp_hit = now; } } /* ================================================== */ void CLG_LogNTPPeerAccess(IPAddr *client, time_t now) { uint32_t ip6[4]; Node *node; if (active) { switch (client->family) { case IPADDR_INET4: node = (Node *) find_subnet(&top_subnet4, &client->addr.in4, 1, 0); break; case IPADDR_INET6: split_ip6(client, ip6); node = (Node *) find_subnet(&top_subnet6, ip6, 4, 0); break; default: assert(0); } if (node == NULL) return; node->ip_addr = *client; ++node->peer_hits; node->last_ntp_hit = now; } } /* ================================================== */ void CLG_LogCommandAccess(IPAddr *client, CLG_Command_Type type, time_t now) { uint32_t ip6[4]; Node *node; if (active) { switch (client->family) { case IPADDR_INET4: node = (Node *) find_subnet(&top_subnet4, &client->addr.in4, 1, 0); break; case IPADDR_INET6: split_ip6(client, ip6); node = (Node *) find_subnet(&top_subnet6, ip6, 4, 0); break; default: assert(0); } if (node == NULL) return; node->ip_addr = *client; node->last_cmd_hit = now; switch (type) { case CLG_CMD_AUTH: ++node->cmd_hits_auth; break; case CLG_CMD_NORMAL: ++node->cmd_hits_normal; break; case CLG_CMD_BAD_PKT: ++node->cmd_hits_bad; break; default: assert(0); break; } } } /* ================================================== */ CLG_Status CLG_GetSubnetBitmap(IPAddr *subnet, int bits, CLG_Bitmap result) { Subnet *s; uint32_t ip6[4]; unsigned long i; unsigned long word, bit, mask; if (bits >= 0 && bits % 8 == 0) { memset (result, 0, TABLE_SIZE/8); if (active) { switch (subnet->family) { case IPADDR_INET4: if (bits >= 32) return CLG_BADSUBNET; s = find_subnet_dont_open(&top_subnet4, &subnet->addr.in4, 1, 32 - bits); break; case IPADDR_INET6: if (bits >= 128) return CLG_BADSUBNET; split_ip6(subnet, ip6); s = find_subnet_dont_open(&top_subnet6, ip6, 4, 128 - bits); break; default: return CLG_BADSUBNET; } if (s) { for (i=0; i<256; i++) { if (s->entry[i]) { word = i / 32; bit = i % 32; mask = 1UL << bit; result[word] |= mask; } } return CLG_SUCCESS; } else { return CLG_EMPTYSUBNET; } } else { return CLG_INACTIVE; } } else { return CLG_BADSUBNET; } } /* ================================================== */ CLG_Status CLG_GetClientAccessReportByIP(IPAddr *ip, RPT_ClientAccess_Report *report, time_t now) { uint32_t ip6[4]; Node *node; if (!active) { return CLG_INACTIVE; } else { switch (ip->family) { case IPADDR_INET4: node = (Node *) find_subnet_dont_open(&top_subnet4, &ip->addr.in4, 1, 0); break; case IPADDR_INET6: split_ip6(ip, ip6); node = (Node *) find_subnet_dont_open(&top_subnet6, ip6, 4, 0); break; default: return CLG_EMPTYSUBNET; } if (!node) { return CLG_EMPTYSUBNET; } else { report->client_hits = node->client_hits; report->peer_hits = node->peer_hits; report->cmd_hits_auth = node->cmd_hits_auth; report->cmd_hits_normal = node->cmd_hits_normal; report->cmd_hits_bad = node->cmd_hits_bad; report->last_ntp_hit_ago = now - node->last_ntp_hit; report->last_cmd_hit_ago = now - node->last_cmd_hit; return CLG_SUCCESS; } } } /* ================================================== */ CLG_Status CLG_GetClientAccessReportByIndex(int index, RPT_ClientAccessByIndex_Report *report, time_t now, unsigned long *n_indices) { Node *node; *n_indices = n_nodes; if (!active) { return CLG_INACTIVE; } else { if ((index < 0) || (index >= n_nodes)) { return CLG_INDEXTOOLARGE; } node = nodes[index]; report->ip_addr = node->ip_addr; report->client_hits = node->client_hits; report->peer_hits = node->peer_hits; report->cmd_hits_auth = node->cmd_hits_auth; report->cmd_hits_normal = node->cmd_hits_normal; report->cmd_hits_bad = node->cmd_hits_bad; report->last_ntp_hit_ago = now - node->last_ntp_hit; report->last_cmd_hit_ago = now - node->last_cmd_hit; return CLG_SUCCESS; } }