Beispiel #1
0
        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)
Beispiel #2
0
        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)
Beispiel #3
0
    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)
Beispiel #4
0
    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)
Beispiel #5
0
    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)
Beispiel #6
0
    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)
Beispiel #7
0
    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())
Beispiel #8
0
    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())
Beispiel #9
0
    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
Beispiel #10
0
    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
Beispiel #11
0
    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)
Beispiel #13
0
    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)
Beispiel #14
0
        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()
Beispiel #15
0
    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()
Beispiel #16
0
    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)
Beispiel #17
0
    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)
Beispiel #18
0
    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)
Beispiel #19
0
    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)
Beispiel #20
0
    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)
Beispiel #21
0
    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)
Beispiel #22
0
    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)
Beispiel #23
0
    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
Beispiel #24
0
 def pushSearchRequest(self, ipp, search_string):
     ad = Ad().setRawIPPort(ipp)
     self.sendLine("$Search %s %s" % (ad.getTextIPPort(), search_string))
Beispiel #25
0
 def pushSearchRequest(self, ipp, search_string):
     ad = Ad().setRawIPPort(ipp)
     self.sendLine("$Search %s %s" % (ad.getTextIPPort(), search_string))
Beispiel #26
0
    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