[Git][NTPsec/ntpsec][master] 6 commits: Rate limiting cleanup
Hal Murray
gitlab at mg.gitlab.com
Fri Mar 13 18:28:21 UTC 2020
Hal Murray pushed to branch master at NTPsec / ntpsec
Commits:
4ac74571 by Hal Murray at 2020-03-09T21:02:45-07:00
Rate limiting cleanup
The old rate limiting scheme was carefully tuned to work with
a single copy of the same code. That doesn't work if you have
several clients behind a NAT box.
That chunk of code now uses a simple exponential decay (aka
leaky bucket) to keep track of recent traffic. The time constant
is 20 seconds and the rate limit is 1 packet per second. There is
no config option to change them. (yet?) The "score" is scaled
such that the units are packets/second.
That pair of parameters means that a client can start with a burst
of 20 packets but over the long term, but can only sustain 1 packet
per second.
ntpq/mru now displays the score and the number of packets
from that IP Address that were dropped.
- - - - -
4cbab792 by Hal Murray at 2020-03-10T19:19:44-07:00
seccomp: Add comment about systemd to catchTrap
- - - - -
6fa76a4d by Hal Murray at 2020-03-12T16:31:20-07:00
Add KoD limiting
- - - - -
0093b02b by Hal Murray at 2020-03-12T18:20:41-07:00
Add sorting by score and drop to ntpq/mrulist
- - - - -
0e0e4db4 by Hal Murray at 2020-03-12T23:36:19-07:00
Add filtering by mindrop and minscore to ntpq/mrulist
- - - - -
f67f8bf1 by Hal Murray at 2020-03-13T02:04:25-07:00
Add minlstint and doc for maxlstint for ntpq/mrulist
- - - - -
11 changed files:
- docs/includes/mrufmt.adoc
- docs/includes/ntpq-body.adoc
- docs/mode6.adoc
- include/ntp.h
- ntpd/ntp_control.c
- ntpd/ntp_monitor.c
- ntpd/ntp_sandbox.c
- pylib/packet.py
- pylib/util.py
- tests/pylib/test_packet.py
- tests/pylib/test_util.py
Changes:
=====================================
docs/includes/mrufmt.adoc
=====================================
@@ -19,6 +19,8 @@ KoD response, respectively.
|+m+ |Packet mode.
|+v+ |Packet version number.
|+count+ |Packets received from this address.
+|+score+ |Packets per second with 20 second decay time.
+|+drop+ |Packets dropped (or KoDed) from this address.
|+rport+ |Source port of last packet from this address.
|+remote address+|
DNS name, numeric address, or address followed by claimed DNS name
=====================================
docs/includes/ntpq-body.adoc
=====================================
@@ -295,7 +295,7 @@ ind assid status conf reach auth condition last_event cnt
server so loaded that none of its MRU entries age out before they
are shipped. With this option, each segment is reported as it arrives.
-+mrulist+ [+limited+ | +kod+ | +mincount=+'count' | +laddr=+'localaddr' | +sort=+'sortorder' | +resany=+'hexmask' | +resall=+'hexmask']::
++mrulist+ [+limited+ | +kod+ | +mincount=+'count' | +mindrop=+'drop' | +minscore=+'score' | +maxlstint=+'seconds' | +minlstint=+'seconds' | +laddr=+'localaddr' | +sort=+'sortorder' | +resany=+'hexmask' | +resall=+'hexmask']::
Obtain and print traffic counts collected and maintained by the
monitor facility. This is useful for tracking who _uses_ or
_abuses_ your server.
@@ -303,16 +303,26 @@ ind assid status conf reach auth condition last_event cnt
With the exception of +sort=+'sortorder', the options
filter the list returned by +ntpd+. The +limited+ and +kod+ options
return only entries representing client addresses from which the last
-packet received triggered either discarding or a KoD response. The
-+mincount=+'count' option filters entries representing less than 'count'
-packets. The +laddr=+'localaddr' option filters entries for packets
+packet received triggered either discarding or a KoD response.
+The +mincount=+'count' option filters entries that have received less
+than 'count' packets.
+The +mindrop=+'drop' option filters entries that have dropped less
+than 'drop' packets.
+The +minscore=+'score' option filters entries with a score less
+than 'score'.
+The +maxlstint=+'seconds' option filters entries where no packets have
+arrived within 'seconds'.
+The +minlstint=+'seconds' option filters entries with a packet has
+arrived within 'seconds'.
+The +laddr=+'localaddr' option filters entries for packets
received on any local address other than 'localaddr'. +resany=+'hexmask'
and +resall=+'hexmask' filter entries containing none or less than all,
respectively, of the bits in 'hexmask', which must begin with +0x+.
+
The _sortorder_ defaults to +lstint+ and may be any of +addr+,
-+count+, +avgint+, +lstint+, or any of those preceded by a minus sign
-(hyphen) to reverse the sort order. The output columns are:
++count+, +avgint+, +lstint+, +score+, +drop+ or any of those
+preceded by a minus sign (hyphen) to reverse the sort order.
+The output columns are:
+
include::mrufmt.adoc[]
=====================================
docs/mode6.adoc
=====================================
@@ -302,7 +302,14 @@ limit:: Limit on MRU entries returned. One of frags= or limit=
mincount:: (decimal) Return entries with packet count >= mincount.
-maxlstint:: (decimal) Return entries with lstint <= maxlstint.
+mindrop:: (decimal) Return entries with drop count >= mindrop.
+
+minscore:: (float) Return entries with score >= minscore.
+
+maxlstint:: (decimal seconds) Return entries with lstint <= maxlstint.
+ (lstint is now-time of most recent packet)
+
+minlstint:: (decimal seconds) Return entries with lstint >= minlstint.
(lstint is now-time of most recent packet)
laddr:: Return entries associated with the server's IP
=====================================
include/ntp.h
=====================================
@@ -613,8 +613,9 @@ struct mon_data {
endpt * lcladr; /* address on which this arrived */
l_fp first; /* first time seen */
l_fp last; /* last time seen */
- int leak; /* leaky bucket accumulator */
int count; /* total packet count */
+ unsigned int dropped; /* packets dropped */
+ float score; /* recent packets/second */
unsigned short flags; /* restrict flags */
uint8_t vn_mode; /* packet mode & version */
sockaddr_u rmtadr; /* address of remote host */
=====================================
ntpd/ntp_control.c
=====================================
@@ -3227,8 +3227,10 @@ send_mru_entry(
const char ct_fmt[] = "ct.%d";
const char mv_fmt[] = "mv.%d";
const char rs_fmt[] = "rs.%d";
+ const char sc_fmt[] = "sc.%d";
+ const char dr_fmt[] = "dr.%d";
char tag[32];
- bool sent[6]; /* 6 tag=value pairs */
+ bool sent[8]; /* 8 tag=value pairs */
uint32_t noise;
unsigned int which = 0;
unsigned int remaining;
@@ -3278,6 +3280,16 @@ send_mru_entry(
ctl_puthex(tag, mon->flags);
break;
+ case 6:
+ snprintf(tag, sizeof(tag), sc_fmt, count);
+ ctl_putdblf(tag, true, 3, mon->score);
+ break;
+
+ case 7:
+ snprintf(tag, sizeof(tag), dr_fmt, count);
+ ctl_putuint(tag, mon->dropped);
+ break;
+
default:
/* huh? */
break;
@@ -3331,6 +3343,8 @@ send_mru_entry(
* address. When limit is not one and frags= is
* provided, the fragment limit controls.
* mincount= (decimal) Return entries with count >= mincount.
+ * mindrop= (decimal) Return entries with drop >= mindrop.
+ * minscore= (float) Return entries with score >= minscore.
* laddr= Return entries associated with the server's IP
* address given. No port specification is needed,
* and any supplied is ignored.
@@ -3414,9 +3428,12 @@ static void read_mru_list(
static const char frags_text[] = "frags";
static const char limit_text[] = "limit";
static const char mincount_text[] = "mincount";
+ static const char mindrop_text[] = "mindrop";
+ static const char minscore_text[] = "minscore";
static const char resall_text[] = "resall";
static const char resany_text[] = "resany";
static const char maxlstint_text[] = "maxlstint";
+ static const char minlstint_text[] = "minlstint";
static const char laddr_text[] = "laddr";
static const char recent_text[] = "recent";
static const char resaxx_fmt[] = "0x%hx";
@@ -3426,7 +3443,10 @@ static void read_mru_list(
unsigned short resall;
unsigned short resany;
int mincount;
+ unsigned int mindrop;
+ float minscore;
unsigned int maxlstint;
+ unsigned int minlstint;
sockaddr_u laddr;
unsigned int recent;
endpt * lcladr;
@@ -3467,9 +3487,12 @@ static void read_mru_list(
set_var(&in_parms, frags_text, sizeof(frags_text), 0);
set_var(&in_parms, limit_text, sizeof(limit_text), 0);
set_var(&in_parms, mincount_text, sizeof(mincount_text), 0);
+ set_var(&in_parms, mindrop_text, sizeof(mindrop_text), 0);
+ set_var(&in_parms, minscore_text, sizeof(minscore_text), 0);
set_var(&in_parms, resall_text, sizeof(resall_text), 0);
set_var(&in_parms, resany_text, sizeof(resany_text), 0);
set_var(&in_parms, maxlstint_text, sizeof(maxlstint_text), 0);
+ set_var(&in_parms, minlstint_text, sizeof(minlstint_text), 0);
set_var(&in_parms, laddr_text, sizeof(laddr_text), 0);
set_var(&in_parms, recent_text, sizeof(recent_text), 0);
for (i = 0; i < COUNTOF(last); i++) {
@@ -3484,9 +3507,12 @@ static void read_mru_list(
frags = 0;
limit = 0;
mincount = 0;
+ mindrop = 0;
+ minscore = 0.0;
resall = 0;
resany = 0;
maxlstint = 0;
+ minlstint = 0;
recent = 0;
lcladr = NULL;
priors = 0;
@@ -3517,6 +3543,14 @@ static void read_mru_list(
goto blooper;
if (mincount < 0)
mincount = 0;
+ } else if (!strcmp(mindrop_text, v->text)) {
+ if (1 != sscanf(val, "%u", &mindrop))
+ goto blooper;
+ } else if (!strcmp(minscore_text, v->text)) {
+ if (1 != sscanf(val, "%f", &minscore))
+ goto blooper;
+ if (minscore < 0)
+ minscore = 0.0;
} else if (!strcmp(resall_text, v->text)) {
if (1 != sscanf(val, resaxx_fmt, &resall))
goto blooper;
@@ -3526,6 +3560,9 @@ static void read_mru_list(
} else if (!strcmp(maxlstint_text, v->text)) {
if (1 != sscanf(val, "%u", &maxlstint))
goto blooper;
+ } else if (!strcmp(minlstint_text, v->text)) {
+ if (1 != sscanf(val, "%u", &minlstint))
+ goto blooper;
} else if (!strcmp(laddr_text, v->text)) {
if (decodenetnum(val, &laddr))
goto blooper;
@@ -3639,6 +3676,10 @@ static void read_mru_list(
if (mon->count < mincount)
continue;
+ if (mon->dropped < mindrop)
+ continue;
+ if (mon->score < minscore)
+ continue;
if (resall && resall != (resall & mon->flags))
continue;
if (resany && !(resany & mon->flags))
@@ -3646,6 +3687,9 @@ static void read_mru_list(
if (maxlstint > 0 && lfpuint(now) - lfpuint(mon->last) >
maxlstint)
continue;
+ if (minlstint > 0 && lfpuint(now) - lfpuint(mon->last) <
+ minlstint)
+ continue;
if (lcladr != NULL && mon->lcladr != lcladr)
continue;
if (recent != 0 && countdown-- > recent)
=====================================
ntpd/ntp_monitor.c
=====================================
@@ -4,6 +4,9 @@
#include "config.h"
+#include <math.h>
+#include <stdlib.h>
+
#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_lists.h"
@@ -54,14 +57,14 @@ struct monitor_data mon_data = {
.mru_maxage = 3600, /* recycle if older than this */
.mru_minage = 64, /* recycle if full and older than this */
.mru_maxdepth = MRU_MAXDEPTH_DEF, /* MRU count hard limit */
- .mon_age = 3000, /* preemption limit */
.mru_initalloc = INIT_MONLIST, /* entries to preallocate */
.mru_incalloc = INC_MONLIST, /* allocation batch factor */
.mru_exists = 0, /* slot already exists */
.mru_new = 0, /* allocate a new slot (2 cases) */
.mru_recycleold = 0, /* recycle slot: age > mru_maxage */
.mru_recyclefull = 0, /* recycle slot: full and age > mru_minage */
- .mru_none = 0 /* couldn't get one */
+ .mru_none = 0, /* couldn't get one */
+ .mon_age = 3000 /* preemption limit */
};
/*
@@ -77,6 +80,12 @@ static void remove_from_hash(mon_entry *);
static void mon_free_entry(mon_entry *);
static void mon_reclaim_entry(mon_entry *);
+/* Rate limiting */
+float rate_limit = 1.0; /* packets per second */
+float kod_limit = 0.1; /* KOD per second - see comments below */
+float decay_time = 20; /* seconds, exponential decay time */
+
+
/*
* init_mon - initialize monitoring global data
*/
@@ -319,11 +328,8 @@ ntp_monitor(
unsigned short restrict_mask;
uint8_t mode;
uint8_t version;
- uint8_t li_vn_mode;
- int interval;
- int head; /* headway increment */
- int leak; /* new headway */
- int limit; /* average threshold */
+ uint8_t li_vn_mode;
+ float since_last; /* seconds since last packet */
if (mon_data.mon_enabled == MON_OFF)
return ~(RES_LIMITED | RES_KOD) & flags;
@@ -346,9 +352,6 @@ ntp_monitor(
mon_data.mru_exists++;
interval_fp = rbufp->recv_time;
interval_fp -= mon->last;
- /* add one-half second to round up */
- interval_fp += 0x80000000;
- interval = lfpsint(interval_fp);
mon->last = rbufp->recv_time;
NSRCPORT(&mon->rmtadr) = NSRCPORT(&rbufp->recv_srcadr);
mon->count++;
@@ -359,44 +362,35 @@ ntp_monitor(
UNLINK_DLIST(mon, mru);
LINK_DLIST(mon_data.mon_mru_list, mon, mru);
- /*
- * At this point the most recent arrival is first in the
- * MRU list. Decrease the counter by the headway, but
- * not less than zero.
- */
- mon->leak -= interval;
- mon->leak = max(0, mon->leak);
- head = 1 << rstrct.ntp_minpoll;
- leak = mon->leak + head;
- limit = NTP_SHIFT * head;
-
- DPRINT(2, ("MRU: interval %d headway %d limit %d\n",
- interval, leak, limit));
-
- /*
- * If the minimum and average thresholds are not
- * exceeded, douse the RES_LIMITED and RES_KOD bits and
- * increase the counter by the headway increment. Note
- * that we give a 1-s grace for the minimum threshold
- * and a 2-s grace for the headway increment. If one or
- * both thresholds are exceeded and the old counter is
- * less than the average threshold, set the counter to
- * the average threshold plus the increment and leave
- * the RES_LIMITED and RES_KOD bits lit. Otherwise,
- * leave the counter alone and douse the RES_KOD bit.
- * This rate-limits the KoDs to no less than the average
- * headway.
+ /* Keep score:
+ * if packets arrive at 1/second,
+ * score will build up to (almost) 1.0
*/
- if (interval + 1 >= rstrct.ntp_minpkt && leak < limit) {
- mon->leak = leak - 2;
+ since_last = ldexpf(interval_fp, -32);
+ mon->score *= expf(-since_last/decay_time);
+ mon->score += 1.0/decay_time;
+ if (mon->score < rate_limit) {
+ /* low score, turn off reject bits */
restrict_mask &= ~(RES_LIMITED | RES_KOD);
- } else if (mon->leak < limit)
- mon->leak = limit + head;
- else
- restrict_mask &= ~RES_KOD;
+ }
- mon->flags = restrict_mask;
+ if (RES_LIMITED & restrict_mask)
+ mon->dropped++;
+ if (RES_KOD & restrict_mask) {
+ /* We need rate limiting on KoD too.
+ * Note that DDoS attackers often send
+ * >1000 packets/second. A simple fraction
+ * would turn into lots of KoDs.
+ * So we try 1/score-squared.
+ * kod_limit is roughly packets/second when
+ * score is close to 1.
+ */
+ float rand = random()*1.0/RAND_MAX;
+ if (rand > kod_limit/(mon->score*mon->score))
+ restrict_mask &= ~RES_KOD;
+ }
+ mon->flags = restrict_mask;
return mon->flags;
}
@@ -471,8 +465,9 @@ ntp_monitor(
mon->last = rbufp->recv_time;
mon->first = mon->last;
mon->count = 1;
+ mon->dropped = 0;
+ mon->score = 1.0/decay_time;
mon->flags = ~(RES_LIMITED | RES_KOD) & flags;
- mon->leak = 0;
memcpy(&mon->rmtadr, &rbufp->recv_srcadr, sizeof(mon->rmtadr));
mon->vn_mode = VN_MODE(version, mode);
mon->lcladr = rbufp->dstadr;
=====================================
ntpd/ntp_sandbox.c
=====================================
@@ -493,6 +493,11 @@ int scmp_sc[] = {
* You will get several hits for various architures/modes.
* You can probably guess the right one.
*
+ * If this trap doesn't work, systemd may print out the
+ * critical info. Look for something like this:
+ * Main process exited, code=killed, status=31/SYS
+ * See above for decoding that number.
+ *
* Option two:
* use strace
* sudo strace -t -f -o<filename> <path-to-ntpd> <args>
=====================================
pylib/packet.py
=====================================
@@ -637,9 +637,11 @@ class MRUEntry:
self.addr = None # text of IPv4 or IPv6 address and port
self.last = None # timestamp of last receipt
self.first = None # timestamp of first receipt
- self.ct = 0 # count of packets received
self.mv = None # mode and version
self.rs = None # restriction mask (RES_* bits)
+ self.ct = 0 # count of packets received
+ self.sc = None # score
+ self.dr = None # dropped packets
def avgint(self):
last = ntp.ntpc.lfptofloat(self.last)
@@ -1329,7 +1331,7 @@ This combats source address spoofing
elif tag == "last.newest":
# more finished
continue
- for prefix in ("addr", "last", "first", "ct", "mv", "rs"):
+ for prefix in ("addr", "last", "first", "ct", "mv", "rs", "sc", "dr"):
if tag.startswith(prefix + "."):
(member, idx) = tag.split(".")
try:
@@ -1344,10 +1346,11 @@ This combats source address spoofing
fake_list.sort()
for idx in fake_list:
mru = MRUEntry()
+ self.slots += 1
# Always 6 in practice, in the tests not so much
# if len(fake_dict[str(idx)]) != 6:
# continue
- for prefix in ("addr", "last", "first", "ct", "mv", "rs"):
+ for prefix in ("addr", "last", "first", "ct", "mv", "rs", "sc", "dr"):
if prefix in fake_dict[str(idx)]: # dodgy test needs this line
setattr(mru, prefix, fake_dict[str(idx)][prefix])
span.entries.append(mru)
@@ -1550,10 +1553,18 @@ def parse_mru_variables(variables):
"addr": lambda e: e.sortaddr(),
# IPv6 desc. then IPv4 desc.
"-addr": lambda e: e.sortaddr(),
- # hit count ascending
+ # hit count ascending
"count": lambda e: -e.ct,
# hit count descending
"-count": lambda e: e.ct,
+ # score ascending
+ "score": lambda e: -e.sc,
+ # score descending
+ "-score": lambda e: e.sc,
+ # drop count ascending
+ "drop": lambda e: -e.dr,
+ # drop count descending
+ "-drop": lambda e: e.dr,
}
if sortkey == "lstint":
sortkey = None # normal/default case, no need to sort
@@ -1562,8 +1573,10 @@ def parse_mru_variables(variables):
if sorter is None:
raise ControlException(SERR_BADSORT % sortkey)
for k in list(variables.keys()):
- if k in ("mincount", "resall", "resany", "kod", "limited",
- "maxlstint", "laddr", "recent", "sort", "frags", "limit"):
+ if k in ("mincount", "mindrop", "minscore",
+ "resall", "resany", "kod", "limited",
+ "maxlstint", "minlstint", "laddr", "recent",
+ "sort", "frags", "limit"):
continue
else:
raise ControlException(SERR_BADPARAM % k)
=====================================
pylib/util.py
=====================================
@@ -1235,7 +1235,7 @@ class MRUSummary:
self.showhostnames = showhostnames # If false, display numeric IPs
self.wideremote = wideremote
- header = " lstint avgint rstr r m v count rport remote address"
+ header = " lstint avgint rstr r m v count score drop rport remote address"
def summary(self, entry):
last = ntp.ntpc.lfptofloat(entry.last)
@@ -1290,11 +1290,25 @@ class MRUSummary:
if not self.wideremote:
# truncate for narrow display
dns = dns[:40]
- stats += " %4hx %c %d %d %6d %5s %s" % \
+ if entry.sc:
+ score = float(entry.sc)
+ if score > 100000.0:
+ score = "%8.1f" % score
+ elif score > 10000.0:
+ score = "%8.2f" % score
+ else:
+ score = "%8.3f" % score
+ else:
+ score = "-"
+ if entry.dr!= None: # 0 is valid
+ drop = "%4d" % entry.dr
+ else:
+ drop = "-"
+ stats += " %4hx %c %d %d %6d %8s %6s %5s %s" % \
(entry.rs, rscode,
ntp.magic.PKT_MODE(entry.mv),
ntp.magic.PKT_VERSION(entry.mv),
- entry.ct, port[1:], dns)
+ entry.ct, score, drop, port[1:], dns)
return stats
except ValueError:
# This can happen when ntpd ships a corrupt varlist
=====================================
tests/pylib/test_packet.py
=====================================
@@ -599,6 +599,7 @@ class TestMisc(unittest.TestCase):
cls.last = "0x00000200.00000000"
cls.first = "0x00000100.00000000"
cls.ct = 4
+ cls.sc = "0.12345"
self.assertEqual(cls.avgint(), 64)
if socket.has_ipv6:
# Test sortaddr, ipv6
@@ -624,15 +625,17 @@ class TestMisc(unittest.TestCase):
"<MRUEntry: "
"'last': '0x00000200.00000000', "
"'addr': '11.22.33.44:23', 'rs': None, "
- "'mv': None, 'first': '0x00000100.00000000', "
- "'ct': 4>")
+ "'mv': None, 'sc': '0.12345', "
+ "'first': '0x00000100.00000000', "
+ "'dr': None, 'ct': 4>")
elif sys.version_info[1] >= 6: # Already know it is 3.something
# Python 3.6+, dicts enumerate in assignment order
self.assertEqual(cls.__repr__(),
"<MRUEntry: 'addr': '11.22.33.44:23', "
"'last': '0x00000200.00000000', "
- "'first': '0x00000100.00000000', 'ct': 4, "
- "'mv': None, 'rs': None>")
+ "'first': '0x00000100.00000000', "
+ "'mv': None, 'rs': None, 'ct': 4, "
+ "'sc': '0.12345', 'dr': None>")
else:
# Python 3.x < 3.6, dicts enumerate randomly
# I can not test randomness of this type
=====================================
tests/pylib/test_util.py
=====================================
@@ -939,7 +939,7 @@ class TestPylibUtilMethods(unittest.TestCase):
"foo.bar.com", "1.2.3.4")]
self.assertEqual(cls.summary(ent),
"64730 23296 0 400 K 7 2"
- " 1 42 1.2.3.4")
+ " 1 - - 42 1.2.3.4")
# Test summary, second options
mycache._cache = {}
cls.now = 0x00000000
@@ -953,7 +953,7 @@ class TestPylibUtilMethods(unittest.TestCase):
cdns_jig_returns = ["foo.com"]
self.assertEqual(cls.summary(ent),
"64730 23808 4.00 20 L 7 2 65"
- " 42 foo.com")
+ " - - 42 foo.com")
# Test summary, third options
mycache._cache = {}
ent.ct = 2
@@ -961,7 +961,7 @@ class TestPylibUtilMethods(unittest.TestCase):
fakesockmod.gai_error_count = 1
cdns_jig_returns = ["foobarbaz" * 5] # 45 chars, will be cropped
self.assertEqual(cls.summary(ent),
- "64730 23808 256 0 . 7 2 2 42"
+ "64730 23808 256 0 . 7 2 2 - - 42"
" 1.2.3.4 (foobarbazfoobarbazfoobarbazfoob")
# Test summary, wide
mycache._cache = {}
@@ -970,7 +970,7 @@ class TestPylibUtilMethods(unittest.TestCase):
cdns_jig_returns = ["foobarbaz" * 5] # 45 chars, will be cropped
self.assertEqual(cls.summary(ent),
"64730 23808 256 0 . 7 2 2"
- " 42 1.2.3.4 "
+ " - - 42 1.2.3.4 "
"(foobarbazfoobarbazfoobarbazfoobarbazfoobarbaz)")
finally:
ntp.util.socket = socktemp
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/-/compare/51f41df59751ce28aab12e999ba3e6472e573756...f67f8bf19f58e8736a7882eab03003bb1ed96a41
--
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/-/compare/51f41df59751ce28aab12e999ba3e6472e573756...f67f8bf19f58e8736a7882eab03003bb1ed96a41
You're receiving this email because of your account on gitlab.com.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20200313/8a329c14/attachment-0001.htm>
More information about the vc
mailing list