commit dfd80a48930b328a8410172bc74fcfdf8593428f Merge: 751158f a54308b Author: Ximin Luo infinity0@gmx.com Date: Wed Nov 13 16:23:40 2013 +0000
Merge branch 'fac-build'
Conflicts: Makefile facilitator/Makefile facilitator/facilitator-test.py facilitator/init.d/facilitator.in
Makefile | 12 +- facilitator/.gitignore | 28 ++ facilitator/INSTALL | 27 ++ facilitator/Makefile | 21 -- facilitator/Makefile.am | 140 ++++++++ facilitator/README | 36 +- facilitator/appengine/README | 8 +- facilitator/appengine/app.yaml | 3 +- facilitator/appengine/config.go | 5 + facilitator/appengine/fp-reg.go | 7 +- facilitator/autogen.sh | 2 + facilitator/configure.ac | 49 +++ facilitator/default/facilitator | 11 + facilitator/default/facilitator-email-poller | 7 + facilitator/default/facilitator-reg-daemon | 11 + facilitator/doc/appengine-howto.txt | 56 --- facilitator/doc/appspot-howto.txt | 58 ++++ facilitator/doc/email-howto.txt | 75 ++++ facilitator/doc/facilitator-design.txt | 44 +++ facilitator/doc/facilitator-howto.txt | 199 ----------- facilitator/doc/gmail-howto.txt | 61 ---- facilitator/doc/http-howto.txt | 49 +++ facilitator/doc/server-howto.txt | 55 +++ facilitator/examples/fp-facilitator.conf.in | 28 ++ facilitator/examples/reg-email.pass | 10 + facilitator/fac.py | 11 +- facilitator/facilitator-email-poller | 49 +-- facilitator/facilitator-test | 437 ----------------------- facilitator/facilitator-test.py | 444 ++++++++++++++++++++++++ facilitator/init.d/facilitator | 120 ------- facilitator/init.d/facilitator-email-poller | 119 ------- facilitator/init.d/facilitator-email-poller.in | 133 +++++++ facilitator/init.d/facilitator-reg-daemon | 119 ------- facilitator/init.d/facilitator-reg-daemon.in | 132 +++++++ facilitator/init.d/facilitator.in | 133 +++++++ 35 files changed, 1528 insertions(+), 1171 deletions(-)
diff --cc Makefile index 207115c,8771405..9e8d2dd --- a/Makefile +++ b/Makefile @@@ -1,90 -1,74 +1,96 @@@ -VERSION = 1.3 +# Makefile for a self-contained binary distribution of flashproxy-client. +# +# This builds two zipball targets, dist and dist-exe, for POSIX and Windows +# respectively. Both can be extracted and run in-place by the end user. +# (PGP-signed forms also exist, sign and sign-exe.) +# +# If you are a distro packager, instead see the separate build scripts for each +# source component, all of which have an `install` target: +# - client: Makefile.client +# - common: setup-common.py +# - facilitator: facilitator/{configure.ac,Makefile.am} +# +# Not for the faint-hearted: it is possible to build dist-exe on GNU/Linux by +# using wine to install the windows versions of Python, py2exe, and m2crypto, +# then running `make PYTHON_W32="wine python" dist-exe`. + +PACKAGE = flashproxy-client +VERSION = $(shell sh version.sh) +DISTNAME = $(PACKAGE)-$(VERSION) + +THISFILE = $(lastword $(MAKEFILE_LIST)) +PYTHON = python +PYTHON_W32 = $(PYTHON)
-DESTDIR = -PREFIX = /usr/local -BINDIR = $(PREFIX)/bin -MANDIR = $(PREFIX)/share/man +MAKE_CLIENT = $(MAKE) -f Makefile.client PYTHON="$(PYTHON)" +# don't rebuild man pages due to VCS giving spurious timestamps, see #9940 +REBUILD_MAN = 0
-PYTHON = python -export PY2EXE_TMPDIR = py2exe-tmp +# all is N/A for a binary package, but include for completeness +all: dist
-CLIENT_BIN = flashproxy-client flashproxy-reg-appspot flashproxy-reg-email flashproxy-reg-http flashproxy-reg-url -CLIENT_MAN = doc/flashproxy-client.1 doc/flashproxy-reg-appspot.1 doc/flashproxy-reg-email.1 doc/flashproxy-reg-http.1 doc/flashproxy-reg-url.1 -CLIENT_DIST_FILES = $(CLIENT_BIN) Makefile README LICENSE ChangeLog torrc -CLIENT_DIST_DOC_FILES = $(CLIENT_MAN) +DISTDIR = dist/$(DISTNAME) +$(DISTDIR): Makefile.client setup-common.py $(THISFILE) + mkdir -p $(DISTDIR) + $(MAKE_CLIENT) DESTDIR=$(DISTDIR) bindir=/ docdir=/ man1dir=/doc/ \ + REBUILD_MAN="$(REBUILD_MAN)" install + $(PYTHON) setup-common.py build_py -d $(DISTDIR)
-all: $(CLIENT_DIST_FILES) $(CLIENT_MAN) - : +dist/%.zip: dist/% + cd dist && zip -q -r -9 "$(@:dist/%=%)" "$(<:dist/%=%)"
-%.1: %.1.txt - rm -f $@ - a2x --no-xmllint --xsltproc-opts "--stringparam man.th.title.max.length 24" -d manpage -f manpage $< +dist/%.zip.asc: dist/%.zip + rm -f "$@" + gpg --sign --detach-sign --armor "$<" + gpg --verify "$@" "$<"
-install: - mkdir -p $(DESTDIR)$(BINDIR) - mkdir -p $(DESTDIR)$(MANDIR)/man1 - cp -f $(CLIENT_BIN) $(DESTDIR)$(BINDIR) - cp -f $(CLIENT_MAN) $(DESTDIR)$(MANDIR)/man1 +dist: force-dist $(DISTDIR).zip
-DISTNAME = flashproxy-client-$(VERSION) -DISTDIR = dist/$(DISTNAME) -dist: $(CLIENT_MAN) - rm -rf dist - mkdir -p $(DISTDIR) - mkdir $(DISTDIR)/doc - cp -f $(CLIENT_DIST_FILES) $(DISTDIR) - cp -f $(CLIENT_DIST_DOC_FILES) $(DISTDIR)/doc - cd dist && zip -q -r -9 $(DISTNAME).zip $(DISTNAME) - -dist/$(DISTNAME).zip: $(CLIENT_DIST_FILES) - $(MAKE) dist - -sign: dist/$(DISTNAME).zip - rm -f dist/$(DISTNAME).zip.asc - cd dist && gpg --sign --detach-sign --armor $(DISTNAME).zip - cd dist && gpg --verify $(DISTNAME).zip.asc $(DISTNAME).zip - -$(PY2EXE_TMPDIR)/dist: $(CLIENT_BIN) - rm -rf $(PY2EXE_TMPDIR) - $(PYTHON) setup.py py2exe -q - -dist-exe: DISTNAME := $(DISTNAME)-win32 -dist-exe: CLIENT_BIN := $(PY2EXE_TMPDIR)/dist/* -dist-exe: CLIENT_MAN := $(addsuffix .txt,$(CLIENT_MAN)) -# Delegate to the "dist" target using the substitutions above. -dist-exe: $(PY2EXE_TMPDIR)/dist setup.py dist - -clean: - rm -f *.pyc +sign: force-dist $(DISTDIR).zip.asc + +PY2EXE_TMPDIR = py2exe-tmp +export PY2EXE_TMPDIR +$(PY2EXE_TMPDIR): setup-client-exe.py + $(PYTHON_W32) setup-client-exe.py py2exe -q + +DISTDIR_W32 = $(DISTDIR)-win32 +# below, we override DST_SCRIPT and DST_MAN1 for windows +$(DISTDIR_W32): $(PY2EXE_TMPDIR) $(THISFILE) + mkdir -p $(DISTDIR_W32) + $(MAKE_CLIENT) DESTDIR=$(DISTDIR_W32) bindir=/ docdir=/ man1dir=/doc/ \ + DST_SCRIPT= DST_MAN1='$$(SRC_MAN1)' \ + REBUILD_MAN="$(REBUILD_MAN)" install + cp -t $(DISTDIR_W32) $(PY2EXE_TMPDIR)/dist/* + +dist-exe: force-dist-exe $(DISTDIR_W32).zip + +sign-exe: force-dist-exe $(DISTDIR_W32).zip.asc + +# clean is N/A for a binary package, but include for completeness +clean: distclean + +distclean: + $(MAKE_CLIENT) clean + $(PYTHON) setup-common.py clean --all rm -rf dist $(PY2EXE_TMPDIR)
-clean-all: clean - rm -f doc/*.1 +test: check +check: + $(MAKE_CLIENT) check + $(PYTHON) setup-common.py test - cd facilitator && ./facilitator-test - cd proxy && ./flashproxy-test.js + -test: - ./flashproxy-client-test + + test-full: test + cd facilitator && \ + { test -x ./config.status && ./config.status || \ + { test -x ./configure || ./autogen.sh; } && ./configure; } \ + && make && make check + cd proxy && make test
-.PHONY: all install dist sign dist-exe clean clean-all test test-full +force-dist: + rm -rf $(DISTDIR) $(DISTDIR).zip + +force-dist-exe: + rm -rf $(DISTDIR_W32) $(DISTDIR_W32).zip $(PY2EXE_TMPDIR) + - .PHONY: all dist sign dist-exe sign-exe clean distclean test check force-dist force-dist-exe ++.PHONY: all dist sign dist-exe sign-exe clean distclean test check test-full force-dist force-dist-exe diff --cc facilitator/facilitator-email-poller index 5348219,d4e1e16..be1ab58 --- a/facilitator/facilitator-email-poller +++ b/facilitator/facilitator-email-poller @@@ -19,11 -19,9 +19,9 @@@ import tim import fac
from hashlib import sha1 -from M2Crypto import SSL, X509 +from M2Crypto import SSL
- DEFAULT_IMAP_HOST = "imap.gmail.com" DEFAULT_IMAP_PORT = 993 - DEFAULT_EMAIL_ADDRESS = "flashproxyreg.a@gmail.com" DEFAULT_LOG_FILENAME = "facilitator-email-poller.log"
POLL_INTERVAL = 60 diff --cc facilitator/facilitator-test.py index 0000000,e00ea5e..8debb82 mode 000000,100755..100755 --- a/facilitator/facilitator-test.py +++ b/facilitator/facilitator-test.py @@@ -1,0 -1,199 +1,444 @@@ + #!/usr/bin/env python + ++from cStringIO import StringIO + import os + import socket + import subprocess ++import tempfile ++import sys + import time + import unittest + + import fac ++from fac import Transport, Endpoint ++ ++# Import the facilitator program as a module. ++import imp ++dont_write_bytecode = sys.dont_write_bytecode ++sys.dont_write_bytecode = True ++facilitator = imp.load_source("facilitator", "facilitator") ++Endpoints = facilitator.Endpoints ++parse_relay_file = facilitator.parse_relay_file ++sys.dont_write_bytecode = dont_write_bytecode ++del dont_write_bytecode ++del facilitator + + FACILITATOR_HOST = "127.0.0.1" -FACILITATOR_PORT = 39002 ++FACILITATOR_PORT = 39002 # diff port to not conflict with production service + FACILITATOR_ADDR = (FACILITATOR_HOST, FACILITATOR_PORT) ++CLIENT_TP = "websocket" ++RELAY_TP = "websocket" ++PROXY_TPS = ["websocket", "webrtc"] + + def gimme_socket(host, port): + addrinfo = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP)[0] + s = socket.socket(addrinfo[0], addrinfo[1], addrinfo[2]) + s.settimeout(10.0) + s.connect(addrinfo[4]) + return s + ++class EndpointsTest(unittest.TestCase): ++ ++ def setUp(self): ++ self.pts = Endpoints(af=socket.AF_INET) ++ ++ def test_addEndpoints_twice(self): ++ self.pts.addEndpoint("A", "a|b|p") ++ self.assertFalse(self.pts.addEndpoint("A", "zzz")) ++ self.assertEquals(self.pts._endpoints["A"], Transport("a|b", "p")) ++ ++ def test_delEndpoints_twice(self): ++ self.pts.addEndpoint("A", "a|b|p") ++ self.assertTrue(self.pts.delEndpoint("A")) ++ self.assertFalse(self.pts.delEndpoint("A")) ++ self.assertEquals(self.pts._endpoints.get("A"), None) ++ ++ def test_Endpoints_indexing(self): ++ self.assertEquals(self.pts._indexes.get("p"), None) ++ # test defaultdict works as expected ++ self.assertEquals(self.pts._indexes["p"]["a|b"], set("")) ++ self.pts.addEndpoint("A", "a|b|p") ++ self.assertEquals(self.pts._indexes["p"]["a|b"], set("A")) ++ self.pts.addEndpoint("B", "a|b|p") ++ self.assertEquals(self.pts._indexes["p"]["a|b"], set("AB")) ++ self.pts.delEndpoint("A") ++ self.assertEquals(self.pts._indexes["p"]["a|b"], set("B")) ++ self.pts.delEndpoint("B") ++ self.assertEquals(self.pts._indexes["p"]["a|b"], set("")) ++ ++ def test_serveReg_maxserve_infinite_roundrobin(self): ++ # case for servers, they never exhaust ++ self.pts.addEndpoint("A", "a|p") ++ self.pts.addEndpoint("B", "a|p") ++ self.pts.addEndpoint("C", "a|p") ++ for i in xrange(64): # 64 is infinite ;) ++ served = set() ++ served.add(self.pts._serveReg("ABC").addr) ++ served.add(self.pts._serveReg("ABC").addr) ++ served.add(self.pts._serveReg("ABC").addr) ++ self.assertEquals(served, set("ABC")) ++ ++ def test_serveReg_maxserve_finite_exhaustion(self): ++ # case for clients, we don't want to keep serving them ++ self.pts = Endpoints(af=socket.AF_INET, maxserve=5) ++ self.pts.addEndpoint("A", "a|p") ++ self.pts.addEndpoint("B", "a|p") ++ self.pts.addEndpoint("C", "a|p") ++ # test getNumUnservedEndpoints whilst we're at it ++ self.assertEquals(self.pts.getNumUnservedEndpoints(), 3) ++ served = set() ++ served.add(self.pts._serveReg("ABC").addr) ++ self.assertEquals(self.pts.getNumUnservedEndpoints(), 2) ++ served.add(self.pts._serveReg("ABC").addr) ++ self.assertEquals(self.pts.getNumUnservedEndpoints(), 1) ++ served.add(self.pts._serveReg("ABC").addr) ++ self.assertEquals(self.pts.getNumUnservedEndpoints(), 0) ++ self.assertEquals(served, set("ABC")) ++ for i in xrange(5-2): ++ served = set() ++ served.add(self.pts._serveReg("ABC").addr) ++ served.add(self.pts._serveReg("ABC").addr) ++ served.add(self.pts._serveReg("ABC").addr) ++ self.assertEquals(served, set("ABC")) ++ remaining = set("ABC") ++ remaining.remove(self.pts._serveReg(remaining).addr) ++ self.assertRaises(KeyError, self.pts._serveReg, "ABC") ++ remaining.remove(self.pts._serveReg(remaining).addr) ++ self.assertRaises(KeyError, self.pts._serveReg, "ABC") ++ remaining.remove(self.pts._serveReg(remaining).addr) ++ self.assertRaises(KeyError, self.pts._serveReg, "ABC") ++ self.assertEquals(remaining, set()) ++ self.assertEquals(self.pts.getNumUnservedEndpoints(), 0) ++ ++ def test_match_normal(self): ++ self.pts.addEndpoint("A", "a|p") ++ self.pts2 = Endpoints(af=socket.AF_INET) ++ self.pts2.addEndpoint("B", "a|p") ++ self.pts2.addEndpoint("C", "b|p") ++ self.pts2.addEndpoint("D", "a|q") ++ expected = (Endpoint("A", Transport("a","p")), Endpoint("B", Transport("a","p"))) ++ empty = Endpoints.EMPTY_MATCH ++ self.assertEquals(expected, Endpoints.match(self.pts, self.pts2, ["p"])) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["x"])) ++ ++ def test_match_unequal_client_server(self): ++ self.pts.addEndpoint("A", "a|p") ++ self.pts2 = Endpoints(af=socket.AF_INET) ++ self.pts2.addEndpoint("B", "a|q") ++ expected = (Endpoint("A", Transport("a","p")), Endpoint("B", Transport("a","q"))) ++ empty = Endpoints.EMPTY_MATCH ++ self.assertEquals(expected, Endpoints.match(self.pts, self.pts2, ["p", "q"])) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["p"])) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["q"])) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["x"])) ++ ++ def test_match_raw_server(self): ++ self.pts.addEndpoint("A", "p") ++ self.pts2 = Endpoints(af=socket.AF_INET) ++ self.pts2.addEndpoint("B", "p") ++ expected = (Endpoint("A", Transport("","p")), Endpoint("B", Transport("","p"))) ++ empty = Endpoints.EMPTY_MATCH ++ self.assertEquals(expected, Endpoints.match(self.pts, self.pts2, ["p"])) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["x"])) ++ ++ def test_match_many_inners(self): ++ self.pts.addEndpoint("A", "a|p") ++ self.pts.addEndpoint("B", "b|p") ++ self.pts.addEndpoint("C", "p") ++ self.pts2 = Endpoints(af=socket.AF_INET) ++ self.pts2.addEndpoint("D", "a|p") ++ self.pts2.addEndpoint("E", "b|p") ++ self.pts2.addEndpoint("F", "p") ++ # this test ensures we have a sane policy for selecting between inners pools ++ expected = set() ++ expected.add((Endpoint("A", Transport("a","p")), Endpoint("D", Transport("a","p")))) ++ expected.add((Endpoint("B", Transport("b","p")), Endpoint("E", Transport("b","p")))) ++ expected.add((Endpoint("C", Transport("","p")), Endpoint("F", Transport("","p")))) ++ result = set() ++ result.add(Endpoints.match(self.pts, self.pts2, ["p"])) ++ result.add(Endpoints.match(self.pts, self.pts2, ["p"])) ++ result.add(Endpoints.match(self.pts, self.pts2, ["p"])) ++ empty = Endpoints.EMPTY_MATCH ++ self.assertEquals(expected, result) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["x"])) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["x"])) ++ self.assertEquals(empty, Endpoints.match(self.pts, self.pts2, ["x"])) ++ ++ def test_match_exhaustion(self): ++ self.pts.addEndpoint("A", "p") ++ self.pts2 = Endpoints(af=socket.AF_INET, maxserve=2) ++ self.pts2.addEndpoint("B", "p") ++ Endpoints.match(self.pts2, self.pts, ["p"]) ++ Endpoints.match(self.pts2, self.pts, ["p"]) ++ empty = Endpoints.EMPTY_MATCH ++ self.assertTrue("B" not in self.pts2._endpoints) ++ self.assertTrue("B" not in self.pts2._indexes["p"][""]) ++ self.assertEquals(empty, Endpoints.match(self.pts2, self.pts, ["p"])) ++ ++ + class FacilitatorTest(unittest.TestCase): ++ ++ def test_transport_parse(self): ++ self.assertEquals(Transport.parse("a"), Transport("", "a")) ++ self.assertEquals(Transport.parse("|a"), Transport("", "a")) ++ self.assertEquals(Transport.parse("a|b|c"), Transport("a|b","c")) ++ self.assertEquals(Transport.parse(Transport("a|b","c")), Transport("a|b","c")) ++ self.assertRaises(ValueError, Transport, "", "") ++ self.assertRaises(ValueError, Transport, "a", "") ++ self.assertRaises(ValueError, Transport.parse, "") ++ self.assertRaises(ValueError, Transport.parse, "|") ++ self.assertRaises(ValueError, Transport.parse, "a|") ++ self.assertRaises(ValueError, Transport.parse, ["a"]) ++ self.assertRaises(ValueError, Transport.parse, [Transport("a", "b")]) ++ ++ def test_parse_relay_file(self): ++ fp = StringIO() ++ fp.write("websocket 0.0.1.0:1\n") ++ fp.flush() ++ fp.seek(0) ++ af = socket.AF_INET ++ servers = { af: Endpoints(af=af) } ++ parse_relay_file(servers, fp) ++ self.assertEquals(servers[af]._endpoints, {('0.0.1.0', 1): Transport('', 'websocket')}) ++ ++class FacilitatorProcTest(unittest.TestCase): + IPV4_CLIENT_ADDR = ("1.1.1.1", 9000) + IPV6_CLIENT_ADDR = ("[11::11]", 9000) + IPV4_PROXY_ADDR = ("2.2.2.2", 13000) + IPV6_PROXY_ADDR = ("[22::22]", 13000) ++ IPV4_RELAY_ADDR = ("0.0.1.0", 1) ++ IPV6_RELAY_ADDR = ("[0:0::1:0]", 1) + + def gimme_socket(self): + return gimme_socket(FACILITATOR_HOST, FACILITATOR_PORT) + + def setUp(self): ++ self.relay_file = tempfile.NamedTemporaryFile() ++ self.relay_file.write("%s %s\n" % (RELAY_TP, fac.format_addr(self.IPV4_RELAY_ADDR))) ++ self.relay_file.write("%s %s\n" % (RELAY_TP, fac.format_addr(self.IPV6_RELAY_ADDR))) ++ self.relay_file.flush() ++ self.relay_file.seek(0) + fn = os.path.join(os.path.dirname(__file__), "./facilitator") - self.process = subprocess.Popen(["python", fn, "-d", "-p", str(FACILITATOR_PORT), "-r", "0.0.1.0:1", "-l", "/dev/null"]) ++ self.process = subprocess.Popen(["python", fn, "-d", "-p", str(FACILITATOR_PORT), "-r", self.relay_file.name, "-l", "/dev/null"]) + time.sleep(0.1) + + def tearDown(self): + ret = self.process.poll() + if ret is not None: + raise Exception("facilitator subprocess exited unexpectedly with status %d" % ret) + self.process.terminate() + + def test_timeout(self): + """Test that the socket will not accept slow writes indefinitely. + Successive sends should not reset the timeout counter.""" + s = self.gimme_socket() + time.sleep(0.3) + s.send("w") + time.sleep(0.3) + s.send("w") + time.sleep(0.3) + s.send("w") + time.sleep(0.3) + s.send("w") + time.sleep(0.3) + self.assertRaises(socket.error, s.send, "w") + + def test_readline_limit(self): + """Test that reads won't buffer indefinitely.""" + s = self.gimme_socket() + buflen = 0 + try: + while buflen + 1024 < 200000: + s.send("X" * 1024) + buflen += 1024 ++ # TODO(dcf1): sometimes no error is raised, and this test fails + self.fail("should have raised a socket error") + except socket.error: + pass + + def test_af_v4_v4(self): + """Test that IPv4 proxies can get IPv4 clients.""" - fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR) - fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR) - reg = fac.get_reg(FACILITATOR_ADDR, self.IPV4_PROXY_ADDR) ++ fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR, CLIENT_TP) ++ fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR, CLIENT_TP) ++ reg = fac.get_reg(FACILITATOR_ADDR, self.IPV4_PROXY_ADDR, PROXY_TPS) + self.assertEqual(reg["client"], fac.format_addr(self.IPV4_CLIENT_ADDR)) + + def test_af_v4_v6(self): + """Test that IPv4 proxies do not get IPv6 clients.""" - fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR) - reg = fac.get_reg(FACILITATOR_ADDR, self.IPV4_PROXY_ADDR) ++ fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR, CLIENT_TP) ++ reg = fac.get_reg(FACILITATOR_ADDR, self.IPV4_PROXY_ADDR, PROXY_TPS) + self.assertEqual(reg["client"], "") + + def test_af_v6_v4(self): + """Test that IPv6 proxies do not get IPv4 clients.""" - fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR) - reg = fac.get_reg(FACILITATOR_ADDR, self.IPV6_PROXY_ADDR) ++ fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR, CLIENT_TP) ++ reg = fac.get_reg(FACILITATOR_ADDR, self.IPV6_PROXY_ADDR, PROXY_TPS) + self.assertEqual(reg["client"], "") + + def test_af_v6_v6(self): + """Test that IPv6 proxies can get IPv6 clients.""" - fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR) - fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR) - reg = fac.get_reg(FACILITATOR_ADDR, self.IPV6_PROXY_ADDR) ++ fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR, CLIENT_TP) ++ fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR, CLIENT_TP) ++ reg = fac.get_reg(FACILITATOR_ADDR, self.IPV6_PROXY_ADDR, PROXY_TPS) + self.assertEqual(reg["client"], fac.format_addr(self.IPV6_CLIENT_ADDR)) + - def test_check_back_in(self): - """Test that facilitator responses contain a CHECK-BACK-IN key with a - numeric value.""" - reg = fac.get_reg(FACILITATOR_ADDR, self.IPV6_PROXY_ADDR) ++ def test_fields(self): ++ """Test that facilitator responses contain all the required fields.""" ++ fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR, CLIENT_TP) ++ reg = fac.get_reg(FACILITATOR_ADDR, self.IPV4_PROXY_ADDR, PROXY_TPS) ++ self.assertEqual(reg["client"], fac.format_addr(self.IPV4_CLIENT_ADDR)) ++ self.assertEqual(reg["client-transport"], CLIENT_TP) ++ self.assertEqual(reg["relay"], fac.format_addr(self.IPV4_RELAY_ADDR)) ++ self.assertEqual(reg["relay-transport"], RELAY_TP) + self.assertGreater(int(reg["check-back-in"]), 0) + + # def test_same_proxy(self): + # """Test that the same proxy doesn't get the same client when asking + # twice.""" + # self.fail() + # + # def test_num_clients(self): + # """Test that the same proxy can pick up up to five different clients but + # no more. Test that a proxy ceasing to handle a client allows the proxy + # to handle another, different client.""" + # self.fail() + # + # def test_num_proxies(self): + # """Test that a single client is handed out to five different proxies but + # no more. Test that a proxy ceasing to handle a client reduces its count + # so another proxy can handle it.""" + # self.fail() + # + # def test_proxy_timeout(self): + # """Test that a proxy ceasing to connect for some time period causes that + # proxy's clients to be unhandled by that proxy.""" + # self.fail() + # + # def test_localhost_only(self): + # """Test that the facilitator doesn't listen on any external + # addresses.""" + # self.fail() + # + # def test_hostname(self): + # """Test that the facilitator rejects hostnames.""" + # self.fail() + + class ParseAddrSpecTest(unittest.TestCase): + def test_ipv4(self): + self.assertEqual(fac.parse_addr_spec("192.168.0.1:9999"), ("192.168.0.1", 9999)) + + def test_ipv6(self): + self.assertEqual(fac.parse_addr_spec("[12::34]:9999"), ("12::34", 9999)) + + def test_defhost_defport_ipv4(self): + self.assertEqual(fac.parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 8888)) + self.assertEqual(fac.parse_addr_spec("192.168.0.2:", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 9999)) + self.assertEqual(fac.parse_addr_spec("192.168.0.2", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 9999)) + self.assertEqual(fac.parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 8888)) + self.assertEqual(fac.parse_addr_spec(":", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 9999)) + self.assertEqual(fac.parse_addr_spec("", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 9999)) + + def test_defhost_defport_ipv6(self): + self.assertEqual(fac.parse_addr_spec("[1234::2]:8888", defhost="1234::1", defport=9999), ("1234::2", 8888)) + self.assertEqual(fac.parse_addr_spec("[1234::2]:", defhost="1234::1", defport=9999), ("1234::2", 9999)) + self.assertEqual(fac.parse_addr_spec("[1234::2]", defhost="1234::1", defport=9999), ("1234::2", 9999)) + self.assertEqual(fac.parse_addr_spec(":8888", defhost="1234::1", defport=9999), ("1234::1", 8888)) + self.assertEqual(fac.parse_addr_spec(":", defhost="1234::1", defport=9999), ("1234::1", 9999)) + self.assertEqual(fac.parse_addr_spec("", defhost="1234::1", defport=9999), ("1234::1", 9999)) + + def test_noresolve(self): + """Test that parse_addr_spec does not do DNS resolution by default.""" + self.assertRaises(ValueError, fac.parse_addr_spec, "example.com") + + def test_noresolve_nameok(self): + """Test that nameok passes through a domain name without resolving it.""" + self.assertEqual(fac.parse_addr_spec("example.com:8888", defhost="other.com", defport=9999, nameOk=True), ("example.com", 8888)) + self.assertEqual(fac.parse_addr_spec("", defhost="other.com", defport=9999, nameOk=True), ("other.com", 9999)) + + class ParseTransactionTest(unittest.TestCase): + def test_empty_string(self): + self.assertRaises(ValueError, fac.parse_transaction, "") + + def test_correct(self): + self.assertEqual(fac.parse_transaction("COMMAND"), ("COMMAND", ())) + self.assertEqual(fac.parse_transaction("COMMAND X="""), ("COMMAND", (("X", ""),))) + self.assertEqual(fac.parse_transaction("COMMAND X="ABC""), ("COMMAND", (("X", "ABC"),))) + self.assertEqual(fac.parse_transaction("COMMAND X="\A\B\C""), ("COMMAND", (("X", "ABC"),))) + self.assertEqual(fac.parse_transaction("COMMAND X="\\\"""), ("COMMAND", (("X", "\""),))) + self.assertEqual(fac.parse_transaction("COMMAND X="ABC" Y="DEF""), ("COMMAND", (("X", "ABC"), ("Y", "DEF")))) + self.assertEqual(fac.parse_transaction("COMMAND KEY-NAME="ABC""), ("COMMAND", (("KEY-NAME", "ABC"),))) + self.assertEqual(fac.parse_transaction("COMMAND KEY_NAME="ABC""), ("COMMAND", (("KEY_NAME", "ABC"),))) + + def test_missing_command(self): + self.assertRaises(ValueError, fac.parse_transaction, "X="ABC"") + self.assertRaises(ValueError, fac.parse_transaction, " X="ABC"") + + def test_missing_space(self): + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND/X="ABC"") + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X="ABC"Y="DEF"") + + def test_bad_quotes(self): + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X="") + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X="ABC") + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X="ABC" Y="ABC") + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X="ABC\") + + def test_truncated(self): + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=") + + def test_newline(self): + self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X="ABC" \nY="DEF"") + ++class ReadClientRegistrationsTest(unittest.TestCase): ++ def testSingle(self): ++ l = list(fac.read_client_registrations("")) ++ self.assertEqual(len(l), 0) ++ l = list(fac.read_client_registrations("client=1.2.3.4:1111")) ++ self.assertEqual(len(l), 1) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ l = list(fac.read_client_registrations("client=1.2.3.4:1111\n")) ++ self.assertEqual(len(l), 1) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ l = list(fac.read_client_registrations("foo=bar&client=1.2.3.4:1111&baz=quux")) ++ self.assertEqual(len(l), 1) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ l = list(fac.read_client_registrations("foo=b%3dar&client=1.2.3.4%3a1111")) ++ self.assertEqual(len(l), 1) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ l = list(fac.read_client_registrations("client=%5b1::2%5d:3333")) ++ self.assertEqual(len(l), 1) ++ self.assertEqual(l[0].addr, ("1::2", 3333)) ++ ++ def testDefaultAddress(self): ++ l = list(fac.read_client_registrations("client=:1111&transport=websocket", defhost="1.2.3.4")) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ l = list(fac.read_client_registrations("client=1.2.3.4:&transport=websocket", defport=1111)) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ ++ def testDefaultTransport(self): ++ l = list(fac.read_client_registrations("client=1.2.3.4:1111")) ++ self.assertEqual(l[0].transport, "websocket") ++ ++ def testMultiple(self): ++ l = list(fac.read_client_registrations("client=1.2.3.4:1111&foo=bar\nfoo=bar&client=5.6.7.8:2222")) ++ self.assertEqual(len(l), 2) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ self.assertEqual(l[1].addr, ("5.6.7.8", 2222)) ++ l = list(fac.read_client_registrations("client=1.2.3.4:1111&foo=bar\nfoo=bar&client=%5b1::2%5d:3333")) ++ self.assertEqual(len(l), 2) ++ self.assertEqual(l[0].addr, ("1.2.3.4", 1111)) ++ self.assertEqual(l[1].addr, ("1::2", 3333)) ++ ++ def testInvalid(self): ++ # Missing "client". ++ with self.assertRaises(ValueError): ++ list(fac.read_client_registrations("foo=bar")) ++ # More than one "client". ++ with self.assertRaises(ValueError): ++ list(fac.read_client_registrations("client=1.2.3.4:1111&foo=bar&client=5.6.7.8:2222")) ++ # Single client with bad syntax. ++ with self.assertRaises(ValueError): ++ list(fac.read_client_registrations("client=1.2.3.4,1111")) ++ + if __name__ == "__main__": + unittest.main() diff --cc facilitator/init.d/facilitator.in index 0000000,d6dc0ec..1a1899b mode 000000,100755..100755 --- a/facilitator/init.d/facilitator.in +++ b/facilitator/init.d/facilitator.in @@@ -1,0 -1,136 +1,133 @@@ + #! /bin/sh + ### BEGIN INIT INFO + # Provides: facilitator + # Required-Start: $remote_fs $syslog + # Required-Stop: $remote_fs $syslog + # Default-Start: 2 3 4 5 + # Default-Stop: 0 1 6 + # Short-Description: Flash proxy facilitator + # Description: Debian init script for the flash proxy facilitator. + ### END INIT INFO + # + # Author: David Fifield david@bamsoftware.com + # + + # Based on /etc/init.d/skeleton from Debian 6. + -# The relay must support the websocket pluggable transport. -# This is the IPv4 address of tor1.bamsoftware.com. -RELAY=173.255.221.44:9901 - + PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin + DESC="Flash proxy facilitator" + NAME=facilitator + + prefix=@prefix@ + exec_prefix=@exec_prefix@ + PIDFILE=@localstatedir@/run/$NAME.pid + LOGFILE=@localstatedir@/log/$NAME.log + CONFDIR=@sysconfdir@/flashproxy ++RELAYFILE=$CONFDIR/relays + PRIVDROP_USER=@fpfacilitatoruser@ + DAEMON=@bindir@/$NAME -DAEMON_ARGS="-r $RELAY --log $LOGFILE --pidfile $PIDFILE --privdrop-user $PRIVDROP_USER" ++DAEMON_ARGS="--relay-file $RELAYFILE --log $LOGFILE --pidfile $PIDFILE --privdrop-user $PRIVDROP_USER" + DEFAULTSFILE=@sysconfdir@/default/$NAME + + # Exit if the package is not installed + [ -x "$DAEMON" ] || exit 0 + + # Read configuration variable file if it is present + [ -r "$DEFAULTSFILE" ] && . "$DEFAULTSFILE" + + . /lib/init/vars.sh + . /lib/lsb/init-functions + + [ "$UNSAFE_LOGGING" = "yes" ] && DAEMON_ARGS="$DAEMON_ARGS --unsafe-logging" + [ -n "$PORT" ] && DAEMON_ARGS="$DAEMON_ARGS --port $PORT" + + # + # Function that starts the daemon/service + # + do_start() + { + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + } + + # + # Function that stops the daemon/service + # + do_stop() + { + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" + } + + case "$1" in + start) + if [ "$RUN_DAEMON" != "yes" ]; then + log_action_msg "Not starting $DESC (Disabled in $DEFAULTSFILE)." + exit 0 + fi + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $0 {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; + esac + + :
tor-commits@lists.torproject.org