From: Brian Warner Date: Mon, 11 Jun 2012 02:10:22 +0000 (-0700) Subject: introducer: add sequence-numbers to announcements, ignore replays X-Git-Url: https://git.rkrishnan.org/?a=commitdiff_plain;h=e1093cbb335f5f893937e95fa684d2a7c868e09e;p=tahoe-lafs%2Ftahoe-lafs.git 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. --- diff --git a/src/allmydata/introducer/client.py b/src/allmydata/introducer/client.py index 260e13cf..33ce5ad8 100644 --- a/src/allmydata/introducer/client.py +++ b/src/allmydata/introducer/client.py @@ -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") diff --git a/src/allmydata/introducer/server.py b/src/allmydata/introducer/server.py index 394fdf00..1a4b7680 100644 --- a/src/allmydata/introducer/server.py +++ b/src/allmydata/introducer/server.py @@ -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 diff --git a/src/allmydata/test/test_introducer.py b/src/allmydata/test/test_introducer.py index 6624a15b..e8eca502 100644 --- a/src/allmydata/test/test_introducer.py +++ b/src/allmydata/test/test_introducer.py @@ -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()