-import os, re
+import os, re, itertools
from base64 import b32decode
import simplejson
from allmydata.interfaces import InsufficientVersionError
from allmydata.introducer.client import IntroducerClient, \
WrapV2ClientInV1Interface
-from allmydata.introducer.server import IntroducerService
+from allmydata.introducer.server import IntroducerService, FurlFileConflictError
from allmydata.introducer.common import get_tubid_string_from_ann, \
get_tubid_string, sign_to_foolscap, unsign_from_foolscap, \
UnknownKeyError
# 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 log(self, msg, **kw):
log.msg(msg, **kw)
-class Node(testutil.SignalMixin, unittest.TestCase):
- def test_loadable(self):
- basedir = "introducer.IntroducerNode.test_loadable"
+class Node(testutil.SignalMixin, testutil.ReallyEqualMixin, unittest.TestCase):
+ def test_furl(self):
+ basedir = "introducer.IntroducerNode.test_furl"
os.mkdir(basedir)
- q = IntroducerNode(basedir)
+ public_fn = os.path.join(basedir, "introducer.furl")
+ private_fn = os.path.join(basedir, "private", "introducer.furl")
+ q1 = IntroducerNode(basedir)
d = fireEventually(None)
- d.addCallback(lambda res: q.startService())
- d.addCallback(lambda res: q.when_tub_ready())
- d.addCallback(lambda res: q.stopService())
+ d.addCallback(lambda res: q1.startService())
+ d.addCallback(lambda res: q1.when_tub_ready())
+ d.addCallback(lambda res: q1.stopService())
d.addCallback(flushEventualQueue)
+ def _check_furl(res):
+ # new nodes create unguessable furls in private/introducer.furl
+ ifurl = fileutil.read(private_fn)
+ self.failUnless(ifurl)
+ ifurl = ifurl.strip()
+ self.failIf(ifurl.endswith("/introducer"), ifurl)
+
+ # old nodes created guessable furls in BASEDIR/introducer.furl
+ guessable = ifurl[:ifurl.rfind("/")] + "/introducer"
+ fileutil.write(public_fn, guessable+"\n", mode="w") # text
+
+ # if we see both files, throw an error
+ self.failUnlessRaises(FurlFileConflictError,
+ IntroducerNode, basedir)
+
+ # when we see only the public one, move it to private/ and use
+ # the existing furl instead of creating a new one
+ os.unlink(private_fn)
+ q2 = IntroducerNode(basedir)
+ d2 = fireEventually(None)
+ d2.addCallback(lambda res: q2.startService())
+ d2.addCallback(lambda res: q2.when_tub_ready())
+ d2.addCallback(lambda res: q2.stopService())
+ d2.addCallback(flushEventualQueue)
+ def _check_furl2(res):
+ self.failIf(os.path.exists(public_fn))
+ ifurl2 = fileutil.read(private_fn)
+ self.failUnless(ifurl2)
+ self.failUnlessEqual(ifurl2.strip(), guessable)
+ d2.addCallback(_check_furl2)
+ return d2
+ d.addCallback(_check_furl)
return d
+ def test_web_static(self):
+ basedir = u"introducer.Node.test_web_static"
+ os.mkdir(basedir)
+ fileutil.write(os.path.join(basedir, "tahoe.cfg"),
+ "[node]\n" +
+ "web.port = tcp:0:interface=127.0.0.1\n" +
+ "web.static = relative\n")
+ c = IntroducerNode(basedir)
+ w = c.getServiceNamed("webish")
+ abs_basedir = fileutil.abspath_expanduser_unicode(basedir)
+ expected = fileutil.abspath_expanduser_unicode(u"relative", abs_basedir)
+ self.failUnlessReallyEqual(w.staticdir, expected)
+
+
class ServiceMixin:
def setUp(self):
self.parent = LoggingMultiService()
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):
- return ic.create_announcement("storage", make_ann(furl), privkey)
+def make_ann_t(ic, furl, privkey, seqnum):
+ 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) )
# 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()
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]))
# 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):
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", {}, realseq)
+ 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)
+ ann1_badseqnum = make_ann_t(ic1, furl1, privkey, seqnum="not an int")
+
+ 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)
+
+ 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
class SystemTestMixin(ServiceMixin, pollmixin.PollMixin):
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:
class FakeRemoteReference:
def notifyOnDisconnect(self, *args, **kwargs): pass
def getRemoteTubID(self): return "62ubehyunnyhzs7r6vdonnm2hpi52w6y"
- def getLocationHints(self): return [("ipv4", "here.example.com", "1234"),
- ("ipv4", "there.example.com", "2345")]
+ def getLocationHints(self): return ["tcp:here.example.com:1234",
+ "tcp:there.example.com2345"]
def getPeer(self): return address.IPv4Address("TCP", "remote.example.com",
3456)
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)
+ #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",
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)
+ 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)
+ 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._node_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._node_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