def __init__(self, tub, introducer_furl,
nickname, my_version, oldest_supported,
- app_versions):
+ app_versions, sequencer):
self._tub = tub
self.introducer_furl = introducer_furl
self._my_version = my_version
self._oldest_supported = oldest_supported
self._app_versions = app_versions
+ self._sequencer = sequencer
self._my_subscriber_info = { "version": 0,
"nickname": self._nickname,
self._stub_client = None # for_v1
self._stub_client_furl = None
- self._published_announcements = {}
+ self._outbound_announcements = {} # not signed
+ self._published_announcements = {} # signed
self._canary = Referenceable()
self._publisher = None
self._subscribed_service_names = set()
self._subscriptions = set() # requests we've actually sent
- # _current_announcements remembers one announcement per
+ # _inbound_announcements remembers one announcement per
# (servicename,serverid) pair. Anything that arrives with the same
# pair will displace the previous one. This stores tuples of
# (unpacked announcement dictionary, verifyingkey, rxtime). The ann
# dicts can be compared for equality to distinguish re-announcement
# from updates. It also provides memory for clients who subscribe
# after startup.
- self._current_announcements = {}
+ self._inbound_announcements = {}
self.encoding_parameters = None
self._local_subscribers.append( (service_name,cb,args,kwargs) )
self._subscribed_service_names.add(service_name)
self._maybe_subscribe()
- for index,(ann,key_s,when) in self._current_announcements.items():
+ for index,(ann,key_s,when) in self._inbound_announcements.items():
servicename = index[0]
if servicename == service_name:
eventually(cb, key_s, ann, *args, **kwargs)
d.addCallback(_publish_stub_client)
return d
- 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,
- "oldest-supported": self._oldest_supported,
-
- "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 create_announcement_dict(self, service_name, ann):
+ ann_d = { "version": 0,
+ # "seqnum" and "nonce" will be populated with new values in
+ # publish(), each time we make a change
+ "nickname": self._nickname,
+ "app-versions": self._app_versions,
+ "my-version": self._my_version,
+ "oldest-supported": self._oldest_supported,
+
+ "service-name": service_name,
+ }
+ ann_d.update(ann)
+ return ann_d
def publish(self, service_name, ann, signing_key=None):
- ann_t = self.create_announcement(service_name, ann, signing_key)
- self._published_announcements[service_name] = ann_t
+ # we increment the seqnum every time we publish something new
+ current_seqnum, current_nonce = self._sequencer()
+
+ ann_d = self.create_announcement_dict(service_name, ann)
+ self._outbound_announcements[service_name] = ann_d
+
+ # publish all announcements with the new seqnum and nonce
+ for service_name,ann_d in self._outbound_announcements.items():
+ ann_d["seqnum"] = current_seqnum
+ ann_d["nonce"] = current_nonce
+ ann_t = sign_to_foolscap(ann_d, signing_key)
+ self._published_announcements[service_name] = ann_t
self._maybe_publish()
def _maybe_publish(self):
index = make_index(ann, key_s)
# is this announcement a duplicate?
- if (index in self._current_announcements
- and self._current_announcements[index][0] == ann):
+ if (index in self._inbound_announcements
+ and self._inbound_announcements[index][0] == ann):
self.log(format="reannouncement for [%(service)s]:%(description)s, ignoring",
service=service_name, description=description,
parent=lp2, level=log.UNUSUAL, umid="B1MIdA")
return
# does it update an existing one?
- if index in self._current_announcements:
- old,_,_ = self._current_announcements[index]
+ if index in self._inbound_announcements:
+ old,_,_ = self._inbound_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"
+ if ("seqnum" not in ann
+ or not isinstance(ann["seqnum"], (int,long))):
+ self.log("not replacing old announcement, no valid seqnum: %s"
% (ann,),
parent=lp2, level=log.NOISY, umid="zFGH3Q")
return
self.log("new announcement[%s]" % service_name,
parent=lp2, level=log.NOISY)
- self._current_announcements[index] = (ann, key_s, time.time())
+ self._inbound_announcements[index] = (ann, key_s, time.time())
# note: we never forget an index, but we might update its value
for (service_name2,cb,args,kwargs) in self._local_subscribers:
-import os, re
+import os, re, itertools
from base64 import b32decode
import simplejson
# test compatibility with old introducer .tac files
from allmydata.introducer import IntroducerNode
from allmydata.web import introweb
-from allmydata.util import pollmixin, keyutil, idlib
+from allmydata.client import Client as TahoeClient
+from allmydata.util import pollmixin, keyutil, idlib, fileutil
import allmydata.test.common_util as testutil
class LoggingMultiService(service.MultiService):
def test_create(self):
ic = IntroducerClient(None, "introducer.furl", u"my_nickname",
- "my_version", "oldest_version", {})
+ "my_version", "oldest_version", {}, fakeseq)
self.failUnless(isinstance(ic, IntroducerClient))
def test_listen(self):
i = IntroducerService()
ic = IntroducerClient(None,
"introducer.furl", u"my_nickname",
- "my_version", "oldest_version", {})
+ "my_version", "oldest_version", {}, fakeseq)
sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s)
keyid = keyutil.remove_prefix(vk_s, "pub-v0-")
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
- ann_t = ic.create_announcement("storage", make_ann(furl1), sk)
+ ann_t = make_ann_t(ic, furl1, sk, 1)
i.remote_publish_v2(ann_t, Referenceable())
announcements = i.get_announcements()
self.failUnlessEqual(len(announcements), 1)
self.failUnlessEqual(ann2_out["anonymous-storage-FURL"], furl2)
+def fakeseq():
+ return 1, "nonce"
+
+seqnum_counter = itertools.count(1)
+def realseq():
+ return seqnum_counter.next(), str(os.randint(1,100000))
+
def make_ann(furl):
ann = { "anonymous-storage-FURL": furl,
"permutation-seed-base32": get_tubid_string(furl) }
return ann
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)
+ ann_d = ic.create_announcement_dict("storage", make_ann(furl))
+ ann_d["seqnum"] = seqnum
+ ann_d["nonce"] = "nonce"
+ ann_t = sign_to_foolscap(ann_d, privkey)
+ return ann_t
class Client(unittest.TestCase):
def test_duplicate_receive_v1(self):
ic = IntroducerClient(None,
"introducer.furl", u"my_nickname",
- "my_version", "oldest_version", {})
+ "my_version", "oldest_version", {}, fakeseq)
announcements = []
ic.subscribe_to("storage",
lambda key_s,ann: announcements.append(ann))
def test_duplicate_receive_v2(self):
ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname",
- "ver23", "oldest_version", {})
+ "ver23", "oldest_version", {}, fakeseq)
# we use a second client just to create a different-looking
# announcement
ic2 = IntroducerClient(None,
"introducer.furl", u"my_nickname",
- "ver24","oldest_version",{})
+ "ver24","oldest_version",{}, fakeseq)
announcements = []
def _received(key_s, ann):
announcements.append( (key_s, ann) )
# not replace the other)
ic = IntroducerClient(None,
"introducer.furl", u"my_nickname",
- "my_version", "oldest_version", {})
+ "my_version", "oldest_version", {}, fakeseq)
announcements = []
ic.subscribe_to("storage",
lambda key_s,ann: announcements.append(ann))
keyid = keyutil.remove_prefix(vk_s, "pub-v0-")
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
furl2 = "pb://%s@127.0.0.1:36106/swissnum" % keyid
- ann_t = ic.create_announcement("storage", make_ann(furl1), sk)
+ ann_t = make_ann_t(ic, furl1, sk, 1)
ic.remote_announce_v2([ann_t])
d = fireEventually()
def _then(ign):
i = IntroducerService()
ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname",
- "ver23", "oldest_version", {})
+ "ver23", "oldest_version", {}, realseq)
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp"
privkey_s, _ = keyutil.make_keypair()
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)
+ ann1_badseqnum = make_ann_t(ic1, furl1, privkey, seqnum="not an int")
i.remote_publish_v2(ann1, None)
all = i.get_announcements()
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 1)
self.failUnlessEqual(i._debug_counts["inbound_update"], 1)
+ i.remote_publish_v2(ann1_badseqnum, None)
+ all = i.get_announcements()
+ self.failUnlessEqual(len(all), 1)
+ self.failUnlessEqual(all[0].announcement["seqnum"], 11)
+ self.failUnlessEqual(i._debug_counts["inbound_message"], 6)
+ self.failUnlessEqual(i._debug_counts["inbound_duplicate"], 1)
+ self.failUnlessEqual(i._debug_counts["inbound_no_seqnum"], 2)
+ 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
tub2 = Tub()
tub2.setServiceParent(self.parent)
c = IntroducerClient(tub2, ifurl,
- u"nickname", "version", "oldest", {})
+ u"nickname", "version", "oldest", {}, fakeseq)
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s)
c = IntroducerClient(tub, self.introducer_furl,
NICKNAME % str(i),
"version", "oldest",
- {"component": "component-v1"})
+ {"component": "component-v1"}, fakeseq)
received_announcements[c] = {}
def got(key_s_or_tubid, ann, announcements, i):
if i == 0:
tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2",
- "my_version", "oldest", app_versions)
+ "my_version", "oldest", app_versions,
+ fakeseq)
#furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
#ann_s = make_ann_t(client_v2, furl1, None, 10)
#introducer.remote_publish_v2(ann_s, Referenceable())
tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
- "my_version", "oldest", app_versions)
+ "my_version", "oldest", app_versions,
+ fakeseq)
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
tubid = "62ubehyunnyhzs7r6vdonnm2hpi52w6y"
- ann_s0 = make_ann_t(client_v2, furl1, None, 10.0)
+ ann_s0 = make_ann_t(client_v2, furl1, None, 10)
canary0 = Referenceable()
introducer.remote_publish_v2(ann_s0, canary0)
a = introducer.get_announcements()
tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
- "my_version", "oldest", app_versions)
+ "my_version", "oldest", app_versions,
+ fakeseq)
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
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, 10.0)
+ ann_t0 = make_ann_t(client_v2, furl1, sk, 10)
canary0 = Referenceable()
introducer.remote_publish_v2(ann_t0, canary0)
a = introducer.get_announcements()
self.failUnlessEqual(a[0].version, "my_version")
self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1)
+class ClientSeqnums(unittest.TestCase):
+ def test_client(self):
+ basedir = "introducer/ClientSeqnums/test_client"
+ fileutil.make_dirs(basedir)
+ f = open(os.path.join(basedir, "tahoe.cfg"), "w")
+ f.write("[client]\n")
+ f.write("introducer.furl = nope\n")
+ f.close()
+ c = TahoeClient(basedir)
+ ic = c.introducer_client
+ outbound = ic._outbound_announcements
+ published = ic._published_announcements
+ def read_seqnum():
+ f = open(os.path.join(basedir, "announcement-seqnum"))
+ seqnum = f.read().strip()
+ f.close()
+ return int(seqnum)
+
+ ic.publish("sA", {"key": "value1"}, c._server_key)
+ self.failUnlessEqual(read_seqnum(), 1)
+ self.failUnless("sA" in outbound)
+ self.failUnlessEqual(outbound["sA"]["seqnum"], 1)
+ nonce1 = outbound["sA"]["nonce"]
+ self.failUnless(isinstance(nonce1, str))
+ self.failUnlessEqual(simplejson.loads(published["sA"][0]),
+ outbound["sA"])
+ # [1] is the signature, [2] is the pubkey
+
+ # publishing a second service causes both services to be
+ # re-published, with the next higher sequence number
+ ic.publish("sB", {"key": "value2"}, c._server_key)
+ self.failUnlessEqual(read_seqnum(), 2)
+ self.failUnless("sB" in outbound)
+ self.failUnlessEqual(outbound["sB"]["seqnum"], 2)
+ self.failUnless("sA" in outbound)
+ self.failUnlessEqual(outbound["sA"]["seqnum"], 2)
+ nonce2 = outbound["sA"]["nonce"]
+ self.failUnless(isinstance(nonce2, str))
+ self.failIfEqual(nonce1, nonce2)
+ self.failUnlessEqual(simplejson.loads(published["sA"][0]),
+ outbound["sA"])
+ self.failUnlessEqual(simplejson.loads(published["sB"][0]),
+ outbound["sB"])
+
+
class TooNewServer(IntroducerService):
VERSION = { "http://allmydata.org/tahoe/protocols/introducer/v999":
tub.setLocation("localhost:%d" % portnum)
c = IntroducerClient(tub, self.introducer_furl,
- u"nickname-client", "version", "oldest", {})
+ u"nickname-client", "version", "oldest", {},
+ fakeseq)
announcements = {}
def got(key_s, ann):
announcements[key_s] = ann