154359Sroberto/* 2132451Sroberto * ntp_monitor - monitor ntpd statistics 354359Sroberto */ 454359Sroberto#ifdef HAVE_CONFIG_H 582498Sroberto# include <config.h> 654359Sroberto#endif 754359Sroberto 854359Sroberto#include "ntpd.h" 954359Sroberto#include "ntp_io.h" 1054359Sroberto#include "ntp_if.h" 11290001Sglebius#include "ntp_lists.h" 1254359Sroberto#include "ntp_stdlib.h" 13182007Sroberto#include <ntp_random.h> 1454359Sroberto 1582498Sroberto#include <stdio.h> 1682498Sroberto#include <signal.h> 1782498Sroberto#ifdef HAVE_SYS_IOCTL_H 1882498Sroberto# include <sys/ioctl.h> 1982498Sroberto#endif 2082498Sroberto 2154359Sroberto/* 22290001Sglebius * Record statistics based on source address, mode and version. The 23290001Sglebius * receive procedure calls us with the incoming rbufp before it does 24290001Sglebius * anything else. While at it, implement rate controls for inbound 25290001Sglebius * traffic. 2654359Sroberto * 27290001Sglebius * Each entry is doubly linked into two lists, a hash table and a most- 28290001Sglebius * recently-used (MRU) list. When a packet arrives it is looked up in 29290001Sglebius * the hash table. If found, the statistics are updated and the entry 30132451Sroberto * relinked at the head of the MRU list. If not found, a new entry is 31132451Sroberto * allocated, initialized and linked into both the hash table and at the 32132451Sroberto * head of the MRU list. 3354359Sroberto * 34132451Sroberto * Memory is usually allocated by grabbing a big chunk of new memory and 35132451Sroberto * cutting it up into littler pieces. The exception to this when we hit 36132451Sroberto * the memory limit. Then we free memory by grabbing entries off the 37132451Sroberto * tail for the MRU list, unlinking from the hash table, and 3854359Sroberto * reinitializing. 3954359Sroberto * 40290001Sglebius * INC_MONLIST is the default allocation granularity in entries. 41290001Sglebius * INIT_MONLIST is the default initial allocation in entries. 4254359Sroberto */ 43290001Sglebius#ifdef MONMEMINC /* old name */ 44290001Sglebius# define INC_MONLIST MONMEMINC 45290001Sglebius#elif !defined(INC_MONLIST) 46290001Sglebius# define INC_MONLIST (4 * 1024 / sizeof(mon_entry)) 4754359Sroberto#endif 48290001Sglebius#ifndef INIT_MONLIST 49290001Sglebius# define INIT_MONLIST (4 * 1024 / sizeof(mon_entry)) 5054359Sroberto#endif 51290001Sglebius#ifndef MRU_MAXDEPTH_DEF 52290001Sglebius# define MRU_MAXDEPTH_DEF (1024 * 1024 / sizeof(mon_entry)) 53290001Sglebius#endif 5454359Sroberto 5554359Sroberto/* 5654359Sroberto * Hashing stuff 5754359Sroberto */ 58290001Sglebiusu_char mon_hash_bits; 5954359Sroberto 6054359Sroberto/* 61290001Sglebius * Pointers to the hash table and the MRU list. Memory for the hash 62290001Sglebius * table is allocated only if monitoring is enabled. 6354359Sroberto */ 64290001Sglebiusmon_entry ** mon_hash; /* MRU hash table */ 65290001Sglebiusmon_entry mon_mru_list; /* mru listhead */ 66132451Sroberto 6754359Sroberto/* 68290001Sglebius * List of free structures structures, and counters of in-use and total 69290001Sglebius * structures. The free structures are linked with the hash_next field. 7054359Sroberto */ 71290001Sglebiusstatic mon_entry *mon_free; /* free list or null if none */ 72290001Sglebius u_int mru_alloc; /* mru list + free list count */ 73290001Sglebius u_int mru_entries; /* mru list count */ 74290001Sglebius u_int mru_peakentries; /* highest mru_entries seen */ 75290001Sglebius u_int mru_initalloc = INIT_MONLIST;/* entries to preallocate */ 76290001Sglebius u_int mru_incalloc = INC_MONLIST;/* allocation batch factor */ 77290001Sglebiusstatic u_int mon_mem_increments; /* times called malloc() */ 7854359Sroberto 7954359Sroberto/* 80290001Sglebius * Parameters of the RES_LIMITED restriction option. We define headway 81290001Sglebius * as the idle time between packets. A packet is discarded if the 82290001Sglebius * headway is less than the minimum, as well as if the average headway 83290001Sglebius * is less than eight times the increment. 84290001Sglebius */ 85290001Sglebiusint ntp_minpkt = NTP_MINPKT; /* minimum (log 2 s) */ 86290001Sglebiusu_char ntp_minpoll = NTP_MINPOLL; /* increment (log 2 s) */ 87290001Sglebius 88290001Sglebius/* 8954359Sroberto * Initialization state. We may be monitoring, we may not. If 9054359Sroberto * we aren't, we may not even have allocated any memory yet. 9154359Sroberto */ 92290001Sglebius u_int mon_enabled; /* enable switch */ 93290001Sglebius u_int mru_mindepth = 600; /* preempt above this */ 94290001Sglebius int mru_maxage = 64; /* for entries older than */ 95290001Sglebius u_int mru_maxdepth = /* MRU count hard limit */ 96290001Sglebius MRU_MAXDEPTH_DEF; 97290001Sglebius int mon_age = 3000; /* preemption limit */ 9854359Sroberto 99290001Sglebiusstatic void mon_getmoremem(void); 100290001Sglebiusstatic void remove_from_hash(mon_entry *); 101290001Sglebiusstatic inline void mon_free_entry(mon_entry *); 102290001Sglebiusstatic inline void mon_reclaim_entry(mon_entry *); 103290001Sglebius 104290001Sglebius 10554359Sroberto/* 10654359Sroberto * init_mon - initialize monitoring global data 10754359Sroberto */ 10854359Srobertovoid 10954359Srobertoinit_mon(void) 11054359Sroberto{ 11154359Sroberto /* 11254359Sroberto * Don't do much of anything here. We don't allocate memory 113290001Sglebius * until mon_start(). 11454359Sroberto */ 11554359Sroberto mon_enabled = MON_OFF; 116290001Sglebius INIT_DLIST(mon_mru_list, mru); 117290001Sglebius} 11854359Sroberto 119290001Sglebius 120290001Sglebius/* 121290001Sglebius * remove_from_hash - removes an entry from the address hash table and 122290001Sglebius * decrements mru_entries. 123290001Sglebius */ 124290001Sglebiusstatic void 125290001Sglebiusremove_from_hash( 126290001Sglebius mon_entry *mon 127290001Sglebius ) 128290001Sglebius{ 129290001Sglebius u_int hash; 130290001Sglebius mon_entry *punlinked; 131290001Sglebius 132290001Sglebius mru_entries--; 133290001Sglebius hash = MON_HASH(&mon->rmtadr); 134290001Sglebius UNLINK_SLIST(punlinked, mon_hash[hash], mon, hash_next, 135290001Sglebius mon_entry); 136290001Sglebius ENSURE(punlinked == mon); 13754359Sroberto} 13854359Sroberto 13954359Sroberto 140290001Sglebiusstatic inline void 141290001Sglebiusmon_free_entry( 142290001Sglebius mon_entry *m 143290001Sglebius ) 144290001Sglebius{ 145290001Sglebius ZERO(*m); 146290001Sglebius LINK_SLIST(mon_free, m, hash_next); 147290001Sglebius} 148290001Sglebius 149290001Sglebius 15054359Sroberto/* 151290001Sglebius * mon_reclaim_entry - Remove an entry from the MRU list and from the 152290001Sglebius * hash array, then zero-initialize it. Indirectly 153290001Sglebius * decrements mru_entries. 154290001Sglebius 155290001Sglebius * The entry is prepared to be reused. Before return, in 156290001Sglebius * remove_from_hash(), mru_entries is decremented. It is the caller's 157290001Sglebius * responsibility to increment it again. 158290001Sglebius */ 159290001Sglebiusstatic inline void 160290001Sglebiusmon_reclaim_entry( 161290001Sglebius mon_entry *m 162290001Sglebius ) 163290001Sglebius{ 164290001Sglebius DEBUG_INSIST(NULL != m); 165290001Sglebius 166290001Sglebius UNLINK_DLIST(m, mru); 167290001Sglebius remove_from_hash(m); 168290001Sglebius ZERO(*m); 169290001Sglebius} 170290001Sglebius 171290001Sglebius 172290001Sglebius/* 173290001Sglebius * mon_getmoremem - get more memory and put it on the free list 174290001Sglebius */ 175290001Sglebiusstatic void 176290001Sglebiusmon_getmoremem(void) 177290001Sglebius{ 178290001Sglebius mon_entry *chunk; 179290001Sglebius u_int entries; 180290001Sglebius 181290001Sglebius entries = (0 == mon_mem_increments) 182290001Sglebius ? mru_initalloc 183290001Sglebius : mru_incalloc; 184290001Sglebius 185290001Sglebius if (entries) { 186290001Sglebius chunk = eallocarray(entries, sizeof(*chunk)); 187290001Sglebius mru_alloc += entries; 188290001Sglebius for (chunk += entries; entries; entries--) 189290001Sglebius mon_free_entry(--chunk); 190290001Sglebius 191290001Sglebius mon_mem_increments++; 192290001Sglebius } 193290001Sglebius} 194290001Sglebius 195290001Sglebius 196290001Sglebius/* 19754359Sroberto * mon_start - start up the monitoring software 19854359Sroberto */ 19954359Srobertovoid 20054359Srobertomon_start( 20154359Sroberto int mode 20254359Sroberto ) 20354359Sroberto{ 204290001Sglebius size_t octets; 205290001Sglebius u_int min_hash_slots; 20654359Sroberto 207290001Sglebius if (MON_OFF == mode) /* MON_OFF is 0 */ 208290001Sglebius return; 209290001Sglebius if (mon_enabled) { 21054359Sroberto mon_enabled |= mode; 21154359Sroberto return; 21254359Sroberto } 213290001Sglebius if (0 == mon_mem_increments) 21454359Sroberto mon_getmoremem(); 215290001Sglebius /* 216290001Sglebius * Select the MRU hash table size to limit the average count 217290001Sglebius * per bucket at capacity (mru_maxdepth) to 8, if possible 218290001Sglebius * given our hash is limited to 16 bits. 219290001Sglebius */ 220290001Sglebius min_hash_slots = (mru_maxdepth / 8) + 1; 221290001Sglebius mon_hash_bits = 0; 222290001Sglebius while (min_hash_slots >>= 1) 223290001Sglebius mon_hash_bits++; 224290001Sglebius mon_hash_bits = max(4, mon_hash_bits); 225290001Sglebius mon_hash_bits = min(16, mon_hash_bits); 226290001Sglebius octets = sizeof(*mon_hash) * MON_HASH_SIZE; 227290001Sglebius mon_hash = erealloc_zero(mon_hash, octets, 0); 22854359Sroberto 22954359Sroberto mon_enabled = mode; 23054359Sroberto} 23154359Sroberto 23254359Sroberto 23354359Sroberto/* 23454359Sroberto * mon_stop - stop the monitoring software 23554359Sroberto */ 23654359Srobertovoid 23754359Srobertomon_stop( 23854359Sroberto int mode 23954359Sroberto ) 24054359Sroberto{ 241290001Sglebius mon_entry *mon; 24254359Sroberto 243290001Sglebius if (MON_OFF == mon_enabled) 244290001Sglebius return; 24554359Sroberto if ((mon_enabled & mode) == 0 || mode == MON_OFF) 246290001Sglebius return; 24754359Sroberto 24854359Sroberto mon_enabled &= ~mode; 24954359Sroberto if (mon_enabled != MON_OFF) 250290001Sglebius return; 25154359Sroberto 25254359Sroberto /* 253290001Sglebius * Move everything on the MRU list to the free list quickly, 254290001Sglebius * without bothering to remove each from either the MRU list or 255290001Sglebius * the hash table. 25654359Sroberto */ 257290001Sglebius ITER_DLIST_BEGIN(mon_mru_list, mon, mru, mon_entry) 258290001Sglebius mon_free_entry(mon); 259290001Sglebius ITER_DLIST_END() 26054359Sroberto 261290001Sglebius /* empty the MRU list and hash table. */ 262290001Sglebius mru_entries = 0; 263290001Sglebius INIT_DLIST(mon_mru_list, mru); 264290001Sglebius zero_mem(mon_hash, sizeof(*mon_hash) * MON_HASH_SIZE); 26554359Sroberto} 26654359Sroberto 267290001Sglebius 268290001Sglebius/* 269290001Sglebius * mon_clearinterface -- remove mru entries referring to a local address 270290001Sglebius * which is going away. 271290001Sglebius */ 272182007Srobertovoid 273290001Sglebiusmon_clearinterface( 274290001Sglebius endpt *lcladr 275290001Sglebius ) 276182007Sroberto{ 277290001Sglebius mon_entry *mon; 27854359Sroberto 279290001Sglebius /* iterate mon over mon_mru_list */ 280290001Sglebius ITER_DLIST_BEGIN(mon_mru_list, mon, mru, mon_entry) 281290001Sglebius if (mon->lcladr == lcladr) { 282290001Sglebius /* remove from mru list */ 283290001Sglebius UNLINK_DLIST(mon, mru); 284290001Sglebius /* remove from hash list, adjust mru_entries */ 285290001Sglebius remove_from_hash(mon); 286290001Sglebius /* put on free list */ 287290001Sglebius mon_free_entry(mon); 288290001Sglebius } 289290001Sglebius ITER_DLIST_END() 290182007Sroberto} 291182007Sroberto 292290001Sglebius 29354359Sroberto/* 29454359Sroberto * ntp_monitor - record stats about this packet 295182007Sroberto * 296290001Sglebius * Returns supplied restriction flags, with RES_LIMITED and RES_KOD 297290001Sglebius * cleared unless the packet should not be responded to normally 298290001Sglebius * (RES_LIMITED) and possibly should trigger a KoD response (RES_KOD). 299290001Sglebius * The returned flags are saved in the MRU entry, so that it reflects 300290001Sglebius * whether the last packet from that source triggered rate limiting, 301290001Sglebius * and if so, possible KoD response. This implies you can not tell 302290001Sglebius * whether a given address is eligible for rate limiting/KoD from the 303290001Sglebius * monlist restrict bits, only whether or not the last packet triggered 304290001Sglebius * such responses. ntpdc -c reslist lets you see whether RES_LIMITED 305290001Sglebius * or RES_KOD is lit for a particular address before ntp_monitor()'s 306290001Sglebius * typical dousing. 30754359Sroberto */ 308290001Sglebiusu_short 30954359Srobertontp_monitor( 310290001Sglebius struct recvbuf *rbufp, 311290001Sglebius u_short flags 31254359Sroberto ) 31354359Sroberto{ 314290001Sglebius l_fp interval_fp; 315290001Sglebius struct pkt * pkt; 316290001Sglebius mon_entry * mon; 317290001Sglebius mon_entry * oldest; 318290001Sglebius int oldest_age; 319290001Sglebius u_int hash; 320290001Sglebius u_short restrict_mask; 321290001Sglebius u_char mode; 322290001Sglebius u_char version; 323290001Sglebius int interval; 324290001Sglebius int head; /* headway increment */ 325290001Sglebius int leak; /* new headway */ 326290001Sglebius int limit; /* average threshold */ 32754359Sroberto 328290001Sglebius REQUIRE(rbufp != NULL); 329290001Sglebius 33054359Sroberto if (mon_enabled == MON_OFF) 331290001Sglebius return ~(RES_LIMITED | RES_KOD) & flags; 33254359Sroberto 33354359Sroberto pkt = &rbufp->recv_pkt; 334290001Sglebius hash = MON_HASH(&rbufp->recv_srcadr); 33554359Sroberto mode = PKT_MODE(pkt->li_vn_mode); 336290001Sglebius version = PKT_VERSION(pkt->li_vn_mode); 337290001Sglebius mon = mon_hash[hash]; 338132451Sroberto 339290001Sglebius /* 340290001Sglebius * We keep track of all traffic for a given IP in one entry, 341290001Sglebius * otherwise cron'ed ntpdate or similar evades RES_LIMITED. 342290001Sglebius */ 343290001Sglebius 344290001Sglebius for (; mon != NULL; mon = mon->hash_next) 345290001Sglebius if (SOCK_EQ(&mon->rmtadr, &rbufp->recv_srcadr)) 346290001Sglebius break; 347290001Sglebius 348290001Sglebius if (mon != NULL) { 349290001Sglebius interval_fp = rbufp->recv_time; 350290001Sglebius L_SUB(&interval_fp, &mon->last); 351290001Sglebius /* add one-half second to round up */ 352290001Sglebius L_ADDUF(&interval_fp, 0x80000000); 353290001Sglebius interval = interval_fp.l_i; 354290001Sglebius mon->last = rbufp->recv_time; 355290001Sglebius NSRCPORT(&mon->rmtadr) = NSRCPORT(&rbufp->recv_srcadr); 356290001Sglebius mon->count++; 357290001Sglebius restrict_mask = flags; 358290001Sglebius mon->vn_mode = VN_MODE(version, mode); 359290001Sglebius 360290001Sglebius /* Shuffle to the head of the MRU list. */ 361290001Sglebius UNLINK_DLIST(mon, mru); 362290001Sglebius LINK_DLIST(mon_mru_list, mon, mru); 363290001Sglebius 364132451Sroberto /* 365290001Sglebius * At this point the most recent arrival is first in the 366290001Sglebius * MRU list. Decrease the counter by the headway, but 367290001Sglebius * not less than zero. 368132451Sroberto */ 369290001Sglebius mon->leak -= interval; 370290001Sglebius mon->leak = max(0, mon->leak); 371290001Sglebius head = 1 << ntp_minpoll; 372290001Sglebius leak = mon->leak + head; 373290001Sglebius limit = NTP_SHIFT * head; 37454359Sroberto 375290001Sglebius DPRINTF(2, ("MRU: interval %d headway %d limit %d\n", 376290001Sglebius interval, leak, limit)); 377290001Sglebius 378290001Sglebius /* 379290001Sglebius * If the minimum and average thresholds are not 380290001Sglebius * exceeded, douse the RES_LIMITED and RES_KOD bits and 381290001Sglebius * increase the counter by the headway increment. Note 382290001Sglebius * that we give a 1-s grace for the minimum threshold 383290001Sglebius * and a 2-s grace for the headway increment. If one or 384290001Sglebius * both thresholds are exceeded and the old counter is 385290001Sglebius * less than the average threshold, set the counter to 386290001Sglebius * the average threshold plus the increment and leave 387290001Sglebius * the RES_LIMITED and RES_KOD bits lit. Otherwise, 388290001Sglebius * leave the counter alone and douse the RES_KOD bit. 389290001Sglebius * This rate-limits the KoDs to no less than the average 390290001Sglebius * headway. 391290001Sglebius */ 392290001Sglebius if (interval + 1 >= ntp_minpkt && leak < limit) { 393290001Sglebius mon->leak = leak - 2; 394290001Sglebius restrict_mask &= ~(RES_LIMITED | RES_KOD); 395290001Sglebius } else if (mon->leak < limit) 396290001Sglebius mon->leak = limit + head; 397290001Sglebius else 398290001Sglebius restrict_mask &= ~RES_KOD; 399290001Sglebius 400290001Sglebius mon->flags = restrict_mask; 401290001Sglebius 402290001Sglebius return mon->flags; 40354359Sroberto } 40454359Sroberto 40554359Sroberto /* 40654359Sroberto * If we got here, this is the first we've heard of this 40754359Sroberto * guy. Get him some memory, either from the free list 40854359Sroberto * or from the tail of the MRU list. 409290001Sglebius * 410290001Sglebius * The following ntp.conf "mru" knobs come into play determining 411290001Sglebius * the depth (or count) of the MRU list: 412290001Sglebius * - mru_mindepth ("mru mindepth") is a floor beneath which 413290001Sglebius * entries are kept without regard to their age. The 414290001Sglebius * default is 600 which matches the longtime implementation 415290001Sglebius * limit on the total number of entries. 416290001Sglebius * - mru_maxage ("mru maxage") is a ceiling on the age in 417290001Sglebius * seconds of entries. Entries older than this are 418290001Sglebius * reclaimed once mon_mindepth is exceeded. 64s default. 419290001Sglebius * Note that entries older than this can easily survive 420290001Sglebius * as they are reclaimed only as needed. 421290001Sglebius * - mru_maxdepth ("mru maxdepth") is a hard limit on the 422290001Sglebius * number of entries. 423290001Sglebius * - "mru maxmem" sets mru_maxdepth to the number of entries 424290001Sglebius * which fit in the given number of kilobytes. The default is 425290001Sglebius * 1024, or 1 megabyte. 426290001Sglebius * - mru_initalloc ("mru initalloc" sets the count of the 427290001Sglebius * initial allocation of MRU entries. 428290001Sglebius * - "mru initmem" sets mru_initalloc in units of kilobytes. 429290001Sglebius * The default is 4. 430290001Sglebius * - mru_incalloc ("mru incalloc" sets the number of entries to 431290001Sglebius * allocate on-demand each time the free list is empty. 432290001Sglebius * - "mru incmem" sets mru_incalloc in units of kilobytes. 433290001Sglebius * The default is 4. 434290001Sglebius * Whichever of "mru maxmem" or "mru maxdepth" occurs last in 435290001Sglebius * ntp.conf controls. Similarly for "mru initalloc" and "mru 436290001Sglebius * initmem", and for "mru incalloc" and "mru incmem". 43754359Sroberto */ 438290001Sglebius if (mru_entries < mru_mindepth) { 439290001Sglebius if (NULL == mon_free) 440290001Sglebius mon_getmoremem(); 441290001Sglebius UNLINK_HEAD_SLIST(mon, mon_free, hash_next); 44254359Sroberto } else { 443290001Sglebius oldest = TAIL_DLIST(mon_mru_list, mru); 444290001Sglebius oldest_age = 0; /* silence uninit warning */ 445290001Sglebius if (oldest != NULL) { 446290001Sglebius interval_fp = rbufp->recv_time; 447290001Sglebius L_SUB(&interval_fp, &oldest->last); 448290001Sglebius /* add one-half second to round up */ 449290001Sglebius L_ADDUF(&interval_fp, 0x80000000); 450290001Sglebius oldest_age = interval_fp.l_i; 451290001Sglebius } 452290001Sglebius /* note -1 is legal for mru_maxage (disables) */ 453290001Sglebius if (oldest != NULL && mru_maxage < oldest_age) { 454290001Sglebius mon_reclaim_entry(oldest); 455290001Sglebius mon = oldest; 456290001Sglebius } else if (mon_free != NULL || mru_alloc < 457290001Sglebius mru_maxdepth) { 458290001Sglebius if (NULL == mon_free) 459290001Sglebius mon_getmoremem(); 460290001Sglebius UNLINK_HEAD_SLIST(mon, mon_free, hash_next); 461290001Sglebius /* Preempt from the MRU list if old enough. */ 462290001Sglebius } else if (ntp_random() / (2. * FRAC) > 463290001Sglebius (double)oldest_age / mon_age) { 464290001Sglebius return ~(RES_LIMITED | RES_KOD) & flags; 465290001Sglebius } else { 466290001Sglebius mon_reclaim_entry(oldest); 467290001Sglebius mon = oldest; 468290001Sglebius } 46954359Sroberto } 47054359Sroberto 471290001Sglebius INSIST(mon != NULL); 472290001Sglebius 47354359Sroberto /* 47454359Sroberto * Got one, initialize it 47554359Sroberto */ 476290001Sglebius mru_entries++; 477290001Sglebius mru_peakentries = max(mru_peakentries, mru_entries); 478290001Sglebius mon->last = rbufp->recv_time; 479290001Sglebius mon->first = mon->last; 480290001Sglebius mon->count = 1; 481290001Sglebius mon->flags = ~(RES_LIMITED | RES_KOD) & flags; 482290001Sglebius mon->leak = 0; 483290001Sglebius memcpy(&mon->rmtadr, &rbufp->recv_srcadr, sizeof(mon->rmtadr)); 484290001Sglebius mon->vn_mode = VN_MODE(version, mode); 485290001Sglebius mon->lcladr = rbufp->dstadr; 486290001Sglebius mon->cast_flags = (u_char)(((rbufp->dstadr->flags & 487290001Sglebius INT_MCASTOPEN) && rbufp->fd == mon->lcladr->fd) ? MDF_MCAST 488290001Sglebius : rbufp->fd == mon->lcladr->bfd ? MDF_BCAST : MDF_UCAST); 48954359Sroberto 49054359Sroberto /* 491132451Sroberto * Drop him into front of the hash table. Also put him on top of 492132451Sroberto * the MRU list. 49354359Sroberto */ 494290001Sglebius LINK_SLIST(mon_hash[hash], mon, hash_next); 495290001Sglebius LINK_DLIST(mon_mru_list, mon, mru); 49654359Sroberto 497290001Sglebius return mon->flags; 49854359Sroberto} 49954359Sroberto 50054359Sroberto 501