def cb(timeout): self.requestBlocks_dcall = None # Pick one of the hashes randomly try: bhash = self.req_blocks.peek() except KeyError: return # Build request packet packet = ['bQ'] packet.append(self.main.osm.me.ipp) packet.append(bhash) # Send to bridge ad = Ad().setRawIPPort(self.parent_n.ipp) self.main.ph.sendPacket(''.join(packet), ad.getAddrTuple()) # Too many failures, just give up if timeout > 30.0: self.req_blocks.clear() return # Schedule next request. # This will become immediate if a reply arrives. when = random.uniform(0.9, 1.1) * timeout timeout *= 1.2 self.requestBlocks_dcall = reactor.callLater(when, cb, timeout)
def bridgeevent_AddNickWithHostname(self, n, hostname): # Set up hostname and hostmask. scfg = getServiceConfig() ad = Ad().setRawIPPort(n.ipp) # Set regular hostname, falling back to IP if none exists. if hostname: n.hostname = hostname else: n.hostname = ad.getTextIP() # Set cloaked hostname. try: hm = scfg.hostmasker except AttributeError: # Masking disabled. n.hostmask = n.hostname else: # Masking enabled. try: if not hostname: raise BadHostnameError n.hostmask = hm.maskHostname(hostname) except BadHostnameError: n.hostmask = hm.maskIPv4(ad) osm = self.getOnlineStateManager() # Check channel bans on-join. if self.isNodeChannelBanned(n): chunks = [] osm.bsm.addKickChunk(chunks, n, cfg.irc_to_dc_bot, "Channel Ban", rejoin=True, silent=True) osm.bsm.sendBridgeChange(chunks) # Remove from Dtella nick list del n.inick osm.nkm.removeNode(n, "ChanBanned") n.setNoUser() return # Announce new user to IRC. if self.ircs: self.ircs.event_AddDtNode(n, n_user(n.ipp)) # Send queued chat messages osm.cms.flushQueue(n)
def bridgeevent_AddNickWithHostname(self, n, hostname): # Set up hostname and hostmask. scfg = getServiceConfig() ad = Ad().setRawIPPort(n.ipp) # Set regular hostname, falling back to IP if none exists. if hostname: n.hostname = hostname else: n.hostname = ad.getTextIP() # Set cloaked hostname. try: hm = scfg.hostmasker except AttributeError: # Masking disabled. n.hostmask = n.hostname else: # Masking enabled. try: if not hostname: raise BadHostnameError n.hostmask = hm.maskHostname(hostname) except BadHostnameError: n.hostmask = hm.maskIPv4(ad) osm = self.getOnlineStateManager() # Check channel bans on-join. if self.isNodeChannelBanned(n): chunks = [] osm.bsm.addKickChunk( chunks, n, cfg.irc_to_dc_bot, "Channel Ban", rejoin=True, silent=True) osm.bsm.sendBridgeChange(chunks) # Remove from Dtella nick list del n.inick osm.nkm.removeNode(n, "ChanBanned") n.setNoUser() return # Announce new user to IRC. if self.ircs: self.ircs.event_AddDtNode(n, n_user(n.ipp)) # Send queued chat messages osm.cms.flushQueue(n)
def queryLocation(self, my_ipp): # Try to convert the IP address into a human-readable location name. # This might be slightly more complicated than it really needs to be. CHECK(local.use_locations) ad = Ad().setRawIPPort(my_ipp) my_ip = ad.getTextIP() # Set my local ip in the state # self.state.local_ip = my_ip # self.state.saveState() skip = False for ip, loc in self.location.items(): if ip == my_ip: skip = True elif loc: # Forget old entries del self.location[ip] # If we already had an entry for this IP, then don't start # another lookup. if skip: return # A location of None indicates that a lookup is in progress self.location[my_ip] = None def cb(hostname): # Use local_config to transform this hostname into a # human-readable location loc = local.hostnameToLocation(hostname) # If we got a location, save it, otherwise dump the # dictionary entry if loc: self.location[my_ip] = loc else: del self.location[my_ip] # Maybe send an info update if self.osm: self.osm.updateMyInfo() # Start lookup ipToHostname(ad).addCallback(cb)
def queryLocation(self, my_ipp): # Try to convert the IP address into a human-readable location name. # This might be slightly more complicated than it really needs to be. CHECK(local.use_locations) ad = Ad().setRawIPPort(my_ipp) my_ip = ad.getTextIP() # Set my local ip in the state # self.state.local_ip = my_ip # self.state.saveState() skip = False for ip,loc in self.location.items(): if ip == my_ip: skip = True elif loc: # Forget old entries del self.location[ip] # If we already had an entry for this IP, then don't start # another lookup. if skip: return # A location of None indicates that a lookup is in progress self.location[my_ip] = None def cb(hostname): # Use local_config to transform this hostname into a # human-readable location loc = local.hostnameToLocation(hostname) # If we got a location, save it, otherwise dump the # dictionary entry if loc: self.location[my_ip] = loc else: del self.location[my_ip] # Maybe send an info update if self.osm: self.osm.updateMyInfo() # Start lookup ipToHostname(ad).addCallback(cb)
def receivedBlockRequest(self, src_ipp, bhash): try: b = self.cached_blocks[bhash] except KeyError: LOG.warning("Requested block not found") return b.scheduleExpire(self.cached_blocks, bhash) packet = ['bB'] packet.append(self.main.osm.me.ipp) packet.append(struct.pack('!H', len(b.data))) packet.append(b.data) ad = Ad().setRawIPPort(src_ipp) self.main.ph.sendPacket(''.join(packet), ad.getAddrTuple())
def formatMyInfo(self): # Build and return a hacked-up version of my info string. # Get version string ver_string = get_version_string() # Split info string try: info = split_info(self.info) except ValueError: # No info. Just use the offline version tag return "<%s>" % ver_string # Split description into description and <tag> desc, tag = split_tag(info[0]) # Update tag if tag: info[0] = "%s<%s,%s>" % (desc, tag, ver_string) else: info[0] = "%s<%s>" % (desc, ver_string) if local.use_locations: # Try to get my location name. try: ad = Ad().setRawIPPort(self.main.osm.me.ipp) loc = self.main.location[ad.getTextIP()] except (AttributeError, KeyError): loc = None # If I got a location name, splice it into my connection field if loc: # Append location suffix, if it exists suffix = self.main.state.suffix if suffix: loc = '%s|%s' % (loc, suffix) info[2] = loc + info[2][-1:] info = '$'.join(info) if len(info) > 255: self.pushStatus("*** Your info string is too long!") info = '' return info
def handleCmd_INVITE(self, out, args, prefix): if len(args) == 0: osm = self.main.osm if osm: ad = Ad().setRawIPPort(osm.me.ipp) extra_msg = "" else: # If I don't know my own IP, at least fill in a dummy one. ad = Ad().setAddrTuple(("0.0.0.0", self.main.state.udp_port)) extra_msg = " (replace 0.0.0.0 with your real IP address)" out("Tell your friend to enter the following into their client " "to join the network%s:" % extra_msg) out("") out(" !addpeer %s" % ad.getTextIPPort()) out("") return self.syntaxHelp(out, 'INVITE', prefix)
def testHalfModeCloak(self): cloak = HalfModeCloak("pre-", "s33kr1t") self.assertEqual( cloak.maskHostname("c-99-100-123-231.hsd1.ca.comcast.net"), "pre-pkjbqg.ca.comcast.net") self.assertEqual(cloak.maskHostname("foo.bar.dtella.org"), "pre-hsfn1d.bar.dtella.org") self.assertEqual(cloak.maskHostname("hawk-d-999.resnet.purdue.edu"), "pre-doft6g.resnet.purdue.edu") self.assertEqual(cloak.maskIPv4(Ad().setTextIP("0.0.0.0")), "pre-j88.3ss.0.0.IP") self.assertEqual(cloak.maskIPv4(Ad().setTextIP("12.34.56.78")), "pre-6l1.prh.34.12.IP") self.assertEqual(cloak.maskHostname("localhost.localdomain"), "pre-alqii9.localdomain") # Too short/long hostnames should raise an error. self.assertRaises(BadHostnameError, cloak.maskHostname, None) self.assertRaises(BadHostnameError, cloak.maskHostname, "") self.assertRaises(BadHostnameError, cloak.maskHostname, "z" * 51)
def dns_cb(): try: # 2011-08-21: New nodes ignore the value of 'when'. when, ipps = self.state.dns_ipcache except ValueError: pass else: random.shuffle(ipps) for ipp in ipps: ad = Ad().setRawIPPort(ipp) self.state.refreshPeer(ad, 0) self.startInitialContact()
def __init__(self): core.DtellaMain_Base.__init__(self) # State Manager self.state = dtella.common.state.StateManager( self, cfg.file_base + '.state', dtella.common.state.bridge_loadsavers) self.state.initLoad() self.state.persistent = True self.state.udp_port = cfg.udp_port # Add an inital value for my own IP, adding it to the exempt list # if it's offsite. if cfg.myip_hint: ad = Ad().setAddrTuple((cfg.myip_hint, cfg.udp_port)) self.state.addExemptIP(ad) self.addMyIPReport(ad, ad) # Add pre-defined entries to my local cache, and add them to # the exempt list of they're offsite. for text_ipp in cfg.ip_cache: ad = Ad().setTextIPPort(text_ipp) self.state.addExemptIP(ad) self.state.refreshPeer(ad, 0) # Peer Handler self.ph = bridge_server.BridgeServerProtocol(self) # Reverse DNS Manager self.rdns = bridge_server.ReverseDNSManager(self) # DNS Update Manager self.dum = push_dconfig.DynamicConfigUpdateManager(self) # IRC State Manager self.ism = None self.startConnecting()
def handleCmd_ADDPEER(self, out, args, prefix): if len(args) == 1: try: ad = Ad().setTextIPPort(args[0]) except ValueError: pass else: if not ad.port: out("Port number must be nonzero.") elif ad.auth('sx', self.main): self.main.state.refreshPeer(ad, 0) out("Added to peer cache: %s" % ad.getTextIPPort()) # Jump-start stuff if it's not already going self.main.startConnecting() else: out("The address '%s' is not permitted on this network." % ad.getTextIPPort()) return self.syntaxHelp(out, 'ADDPEER', prefix)
def load(self, state, d): # Get IP cache try: ipcache = self.getKey(d) if len(ipcache) % 10 != 0: raise StateError except StateError: ipcache = '' now = time.time() for i in range(0, len(ipcache), 10): ipp, when = struct.unpack('!6sI', ipcache[i:i + 10]) state.refreshPeer(Ad().setRawIPPort(ipp), now - when)
def sendSyncReply(self, src_ipp, cont, uncont): # This gets spliced into the SyncRequestRoutingManager ad = Ad().setRawIPPort(src_ipp) osm = self.main.osm # Build Packet packet = ['bY'] # My IP:Port packet.append(osm.me.ipp) # seqnum, expire time, session id, uptime, flags, hashes, pubkey block_hashes, blocks = self.getStateData(packet) # Contacted Nodes packet.append(struct.pack('!B', len(cont))) packet.extend(cont) # Uncontacted Nodes packet.append(struct.pack('!B', len(uncont))) packet.extend(uncont) # Signature self.signPacket(packet, broadcast=False) # Send it self.main.ph.sendPacket(''.join(packet), ad.getAddrTuple()) # Keep track of the data for a while, # so the node can request it. for bhash, data in zip(block_hashes, blocks): try: b = self.cached_blocks[bhash] except KeyError: b = self.cached_blocks[bhash] = self.CachedBlock(data) b.scheduleExpire(self.cached_blocks, bhash)
def advanceQueue(self): # Only continue if we have a queue, and spare capacity if not (self.dnsq and self.limiter > 0): return self.limiter -= 1 ip, ent = self.dnsq.popleft() def cb(hostname): for ipp in ent.waiting_ipps: self.signOn(ipp, hostname) ent.waiting_ipps.clear() if hostname is None: del self.cache[ip] else: ent.hostname = hostname self.limiter += 1 self.advanceQueue() LOG.debug("Querying %s" % Ad().setRawIP(ip).getTextIP()) ipToHostname(Ad().setRawIP(ip)).addCallback(cb)
def setDNSIPCache(self, data): CHECK(len(data) % 6 == 4) # 2011-08-21: New nodes ignore the value of 'when'. when, = struct.unpack("!I", data[:4]) ipps = [data[i:i + 6] for i in range(4, len(data), 6)] self.dns_ipcache = when, ipps # If DNS contains a foreign IP, add it to the exemption # list, so that it can function as a bridge or cache node. self.exempt_ips.clear() for ipp in ipps: ad = Ad().setRawIPPort(ipp) self.addExemptIP(ad)
def getEntries(self): # Build and return a dict of entries which should be sent to the # dynamic config store. def b64(arg): return binascii.b2a_base64(arg).rstrip() # Dictionary of key=value pairs to return. # Start out with the static entries provided in the config. entries = cfg.dconfig_fixed_entries.copy() osm = self.main.osm # Generate public key hash if cfg.private_key: pubkey = long_to_bytes(RSA.construct(cfg.private_key).n) entries['pkhash'] = b64(md5(pubkey).digest()) # Collect IPPs for the ipcache string GOAL = 10 ipps = set() # Initially add all the exempt IPs, without a port for ip in self.main.state.exempt_ips: ad = Ad() ad.ip = ip ad.port = 0 ipps.add(ad.getRawIPPort()) # Helper function to add an IPP, overriding any portless entries. def add_ipp(ipp): ipps.discard(ipp[:4] + '\0\0') ipps.add(ipp) syncd = (osm and osm.syncd) # Add my own IP. # If I'm a hidden node, then only add it if I'm not online yet. if (not self.main.hide_node) or (not syncd): if osm: add_ipp(osm.me.ipp) else: try: add_ipp(self.main.selectMyIP().getRawIPPort()) except ValueError: pass # Add the IPPs of online nodes if syncd: sec = seconds() def n_uptime(n): uptime = max(0, sec - n.uptime) if n.persist: uptime *= 1.5 return -uptime # Sort nodes by uptime, highest first nodes = osm.nodes[:] nodes.sort(key=n_uptime) # Chop list down to the top eighth or so. del nodes[min(GOAL, len(nodes) // 8):] # Select a random sample from the best nodes. try: nodes = random.sample(nodes, GOAL) except ValueError: pass for n in nodes: add_ipp(n.ipp) if len(ipps) >= GOAL: break # Add the IPPs of offline nodes (if necessary) if len(ipps) < GOAL: for when,ipp in self.main.state.getYoungestPeers(GOAL): add_ipp(ipp) if len(ipps) >= GOAL: break ipcache = list(ipps) random.shuffle(ipcache) ipcache = '\xFF\xFF\xFF\xFF' + ''.join(ipcache) ipcache = b64(self.main.pk_enc.encrypt(ipcache)) entries['ipcache'] = ipcache return entries
def pushSearchRequest(self, ipp, search_string): ad = Ad().setRawIPPort(ipp) self.sendLine("$Search %s %s" % (ad.getTextIPPort(), search_string))
def getEntries(self): # Build and return a dict of entries which should be sent to the # dynamic config store. def b64(arg): return binascii.b2a_base64(arg).rstrip() # Dictionary of key=value pairs to return. # Start out with the static entries provided in the config. entries = cfg.dconfig_fixed_entries.copy() osm = self.main.osm # Generate public key hash if cfg.private_key: pubkey = long_to_bytes(RSA.construct(cfg.private_key).n) entries['pkhash'] = b64(md5(pubkey).digest()) # Collect IPPs for the ipcache string GOAL = 10 ipps = set() # Initially add all the exempt IPs, without a port for ip in self.main.state.exempt_ips: ad = Ad() ad.ip = ip ad.port = 0 ipps.add(ad.getRawIPPort()) # Helper function to add an IPP, overriding any portless entries. def add_ipp(ipp): ipps.discard(ipp[:4] + '\0\0') ipps.add(ipp) syncd = (osm and osm.syncd) # Add my own IP. # If I'm a hidden node, then only add it if I'm not online yet. if (not self.main.hide_node) or (not syncd): if osm: add_ipp(osm.me.ipp) else: try: add_ipp(self.main.selectMyIP()) except ValueError: pass # Add the IPPs of online nodes if syncd: sec = seconds() def n_uptime(n): uptime = max(0, sec - n.uptime) if n.persist: uptime *= 1.5 return -uptime # Sort nodes by uptime, highest first nodes = osm.nodes[:] nodes.sort(key=n_uptime) # Chop list down to the top eighth or so. del nodes[min(GOAL, len(nodes) // 8):] # Select a random sample from the best nodes. try: nodes = random.sample(nodes, GOAL) except ValueError: pass for n in nodes: add_ipp(n.ipp) if len(ipps) >= GOAL: break # Add the IPPs of offline nodes (if necessary) if len(ipps) < GOAL: for when,ipp in self.main.state.getYoungestPeers(GOAL): add_ipp(ipp) if len(ipps) >= GOAL: break ipcache = list(ipps) random.shuffle(ipcache) ipcache = '\xFF\xFF\xFF\xFF' + ''.join(ipcache) ipcache = b64(self.main.pk_enc.encrypt(ipcache)) entries['ipcache'] = ipcache return entries