mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-04-13 22:03:04 +00:00
introducer: add sequence-numbers to announcements, ignore replays
This will support revocation of Accounting recommendation records, assuming the gossip-based broadcast channel isn't easily jammed.
This commit is contained in:
parent
bf416af49e
commit
e1093cbb33
@ -201,8 +201,9 @@ class IntroducerClient(service.Service, Referenceable):
|
||||
d.addCallback(_publish_stub_client)
|
||||
return d
|
||||
|
||||
def create_announcement(self, service_name, ann, signing_key):
|
||||
def create_announcement(self, service_name, ann, signing_key, _mod=None):
|
||||
full_ann = { "version": 0,
|
||||
"seqnum": time.time(),
|
||||
"nickname": self._nickname,
|
||||
"app-versions": self._app_versions,
|
||||
"my-version": self._my_version,
|
||||
@ -211,6 +212,8 @@ class IntroducerClient(service.Service, Referenceable):
|
||||
"service-name": service_name,
|
||||
}
|
||||
full_ann.update(ann)
|
||||
if _mod:
|
||||
full_ann = _mod(full_ann) # for unit tests
|
||||
return sign_to_foolscap(full_ann, signing_key)
|
||||
|
||||
def publish(self, service_name, ann, signing_key=None):
|
||||
@ -303,8 +306,27 @@ class IntroducerClient(service.Service, Referenceable):
|
||||
parent=lp2, level=log.UNUSUAL, umid="B1MIdA")
|
||||
self._debug_counts["duplicate_announcement"] += 1
|
||||
return
|
||||
|
||||
# does it update an existing one?
|
||||
if index in self._current_announcements:
|
||||
old,_,_ = self._current_announcements[index]
|
||||
if "seqnum" in old:
|
||||
# must beat previous sequence number to replace
|
||||
if "seqnum" not in ann:
|
||||
self.log("not replacing old announcement, no seqnum: %s"
|
||||
% (ann,),
|
||||
parent=lp2, level=log.NOISY, umid="zFGH3Q")
|
||||
return
|
||||
if ann["seqnum"] <= old["seqnum"]:
|
||||
# note that exact replays are caught earlier, by
|
||||
# comparing the entire signed announcement.
|
||||
self.log("not replacing old announcement, "
|
||||
"new seqnum is too old (%s <= %s) "
|
||||
"(replay attack?): %s"
|
||||
% (ann["seqnum"], old["seqnum"], ann),
|
||||
parent=lp2, level=log.UNUSUAL, umid="JAAAoQ")
|
||||
return
|
||||
# ok, seqnum is newer, allow replacement
|
||||
self._debug_counts["update"] += 1
|
||||
self.log("replacing old announcement: %s" % (ann,),
|
||||
parent=lp2, level=log.NOISY, umid="wxwgIQ")
|
||||
|
@ -120,6 +120,8 @@ class IntroducerService(service.MultiService, Referenceable):
|
||||
|
||||
self._debug_counts = {"inbound_message": 0,
|
||||
"inbound_duplicate": 0,
|
||||
"inbound_no_seqnum": 0,
|
||||
"inbound_old_replay": 0,
|
||||
"inbound_update": 0,
|
||||
"outbound_message": 0,
|
||||
"outbound_announcements": 0,
|
||||
@ -216,6 +218,21 @@ class IntroducerService(service.MultiService, Referenceable):
|
||||
self._debug_counts["inbound_duplicate"] += 1
|
||||
return
|
||||
else:
|
||||
if "seqnum" in old_ann:
|
||||
# must beat previous sequence number to replace
|
||||
if "seqnum" not in ann:
|
||||
self.log("not replacing old ann, no seqnum",
|
||||
level=log.NOISY, umid="ySbaVw")
|
||||
self._debug_counts["inbound_no_seqnum"] += 1
|
||||
return
|
||||
if ann["seqnum"] <= old_ann["seqnum"]:
|
||||
self.log("not replacing old ann, new seqnum is too old"
|
||||
" (%s <= %s) (replay attack?)"
|
||||
% (ann["seqnum"], old_ann["seqnum"]),
|
||||
level=log.UNUSUAL, umid="sX7yqQ")
|
||||
self._debug_counts["inbound_old_replay"] += 1
|
||||
return
|
||||
# ok, seqnum is newer, allow replacement
|
||||
self.log("old announcement being updated", level=log.NOISY,
|
||||
umid="304r9g")
|
||||
self._debug_counts["inbound_update"] += 1
|
||||
|
@ -117,8 +117,13 @@ def make_ann(furl):
|
||||
"permutation-seed-base32": get_tubid_string(furl) }
|
||||
return ann
|
||||
|
||||
def make_ann_t(ic, furl, privkey):
|
||||
return ic.create_announcement("storage", make_ann(furl), privkey)
|
||||
def make_ann_t(ic, furl, privkey, seqnum):
|
||||
def mod(ann):
|
||||
ann["seqnum"] = seqnum
|
||||
if seqnum is None:
|
||||
del ann["seqnum"]
|
||||
return ann
|
||||
return ic.create_announcement("storage", make_ann(furl), privkey, mod)
|
||||
|
||||
class Client(unittest.TestCase):
|
||||
def test_duplicate_receive_v1(self):
|
||||
@ -196,10 +201,12 @@ class Client(unittest.TestCase):
|
||||
# ann1b: ic2, furl1
|
||||
# ann2: ic2, furl2
|
||||
|
||||
self.ann1 = make_ann_t(ic1, furl1, privkey)
|
||||
self.ann1a = make_ann_t(ic1, furl1a, privkey)
|
||||
self.ann1b = make_ann_t(ic2, furl1, privkey)
|
||||
self.ann2 = make_ann_t(ic2, furl2, privkey)
|
||||
self.ann1 = make_ann_t(ic1, furl1, privkey, seqnum=10)
|
||||
self.ann1old = make_ann_t(ic1, furl1, privkey, seqnum=9)
|
||||
self.ann1noseqnum = make_ann_t(ic1, furl1, privkey, seqnum=None)
|
||||
self.ann1b = make_ann_t(ic2, furl1, privkey, seqnum=11)
|
||||
self.ann1a = make_ann_t(ic1, furl1a, privkey, seqnum=12)
|
||||
self.ann2 = make_ann_t(ic2, furl2, privkey, seqnum=13)
|
||||
|
||||
ic1.remote_announce_v2([self.ann1]) # queues eventual-send
|
||||
d = fireEventually()
|
||||
@ -219,6 +226,20 @@ class Client(unittest.TestCase):
|
||||
self.failUnlessEqual(len(announcements), 1)
|
||||
d.addCallback(_then2)
|
||||
|
||||
# an older announcement shouldn't fire the subscriber either
|
||||
d.addCallback(lambda ign: ic1.remote_announce_v2([self.ann1old]))
|
||||
d.addCallback(fireEventually)
|
||||
def _then2a(ign):
|
||||
self.failUnlessEqual(len(announcements), 1)
|
||||
d.addCallback(_then2a)
|
||||
|
||||
# announcement with no seqnum cannot replace one with-seqnum
|
||||
d.addCallback(lambda ign: ic1.remote_announce_v2([self.ann1noseqnum]))
|
||||
d.addCallback(fireEventually)
|
||||
def _then2b(ign):
|
||||
self.failUnlessEqual(len(announcements), 1)
|
||||
d.addCallback(_then2b)
|
||||
|
||||
# and a replacement announcement: same FURL, new other stuff. The
|
||||
# subscriber *should* be fired.
|
||||
d.addCallback(lambda ign: ic1.remote_announce_v2([self.ann1b]))
|
||||
@ -298,6 +319,73 @@ class Client(unittest.TestCase):
|
||||
d.addCallback(_then2)
|
||||
return d
|
||||
|
||||
class Server(unittest.TestCase):
|
||||
def test_duplicate(self):
|
||||
i = IntroducerService()
|
||||
ic1 = IntroducerClient(None,
|
||||
"introducer.furl", u"my_nickname",
|
||||
"ver23", "oldest_version", {})
|
||||
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp"
|
||||
|
||||
privkey_s, _ = keyutil.make_keypair()
|
||||
privkey, _ = keyutil.parse_privkey(privkey_s)
|
||||
|
||||
ann1 = make_ann_t(ic1, furl1, privkey, seqnum=10)
|
||||
ann1_old = make_ann_t(ic1, furl1, privkey, seqnum=9)
|
||||
ann1_new = make_ann_t(ic1, furl1, privkey, seqnum=11)
|
||||
ann1_noseqnum = make_ann_t(ic1, furl1, privkey, seqnum=None)
|
||||
|
||||
i.remote_publish_v2(ann1, None)
|
||||
all = i.get_announcements()
|
||||
self.failUnlessEqual(len(all), 1)
|
||||
self.failUnlessEqual(all[0].announcement["seqnum"], 10)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_message"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_duplicate"], 0)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_no_seqnum"], 0)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 0)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_update"], 0)
|
||||
|
||||
i.remote_publish_v2(ann1, None)
|
||||
all = i.get_announcements()
|
||||
self.failUnlessEqual(len(all), 1)
|
||||
self.failUnlessEqual(all[0].announcement["seqnum"], 10)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_message"], 2)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_duplicate"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_no_seqnum"], 0)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 0)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_update"], 0)
|
||||
|
||||
i.remote_publish_v2(ann1_old, None)
|
||||
all = i.get_announcements()
|
||||
self.failUnlessEqual(len(all), 1)
|
||||
self.failUnlessEqual(all[0].announcement["seqnum"], 10)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_message"], 3)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_duplicate"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_no_seqnum"], 0)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_update"], 0)
|
||||
|
||||
i.remote_publish_v2(ann1_new, None)
|
||||
all = i.get_announcements()
|
||||
self.failUnlessEqual(len(all), 1)
|
||||
self.failUnlessEqual(all[0].announcement["seqnum"], 11)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_message"], 4)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_duplicate"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_no_seqnum"], 0)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_update"], 1)
|
||||
|
||||
i.remote_publish_v2(ann1_noseqnum, None)
|
||||
all = i.get_announcements()
|
||||
self.failUnlessEqual(len(all), 1)
|
||||
self.failUnlessEqual(all[0].announcement["seqnum"], 11)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_message"], 5)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_duplicate"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_no_seqnum"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 1)
|
||||
self.failUnlessEqual(i._debug_counts["inbound_update"], 1)
|
||||
|
||||
|
||||
NICKNAME = u"n\u00EDickname-%s" # LATIN SMALL LETTER I WITH ACUTE
|
||||
|
||||
class SystemTestMixin(ServiceMixin, pollmixin.PollMixin):
|
||||
@ -736,7 +824,7 @@ class ClientInfo(unittest.TestCase):
|
||||
client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2",
|
||||
"my_version", "oldest", app_versions)
|
||||
#furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
|
||||
#ann_s = make_ann_t(client_v2, furl1, None)
|
||||
#ann_s = make_ann_t(client_v2, furl1, None, 10)
|
||||
#introducer.remote_publish_v2(ann_s, Referenceable())
|
||||
subscriber = FakeRemoteReference()
|
||||
introducer.remote_subscribe_v2(subscriber, "storage",
|
||||
@ -798,7 +886,7 @@ class Announcements(unittest.TestCase):
|
||||
"my_version", "oldest", app_versions)
|
||||
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
|
||||
tubid = "62ubehyunnyhzs7r6vdonnm2hpi52w6y"
|
||||
ann_s0 = make_ann_t(client_v2, furl1, None)
|
||||
ann_s0 = make_ann_t(client_v2, furl1, None, 10.0)
|
||||
canary0 = Referenceable()
|
||||
introducer.remote_publish_v2(ann_s0, canary0)
|
||||
a = introducer.get_announcements()
|
||||
@ -821,7 +909,7 @@ class Announcements(unittest.TestCase):
|
||||
sk_s, vk_s = keyutil.make_keypair()
|
||||
sk, _ignored = keyutil.parse_privkey(sk_s)
|
||||
pks = keyutil.remove_prefix(vk_s, "pub-")
|
||||
ann_t0 = make_ann_t(client_v2, furl1, sk)
|
||||
ann_t0 = make_ann_t(client_v2, furl1, sk, 10.0)
|
||||
canary0 = Referenceable()
|
||||
introducer.remote_publish_v2(ann_t0, canary0)
|
||||
a = introducer.get_announcements()
|
||||
|
Loading…
x
Reference in New Issue
Block a user