@Override public Destination lookup(String hostname) { Destination rv = super.lookup(hostname); if (rv != null) { hostname = hostname.toLowerCase(Locale.US); // If it's long, assume it's a key. if (hostname.length() < 516 && hostname.endsWith(".i2p") && !hostname.endsWith(".b32.i2p")) { File f = new File(_context.getRouterDir(), DEFAULT_HOSTS_FILE); if ((f.exists()) && (f.canWrite())) { synchronized (this) { FileOutputStream fos = null; try { fos = new FileOutputStream(f, true); String line = hostname + '=' + rv.toBase64() + System.getProperty("line.separator"); fos.write(DataHelper.getASCII(line)); } catch (IOException ioe) { System.err.println("Error appending: " + ioe); } finally { if (fos != null) try { fos.close(); } catch (IOException cioe) { } } } } } } return rv; }
private void x_startTorrent() { boolean ok = _util.connect(); if (!ok) fatal("Unable to connect to I2P"); if (coordinator == null) { I2PServerSocket serversocket = _util.getServerSocket(); if (serversocket == null) fatal("Unable to listen for I2P connections"); else { Destination d = serversocket.getManager().getSession().getMyDestination(); if (_log.shouldLog(Log.INFO)) _log.info( "Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64()); } if (_log.shouldLog(Log.INFO)) _log.info("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient"); activity = "Collecting pieces"; coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this); coordinator.setUploaded(savedUploaded); if (_peerCoordinatorSet != null) { // multitorrent _peerCoordinatorSet.add(coordinator); } else { // single torrent acceptor = new ConnectionAcceptor(_util, new PeerAcceptor(coordinator)); } // TODO pass saved closest DHT nodes to the tracker? or direct to the coordinator? trackerclient = new TrackerClient(_util, meta, additionalTrackerURL, coordinator, this); } // ensure acceptor is running when in multitorrent if (_peerCoordinatorSet != null && acceptor != null) { acceptor.startAccepting(); } stopped = false; if (coordinator.halted()) { coordinator.restart(); if (_peerCoordinatorSet != null) _peerCoordinatorSet.add(coordinator); } if (!trackerclient.started()) { trackerclient.start(); } else if (trackerclient.halted()) { if (storage != null) { try { storage.reopen(); } catch (IOException ioe) { try { storage.close(); } catch (IOException ioee) { ioee.printStackTrace(); } fatal("Could not reopen storage", ioe); } } trackerclient.start(); } else { if (_log.shouldLog(Log.INFO)) _log.info("NOT starting TrackerClient???"); } }
public AddressBean getLookup() { if (this.detail == null) return null; if (isDirect()) { // go to some trouble to make this work for the published addressbook this.filter = this.detail.substring(0, 1); this.search = this.detail; // we don't want the messages, we just want to populate entries super.getLoadBookMessages(); for (int i = 0; i < this.entries.length; i++) { if (this.search.equals(this.entries[i].getName())) return this.entries[i]; } return null; } Properties nsOptions = new Properties(); Properties outProps = new Properties(); nsOptions.setProperty("list", getFileName()); Destination dest = getNamingService().lookup(this.detail, nsOptions, outProps); if (dest == null) return null; AddressBean rv = new AddressBean(this.detail, dest.toBase64()); rv.setProperties(outProps); return rv; }
public void readBytes(InputStream rawConfig) throws DataFormatException, IOException { _destination = Destination.create(rawConfig); _options = DataHelper.readProperties(rawConfig); _creationDate = DataHelper.readDate(rawConfig); _signature = new Signature(); _signature.readBytes(rawConfig); }
@Override public byte[] toByteArray() { ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(arrayOutputStream); try { writeHeader(dataStream); dataStream.writeShort(peers.size()); for (Destination peer : peers) // write the first 384 bytes (the two public keys) dataStream.write(peer.toByteArray(), 0, 384); } catch (IOException e) { log.error("Can't write to ByteArrayOutputStream.", e); } return arrayOutputStream.toByteArray(); }
public void notifyStreamIncomingConnection(Destination d) throws IOException { if (getStreamSession() == null) { _log.error("BUG! Received stream connection, but session is null!"); throw new NullPointerException("BUG! STREAM session is null!"); } if (!writeString(d.toBase64() + "\n")) { throw new IOException("Error notifying connection to SAM client"); } }
/* Parse and execute a NAMING message */ protected boolean execNamingMessage(String opcode, Properties props) { if (opcode.equals("LOOKUP")) { if (props == null) { _log.debug("No parameters specified in NAMING LOOKUP message"); return false; } String name = props.getProperty("NAME"); if (name == null) { _log.debug("Name to resolve not specified in NAMING message"); return false; } Destination dest = null; if (name.equals("ME")) { if (getRawSession() != null) { dest = getRawSession().getDestination(); } else if (getStreamSession() != null) { dest = getStreamSession().getDestination(); } else if (getDatagramSession() != null) { dest = getDatagramSession().getDestination(); } else { _log.debug("Lookup for SESSION destination, but session is null"); return false; } } else { try { dest = SAMUtils.getDest(name); } catch (DataFormatException e) { } } if (dest == null) { return writeString("NAMING REPLY RESULT=KEY_NOT_FOUND NAME=" + name + "\n"); } return writeString( "NAMING REPLY RESULT=OK NAME=" + name + " VALUE=" + dest.toBase64() + "\n"); } else { _log.debug("Unrecognized NAMING message opcode: \"" + opcode + "\""); return false; } }
public void notifyStreamIncomingConnection(int id, Destination d) throws IOException { if (getStreamSession() == null) { _log.error("BUG! Received stream connection, but session is null!"); return; } if (!writeString("STREAM CONNECTED DESTINATION=" + d.toBase64() + " ID=" + id + "\n")) { throw new IOException("Error notifying connection to SAM client"); } }
/** * Create a new connected socket. Blocks until the socket is created, unless the connectDelay * option (i2p.streaming.connectDelay) is set and greater than zero. If so this will return * immediately, and the client may quickly write initial data to the socket and this data will be * bundled in the SYN packet. * * @param peer Destination to connect to * @param options I2P socket options to be used for connecting, may be null * @return I2PSocket if successful * @throws NoRouteToHostException if the peer is not found or not reachable * @throws I2PException if there is some other I2P-related problem */ public I2PSocket connect(Destination peer, I2PSocketOptions options) throws I2PException, NoRouteToHostException { if (options == null) options = _defaultOptions; ConnectionOptions opts = null; if (options instanceof ConnectionOptions) opts = new ConnectionOptions((ConnectionOptions) options); else opts = new ConnectionOptions(options); if (_log.shouldLog(Log.INFO)) _log.info( "Connecting to " + peer.calculateHash().toBase64().substring(0, 6) + " with options: " + opts); // pick the subsession here I2PSession session = _session; if (!_subsessions.isEmpty()) { updateUserDsaList(); Hash h = peer.calculateHash(); if (_dsaOnly.contains(h) || (!_userDsaOnly.isEmpty() && _userDsaOnly.contains(h))) { // FIXME just taking the first one for now for (I2PSession sess : _subsessions) { if (sess.getMyDestination().getSigType() == SigType.DSA_SHA1) { session = sess; break; } } } } verifySession(session); // the following blocks unless connect delay > 0 Connection con = _connectionManager.connect(peer, opts, session); if (con == null) throw new TooManyStreamsException("Too many streams, max " + _defaultOptions.getMaxConns()); I2PSocketFull socket = new I2PSocketFull(con, _context); con.setSocket(socket); if (con.getConnectionError() != null) { con.disconnect(false); throw new NoRouteToHostException(con.getConnectionError()); } return socket; }
public void writeBytes(OutputStream out) throws DataFormatException, IOException { if ((_destination == null) || (_options == null) || (_signature == null) || (_creationDate == null)) throw new DataFormatException("Not enough data to create the session config"); _destination.writeBytes(out); DataHelper.writeProperties(out, _options, true); // UTF-8 DataHelper.writeDate(out, _creationDate); _signature.writeBytes(out); }
/** Verify with the "olddest" property's public key using the "oldsig" property */ public boolean hasValidInnerSig() { if (props == null || name == null || dest == null) return false; boolean rv = false; // don't cache result if (true) { StringWriter buf = new StringWriter(1024); String sig = props.getProperty(PROP_OLDSIG); String olddest = props.getProperty(PROP_OLDDEST); if (sig == null || olddest == null) return false; buf.append(name); buf.append(KV_SEPARATOR); buf.append(dest); try { writeProps(buf, true, true); } catch (IOException ioe) { // won't happen return false; } byte[] sdata = Base64.decode(sig); if (sdata == null) return false; Destination d; try { d = new Destination(olddest); } catch (DataFormatException dfe) { return false; } SigningPublicKey spk = d.getSigningPublicKey(); SigType type = spk.getType(); if (type == null) return false; Signature s; try { s = new Signature(type, sdata); } catch (IllegalArgumentException iae) { return false; } rv = DSAEngine.getInstance().verifySignature(s, DataHelper.getUTF8(buf.toString()), spk); } return rv; }
/** connect to the given destination */ I2PSocket connect(PeerID peer) throws IOException { I2PSocketManager mgr = _manager; if (mgr == null) throw new IOException("No socket manager"); Destination addr = peer.getAddress(); if (addr == null) throw new IOException("Null address"); if (addr.equals(getMyDestination())) throw new IOException("Attempt to connect to myself"); Hash dest = addr.calculateHash(); if (_banlist.contains(dest)) throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are banlisted"); try { // TODO opts.setPort(xxx); connect(addr, opts) // DHT moved above 6881 in 0.9.9 I2PSocket rv = _manager.connect(addr); if (rv != null) _banlist.remove(dest); return rv; } catch (I2PException ie) { _banlist.add(dest); _context.simpleScheduler().addEvent(new Unbanlist(dest), 10 * 60 * 1000); IOException ioe = new IOException("Unable to reach the peer " + peer); ioe.initCause(ie); throw ioe; } }
// SAMDatagramReceiver implementation public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException { if (getDatagramSession() == null) { _log.error("BUG! Received datagram bytes, but session is null!"); throw new NullPointerException("BUG! DATAGRAM session is null!"); } ByteArrayOutputStream msg = new ByteArrayOutputStream(); String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64() + " SIZE=" + data.length + "\n"; msg.write(msgText.getBytes("ISO-8859-1")); if (_log.shouldLog(Log.DEBUG)) _log.debug("sending to client: " + msgText); msg.write(data); msg.flush(); writeBytes(ByteBuffer.wrap(msg.toByteArray())); }
/** * Unused? see MuxedImpl override * * @param keyUsed unused - no end-to-end crypto * @param tagsSent unused - no end-to-end crypto */ public boolean sendMessage( Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires) throws I2PSessionException { if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message"); if (isClosed()) throw new I2PSessionException("Already closed"); updateActivity(); // Sadly there is no way to send something completely uncompressed in a backward-compatible way, // so we have to still send it in a gzip format, which adds 23 bytes (2.4% for a 960-byte msg) // (10 byte header + 5 byte block header + 8 byte trailer) // In the future we can add a one-byte magic number != 0x1F to signal an uncompressed msg // (Gzip streams start with 0x1F 0x8B 0x08) // assuming we don't need the CRC-32 that comes with gzip (do we?) // Maybe implement this soon in receiveMessage() below so we are ready // in case we ever make an incompatible network change. // This would save 22 of the 23 bytes and a little CPU. boolean sc = shouldCompress(size); if (sc) payload = DataHelper.compress(payload, offset, size); else payload = DataHelper.compress(payload, offset, size, DataHelper.NO_COMPRESSION); // else throw new IllegalStateException("we need to update sendGuaranteed to support partial // send"); int compressed = payload.length; if (_log.shouldLog(Log.INFO)) { String d = dest.calculateHash().toBase64().substring(0, 4); _log.info( "sending message to: " + d + " compress? " + sc + " sizeIn=" + size + " sizeOut=" + compressed); } _context.statManager().addRateData("i2cp.tx.msgCompressed", compressed, 0); _context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0); if (_noEffort) return sendNoEffort(dest, payload, expires, 0); else return sendBestEffort(dest, payload, keyUsed, tagsSent, expires); }
public void receiveDatagramBytes( Destination sender, byte[] data, int proto, int fromPort, int toPort) throws IOException { if (this.clientAddress == null) { this.handler.receiveDatagramBytes(sender, data, proto, fromPort, toPort); } else { StringBuilder buf = new StringBuilder(600); buf.append(sender.toBase64()); if ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) { buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort); } buf.append('\n'); String msg = buf.toString(); ByteBuffer msgBuf = ByteBuffer.allocate(msg.length() + data.length); msgBuf.put(DataHelper.getASCII(msg)); msgBuf.put(data); msgBuf.flip(); this.server.send(this.clientAddress, msgBuf); } }
// SAMDatagramReceiver implementation public void receiveDatagramBytes( Destination sender, byte data[], int proto, int fromPort, int toPort) throws IOException { if (getDatagramSession() == null) { _log.error("BUG! Received datagram bytes, but session is null!"); return; } ByteArrayOutputStream msg = new ByteArrayOutputStream(100 + data.length); String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64() + " SIZE=" + data.length; msg.write(DataHelper.getASCII(msgText)); if ((verMajor == 3 && verMinor >= 2) || verMajor > 3) { msgText = " FROM_PORT=" + fromPort + " TO_PORT=" + toPort; msg.write(DataHelper.getASCII(msgText)); } msg.write((byte) '\n'); if (_log.shouldLog(Log.DEBUG)) _log.debug("sending to client: " + msgText); msg.write(data); msg.flush(); writeBytes(ByteBuffer.wrap(msg.toByteArray())); }
private byte[] getBytes() { if (_destination == null) return null; if (_options == null) return null; if (_creationDate == null) return null; ByteArrayOutputStream out = new ByteArrayOutputStream(); try { // _log.debug("PubKey size for destination: " + _destination.getPublicKey().getData().length); // _log.debug("SigningKey size for destination: " + // _destination.getSigningPublicKey().getData().length); _destination.writeBytes(out); DataHelper.writeProperties(out, _options, true); // UTF-8 DataHelper.writeDate(out, _creationDate); } catch (IOException ioe) { _log.error("IOError signing", ioe); return null; } catch (DataFormatException dfe) { _log.error("Error writing out the bytes for signing/verification", dfe); return null; } return out.toByteArray(); }
public static void notifyStreamIncomingConnection(SocketChannel client, Destination d) throws IOException { if (!writeString(d.toBase64() + "\n", client)) { throw new IOException("Error notifying connection to SAM client"); } }
public String lookup(String name) { Destination dest = getDestination(name); if (dest == null) return null; return dest.toBase64(); }
String getOurIPString() { Destination dest = getMyDestination(); if (dest != null) return dest.toBase64(); return "unknown"; }
/** * Receive an incoming connection (built from a received SYN) Non-SYN packets with a zero * SendStreamID may also be queued here so that they don't get thrown away while the SYN packet * before it is queued. * * @param timeoutMs max amount of time to wait for a connection (if less than 1ms, wait * indefinitely) * @return connection received, or null if there was a timeout or the handler was shut down */ public Connection accept(long timeoutMs) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Accept(" + timeoutMs + ") called"); long expiration = timeoutMs + _context.clock().now(); while (true) { if ((timeoutMs > 0) && (expiration < _context.clock().now())) return null; if (!_active) { // fail all the ones we had queued up while (true) { Packet packet = _synQueue.poll(); // fails immediately if empty if (packet == null || packet.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST) break; sendReset(packet); } return null; } Packet syn = null; while (_active && syn == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug( "Accept(" + timeoutMs + "): active=" + _active + " queue: " + _synQueue.size()); if (timeoutMs <= 0) { try { syn = _synQueue.take(); // waits forever } catch (InterruptedException ie) { } // { break;} } else { long remaining = expiration - _context.clock().now(); // (dont think this applies anymore for LinkedBlockingQueue) // BUGFIX // The specified amount of real time has elapsed, more or less. // If timeout is zero, however, then real time is not taken into consideration // and the thread simply waits until notified. if (remaining < 1) break; try { syn = _synQueue.poll(remaining, TimeUnit.MILLISECONDS); // waits the specified time max } catch (InterruptedException ie) { } break; } } if (syn != null) { if (syn.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST) return null; // deal with forged / invalid syn packets in _manager.receiveConnection() // Handle both SYN and non-SYN packets in the queue if (syn.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { // We are single-threaded here, so this is // a good place to check for dup SYNs and drop them Destination from = syn.getOptionalFrom(); if (from == null) { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping SYN packet with no FROM: " + syn); // drop it continue; } Connection oldcon = _manager.getConnectionByOutboundId(syn.getReceiveStreamId()); if (oldcon != null) { // His ID not guaranteed to be unique to us, but probably is... // only drop it on a destination match too if (from.equals(oldcon.getRemotePeer())) { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping dup SYN: " + syn); continue; } } Connection con = _manager.receiveConnection(syn); if (con != null) return con; } else { reReceivePacket(syn); // ... and keep looping } } // keep looping... } }
/** Perform actions, returning messages about this. */ @Override public String getMessages() { if (isDirect()) return super.getMessages(); // Loading config and addressbook moved into getLoadBookMessages() String message = ""; if( action != null ) { Properties nsOptions = new Properties(); // only blockfile needs this nsOptions.setProperty("list", getFileName()); if (_context.getBooleanProperty(PROP_PW_ENABLE) || (serial != null && serial.equals(lastSerial))) { boolean changed = false; if (action.equals(_("Add")) || action.equals(_("Replace"))) { if(hostname != null && destination != null) { try { // throws IAE with translated message String host = AddressBean.toASCII(hostname); String displayHost = host.equals(hostname) ? hostname : hostname + " (" + host + ')'; Properties outProperties= new Properties(); Destination oldDest = getNamingService().lookup(host, nsOptions, outProperties); if (oldDest != null && destination.equals(oldDest.toBase64())) { message = _("Host name {0} is already in address book, unchanged.", displayHost); } else if (oldDest != null && !action.equals(_("Replace"))) { message = _("Host name {0} is already in address book with a different destination. Click \"Replace\" to overwrite.", displayHost); } else { try { Destination dest = new Destination(destination); if (oldDest != null) { nsOptions.putAll(outProperties); nsOptions.setProperty("m", Long.toString(_context.clock().now())); } nsOptions.setProperty("s", _("Manually added via SusiDNS")); boolean success = getNamingService().put(host, dest, nsOptions); if (success) { changed = true; if (oldDest == null) message = _("Destination added for {0}.", displayHost); else message = _("Destination changed for {0}.", displayHost); if (!host.endsWith(".i2p")) message += "<br>" + _("Warning - host name does not end with \".i2p\""); // clear form hostname = null; destination = null; } else { message = _("Failed to add Destination for {0} to naming service {1}", displayHost, getNamingService().getName()) + "<br>"; } } catch (DataFormatException dfe) { message = _("Invalid Base 64 destination."); } } } catch (IllegalArgumentException iae) { message = iae.getMessage(); if (message == null) message = _("Invalid host name \"{0}\".", hostname); } } else { message = _("Please enter a host name and destination"); } // clear search when adding search = null; } else if (action.equals(_("Delete Selected")) || action.equals(_("Delete Entry"))) { String name = null; int deleted = 0; for (String n : deletionMarks) { boolean success = getNamingService().remove(n, nsOptions); String uni = AddressBean.toUnicode(n); String displayHost = uni.equals(n) ? n : uni + " (" + n + ')'; if (!success) { message += _("Failed to delete Destination for {0} from naming service {1}", displayHost, getNamingService().getName()) + "<br>"; } else if (deleted++ == 0) { changed = true; name = displayHost; } } if( changed ) { if (deleted == 1) // parameter is a host name message += _("Destination {0} deleted.", name); else // parameter will always be >= 2 message = ngettext("1 destination deleted.", "{0} destinations deleted.", deleted); } else { message = _("No entries selected to delete."); } // clear search when deleting if (action.equals(_("Delete Entry"))) search = null; } if( changed ) { message += "<br>" + _("Address book saved."); } } else { message = _("Invalid form submission, probably because you used the \"back\" or \"reload\" button on your browser. Please resubmit.") + ' ' + _("If the problem persists, verify that you have cookies enabled in your browser."); } } action = null; if( message.length() > 0 ) message = "<p class=\"messages\">" + message + "</p>"; return message; }