package net.i2p.client.streaming.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.crypto.SigAlgo;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.ConvertToHash;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.Log;
/**
* Centralize the coordination and multiplexing of the local client's streaming.
* There should be one I2PSocketManager for each I2PSession, and if an application
* is sending and receiving data through the streaming library using an
* I2PSocketManager, it should not attempt to call I2PSession's setSessionListener
* or receive any messages with its .receiveMessage
*
* This is what I2PSocketManagerFactory.createManager() returns.
* Direct instantiation by others is deprecated.
*/
public class I2PSocketManagerFull implements I2PSocketManager {
private final I2PAppContext _context;
private final Log _log;
private final I2PSession _session;
private final Set<I2PSession> _subsessions;
private final I2PServerSocketFull _serverSocket;
private StandardServerSocket _realServerSocket;
private final ConnectionOptions _defaultOptions;
private long _acceptTimeout;
private String _name;
private static final AtomicInteger __managerId = new AtomicInteger();
private final ConnectionManager _connectionManager;
private final AtomicBoolean _isDestroyed = new AtomicBoolean();
/**
* Does not support EC
* @since 0.9.21
*/
private static final Set<Hash> _ecUnsupported = new HashSet<Hash>(16);
private static final String[] EC_UNSUPPORTED_HASHES = {
// list from http://zzz.i2p/topics/1682?page=1#p8414
// bzr.welterde.i2p
"Cvs1gCZTTkgD2Z2byh2J9atPmh5~I8~L7BNQnQl0hUE=",
// docs.i2p2.i2p
"WCXV87RdrF6j-mnn6qt7kVSBifHTlPL0PmVMFWwaolo=",
// flibusta.i2p
"yy2hYtqqfl84N9skwdRkeM7baFMXHKyDWU3XRShlEo8=",
// forum.i2p
"3t5Ar2NCTIOId70uzX2bZyJljR0aBogxMEzNyHirB7A=",
// i2jump.i2p
"9vaoGZbOaeqdRK2qEunlwRM9mUSW-I9R4OON35TDKK4=",
// irc.welterde.i2p
"5rjezx4McFk3bNhoJV-NTLlQW1AR~jiUcN6DOWMCCVc=",
// lists.i2p2.i2p
"qwtgoFoMSK0TOtbT4ovBX1jHUzCoZCPzrJVxjKD7RCg=",
// mtn.i2p2.i2p
"X5VDzYaoX9-P6bAWnrVSR5seGLkOeORP2l3Mh4drXPo=",
// nntp.welterde.i2p
"VXwmNIwMy1BcUVmut0oZ72jbWoqFzvxJukmS-G8kAAE=",
// paste.i2p2.i2p
"DoyMyUUgOSTddvRpqYfKHFPPjkkX~iQmResyfjjBYWs=",
// syndie.welterde.i2p
"xMxC54BFgyp-~zzrQI3F8m2CK--9XMcNmSAep6RH4Kk=",
// ugha.i2p
"zsu3WF~QLBxZXH-gHq9MuZE6y8ROZmMF7dA2MbMMKkY=",
// tracker.welterde.i2p
"EVkFgKkrDKyGfI7TIuDmlHoAmvHC~FbnY946DfujR0A=",
// www.i2p2.i2p
"im9gytzKT15mT1sB5LC9bHXCcwytQ4EPcrGQhoam-4w="
};
/**
* Does not support Ed
* @since 0.9.23
*/
private static final Set<Hash> _edUnsupported = new HashSet<Hash>(16);
private static final String[] ED_UNSUPPORTED_HASHES = {
// list from http://zzz.i2p/topics/1682?page=1#p8414
// minus those tested to support Ed
// last tested 2015-11-04
// bzr.welterde.i2p
"Cvs1gCZTTkgD2Z2byh2J9atPmh5~I8~L7BNQnQl0hUE=",
// docs.i2p2.i2p
"WCXV87RdrF6j-mnn6qt7kVSBifHTlPL0PmVMFWwaolo=",
// i2jump.i2p
"9vaoGZbOaeqdRK2qEunlwRM9mUSW-I9R4OON35TDKK4=",
// irc.welterde.i2p
"5rjezx4McFk3bNhoJV-NTLlQW1AR~jiUcN6DOWMCCVc=",
// lists.i2p2.i2p
"qwtgoFoMSK0TOtbT4ovBX1jHUzCoZCPzrJVxjKD7RCg=",
// mtn.i2p2.i2p
"X5VDzYaoX9-P6bAWnrVSR5seGLkOeORP2l3Mh4drXPo=",
// nntp.welterde.i2p
"VXwmNIwMy1BcUVmut0oZ72jbWoqFzvxJukmS-G8kAAE=",
// paste.i2p2.i2p
"DoyMyUUgOSTddvRpqYfKHFPPjkkX~iQmResyfjjBYWs=",
// syndie.welterde.i2p
"xMxC54BFgyp-~zzrQI3F8m2CK--9XMcNmSAep6RH4Kk=",
// tracker.welterde.i2p
"EVkFgKkrDKyGfI7TIuDmlHoAmvHC~FbnY946DfujR0A=",
// www.i2p2.i2p
"im9gytzKT15mT1sB5LC9bHXCcwytQ4EPcrGQhoam-4w="
};
static {
for (int i = 0; i < EC_UNSUPPORTED_HASHES.length; i++) {
String s = EC_UNSUPPORTED_HASHES[i];
Hash h = ConvertToHash.getHash(s);
if (h != null)
_ecUnsupported.add(h);
else
System.out.println("Bad hash " + s);
}
for (int i = 0; i < ED_UNSUPPORTED_HASHES.length; i++) {
String s = ED_UNSUPPORTED_HASHES[i];
Hash h = ConvertToHash.getHash(s);
if (h != null)
_edUnsupported.add(h);
else
System.out.println("Bad hash " + s);
}
}
/** cache of the property to detect changes */
private static volatile String _userDsaList = "";
private static final Set<Hash> _userDsaOnly = new ConcurrentHashSet<Hash>(4);
private static final String PROP_DSALIST = "i2p.streaming.dsalist";
/**
* How long to wait for the client app to accept() before sending back CLOSE?
* This includes the time waiting in the queue. Currently set to 5 seconds.
*/
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
/**
* @deprecated use 4-arg constructor
* @throws UnsupportedOperationException always
*/
@Deprecated
public I2PSocketManagerFull() {
throw new UnsupportedOperationException();
}
/**
* @deprecated use 4-arg constructor
* @throws UnsupportedOperationException always
*/
@Deprecated
public void init(I2PAppContext context, I2PSession session, Properties opts, String name) {
throw new UnsupportedOperationException();
}
/**
* This is what I2PSocketManagerFactory.createManager() returns.
* Direct instantiation by others is deprecated.
*
* @param context non-null
* @param session non-null
* @param opts may be null
* @param name non-null
*/
public I2PSocketManagerFull(I2PAppContext context, I2PSession session, Properties opts, String name) {
_context = context;
_session = session;
_subsessions = new ConcurrentHashSet<I2PSession>(4);
_log = _context.logManager().getLog(I2PSocketManagerFull.class);
_name = name + " " + (__managerId.incrementAndGet());
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
_defaultOptions = new ConnectionOptions(opts);
_connectionManager = new ConnectionManager(_context, _session, _defaultOptions);
_serverSocket = new I2PServerSocketFull(this);
if (_log.shouldLog(Log.INFO)) {
_log.info("Socket manager created. \ndefault options: " + _defaultOptions
+ "\noriginal properties: " + opts);
}
debugInit(context);
}
/**
* Create a copy of the current options, to be used in a setDefaultOptions() call.
*/
public I2PSocketOptions buildOptions() { return buildOptions(null); }
/**
* Create a modified copy of the current options, to be used in a setDefaultOptions() call.
*
* As of 0.9.19, defaults in opts are honored.
*
* @param opts The new options, may be null
*/
public I2PSocketOptions buildOptions(Properties opts) {
ConnectionOptions curOpts = new ConnectionOptions(_defaultOptions);
curOpts.setProperties(opts);
return curOpts;
}
/**
* @return the session, non-null
*/
public I2PSession getSession() {
return _session;
}
/**
* For a server, you must call connect() on the returned object.
* Connecting the primary session does NOT connect any subsessions.
* If the primary session is not connected, connecting a subsession will connect the primary session first.
*
* @return a new subsession, non-null
* @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
* and different signing keys
* @param opts subsession options if any, may be null
* @since 0.9.21
*/
public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException {
if (privateKeyStream == null) {
// We don't actually need the same pubkey in the dest, just in the LS.
// The dest one is unused. But this is how we find the LS keys
// to reuse in RequestLeaseSetMessageHandler.
ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024);
try {
SigType type = getSigType(opts);
if (type != SigType.DSA_SHA1) {
// hassle, have to set up the padding and cert, see I2PClientImpl
throw new I2PSessionException("type " + type + " unsupported");
}
PublicKey pub = _session.getMyDestination().getPublicKey();
PrivateKey priv = _session.getDecryptionKey();
SimpleDataStructure[] keys = _context.keyGenerator().generateSigningKeys(type);
pub.writeBytes(keyStream);
keys[0].writeBytes(keyStream); // signing pub
Certificate.NULL_CERT.writeBytes(keyStream);
priv.writeBytes(keyStream);
keys[1].writeBytes(keyStream); // signing priv
} catch (GeneralSecurityException e) {
throw new I2PSessionException("Error creating keys", e);
} catch (I2PException e) {
throw new I2PSessionException("Error creating keys", e);
} catch (IOException e) {
throw new I2PSessionException("Error creating keys", e);
} catch (RuntimeException e) {
throw new I2PSessionException("Error creating keys", e);
}
privateKeyStream = new ByteArrayInputStream(keyStream.toByteArray());
}
I2PSession rv = _session.addSubsession(privateKeyStream, opts);
boolean added = _subsessions.add(rv);
if (!added) {
// shouldn't happen
_session.removeSubsession(rv);
throw new I2PSessionException("dup");
}
ConnectionOptions defaultOptions = new ConnectionOptions(opts);
int protocol = defaultOptions.getEnforceProtocol() ? I2PSession.PROTO_STREAMING : I2PSession.PROTO_ANY;
rv.addMuxedSessionListener(_connectionManager.getMessageHandler(), protocol, defaultOptions.getLocalPort());
if (_log.shouldLog(Log.WARN))
_log.warn("Added subsession " + rv);
return rv;
}
/**
* @param opts may be null
* @since 0.9.21 copied from I2PSocketManagerFactory
*/
private SigType getSigType(Properties opts) {
if (opts != null) {
String st = opts.getProperty(I2PClient.PROP_SIGTYPE);
if (st != null) {
SigType rv = SigType.parseSigType(st);
if (rv != null && rv.isAvailable())
return rv;
if (rv != null)
st = rv.toString();
_log.logAlways(Log.WARN, "Unsupported sig type " + st +
", reverting to " + I2PClient.DEFAULT_SIGTYPE);
// TODO throw instead?
}
}
return I2PClient.DEFAULT_SIGTYPE;
}
/**
* Remove the subsession
*
* @since 0.9.21
*/
public void removeSubsession(I2PSession session) {
_session.removeSubsession(session);
boolean removed = _subsessions.remove(session);
if (removed) {
if (_log.shouldLog(Log.WARN))
_log.warn("Removeed subsession " + session);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Subsession not found to remove " + session);
}
}
/**
* @return a list of subsessions, non-null, does not include the primary session
* @since 0.9.21
*/
public List<I2PSession> getSubsessions() {
return _session.getSubsessions();
}
public ConnectionManager getConnectionManager() {
return _connectionManager;
}
/**
* The accept() call.
*
* This only listens on the primary session. There is no way to get
* incoming connections on a subsession.
*
* @return connected I2PSocket, or null through 0.9.16, non-null as of 0.9.17
* @throws I2PException if session is closed
* @throws ConnectException (since 0.9.17; I2PServerSocket interface always declared it)
* @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached.
*/
public I2PSocket receiveSocket() throws I2PException, ConnectException, SocketTimeoutException {
verifySession();
Connection con = _connectionManager.getConnectionHandler().accept(_connectionManager.getSoTimeout());
I2PSocketFull sock = new I2PSocketFull(con, _context);
con.setSocket(sock);
return sock;
}
/**
* Ping the specified peer, returning true if they replied to the ping within
* the timeout specified, false otherwise. This call blocks.
*
* Uses the ports from the default options.
*
* TODO There is no way to ping on a subsession.
*
* @param peer
* @param timeoutMs timeout in ms, greater than zero
* @return true on success, false on failure
* @throws IllegalArgumentException
*/
public boolean ping(Destination peer, long timeoutMs) {
if (timeoutMs <= 0)
throw new IllegalArgumentException("bad timeout");
return _connectionManager.ping(peer, _defaultOptions.getLocalPort(),
_defaultOptions.getPort(), timeoutMs);
}
/**
* Ping the specified peer, returning true if they replied to the ping within
* the timeout specified, false otherwise. This call blocks.
*
* Uses the ports specified.
*
* TODO There is no way to ping on a subsession.
*
* @param peer Destination to ping
* @param localPort 0 - 65535
* @param remotePort 0 - 65535
* @param timeoutMs timeout in ms, greater than zero
* @return success or failure
* @throws IllegalArgumentException
* @since 0.9.12
*/
public boolean ping(Destination peer, int localPort, int remotePort, long timeoutMs) {
if (localPort < 0 || localPort > 65535 ||
remotePort < 0 || remotePort > 65535)
throw new IllegalArgumentException("bad port");
if (timeoutMs <= 0)
throw new IllegalArgumentException("bad timeout");
return _connectionManager.ping(peer, localPort, remotePort, timeoutMs);
}
/**
* Ping the specified peer, returning true if they replied to the ping within
* the timeout specified, false otherwise. This call blocks.
*
* Uses the ports specified.
*
* TODO There is no way to ping on a subsession.
*
* @param peer Destination to ping
* @param localPort 0 - 65535
* @param remotePort 0 - 65535
* @param timeoutMs timeout in ms, greater than zero
* @param payload to include in the ping
* @return the payload received in the pong, zero-length if none, null on failure or timeout
* @throws IllegalArgumentException
* @since 0.9.18
*/
public byte[] ping(Destination peer, int localPort, int remotePort, long timeoutMs, byte[] payload) {
if (localPort < 0 || localPort > 65535 ||
remotePort < 0 || remotePort > 65535)
throw new IllegalArgumentException("bad port");
if (timeoutMs <= 0)
throw new IllegalArgumentException("bad timeout");
return _connectionManager.ping(peer, localPort, remotePort, timeoutMs, payload);
}
/**
* How long should we wait for the client to .accept() a socket before
* sending back a NACK/Close?
*
* @param ms milliseconds to wait, maximum
*/
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
public long getAcceptTimeout() { return _acceptTimeout; }
/**
* Update the options on a running socket manager.
* Parameters in the I2PSocketOptions interface may be changed directly
* with the setters; no need to use this method for those.
* This does NOT update the underlying I2CP or tunnel options; use getSession().updateOptions() for that.
*
* TODO There is no way to update the options on a subsession.
*
* @param options as created from a call to buildOptions(properties), non-null
*/
public void setDefaultOptions(I2PSocketOptions options) {
if (!(options instanceof ConnectionOptions))
throw new IllegalArgumentException();
if (_log.shouldLog(Log.WARN))
_log.warn("Changing options from:\n " + _defaultOptions + "\nto:\n " + options);
_defaultOptions.updateAll((ConnectionOptions) options);
_connectionManager.updateOptions();
}
/**
* Current options, not a copy, setters may be used to make changes.
*
* TODO There is no facility to specify the session.
*/
public I2PSocketOptions getDefaultOptions() {
return _defaultOptions;
}
/**
* Returns non-null socket.
* This method does not throw exceptions, but methods on the returned socket
* may throw exceptions if the socket or socket manager is closed.
*
* This only listens on the primary session. There is no way to get
* incoming connections on a subsession.
*
* @return non-null
*/
public I2PServerSocket getServerSocket() {
_connectionManager.setAllowIncomingConnections(true);
return _serverSocket;
}
/**
* Like getServerSocket but returns a real ServerSocket for easier porting of apps.
*
* This only listens on the primary session. There is no way to get
* incoming connections on a subsession.
*
* @since 0.8.4
*/
public synchronized ServerSocket getStandardServerSocket() throws IOException {
if (_realServerSocket == null)
_realServerSocket = new StandardServerSocket(_serverSocket);
_connectionManager.setAllowIncomingConnections(true);
return _realServerSocket;
}
private void verifySession() throws I2PException {
verifySession(_connectionManager.getSession());
}
/** @since 0.9.21 */
private void verifySession(I2PSession session) throws I2PException {
if (_isDestroyed.get())
throw new I2PException("Session was closed");
if (!session.isClosed())
return;
session.connect();
}
/**
* 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 (peer == null)
throw new NullPointerException();
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();
SigAlgo myAlgo = session.getMyDestination().getSigType().getBaseAlgorithm();
if ((myAlgo == SigAlgo.EC && _ecUnsupported.contains(h)) ||
(myAlgo == SigAlgo.EdDSA && _edUnsupported.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;
}
/**
* Update the global user DSA-only list.
* This does not affect the hardcoded Ex_UNSUPPORTED_HASHES lists above,
* the user can only add, not remove.
*
* @since 0.9.21
*/
private void updateUserDsaList() {
String hashes = _context.getProperty(PROP_DSALIST, "");
if (!_userDsaList.equals(hashes)) {
// rebuild _userDsaOnly when property changes
synchronized(_userDsaOnly) {
if (hashes.length() > 0) {
Set<Hash> newSet = new HashSet<Hash>();
StringTokenizer tok = new StringTokenizer(hashes, ",; ");
while (tok.hasMoreTokens()) {
String hashstr = tok.nextToken();
Hash hh = ConvertToHash.getHash(hashstr);
if (hh != null)
newSet.add(hh);
else
_log.error("Bad " + PROP_DSALIST + " entry: " + hashstr);
}
_userDsaOnly.addAll(newSet);
_userDsaOnly.retainAll(newSet);
_userDsaList = hashes;
} else {
_userDsaOnly.clear();
_userDsaList = "";
}
}
}
}
/**
* Create a new connected socket. Blocks until the socket is created,
* unless the connectDelay option (i2p.streaming.connectDelay) is
* set and greater than zero in the default options. 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
*
* @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) throws I2PException, NoRouteToHostException {
return connect(peer, _defaultOptions);
}
/**
* Like connect() but returns a real Socket, and throws only IOE,
* for easier porting of apps.
* @since 0.8.4
*/
public Socket connectToSocket(Destination peer) throws IOException {
return connectToSocket(peer, _defaultOptions);
}
/**
* Like connect() but returns a real Socket, and throws only IOE,
* for easier porting of apps.
* @param timeout ms if > 0, forces blocking (disables connectDelay)
* @since 0.8.4
*/
public Socket connectToSocket(Destination peer, int timeout) throws IOException {
ConnectionOptions opts = new ConnectionOptions(_defaultOptions);
opts.setConnectTimeout(timeout);
if (timeout > 0)
opts.setConnectDelay(-1);
return connectToSocket(peer, opts);
}
/**
* Like connect() but returns a real Socket, and throws only IOE,
* for easier porting of apps.
* @param options may be null
* @since 0.8.4
*/
private Socket connectToSocket(Destination peer, I2PSocketOptions options) throws IOException {
try {
I2PSocket sock = connect(peer, options);
return new StandardSocket(sock);
} catch (I2PException i2pe) {
IOException ioe = new IOException("connect fail");
ioe.initCause(i2pe);
throw ioe;
}
}
/**
* Destroy the socket manager, freeing all the associated resources. This
* method will block until all the managed sockets are closed.
*
* CANNOT be restarted.
*/
public void destroySocketManager() {
if (!_isDestroyed.compareAndSet(false,true)) {
// shouldn't happen, log a stack trace to find out why it happened
_log.logCloseLoop("I2PSocketManager", getName());
return;
}
_connectionManager.setAllowIncomingConnections(false);
_connectionManager.shutdown();
if (!_subsessions.isEmpty()) {
for (I2PSession sess : _subsessions) {
removeSubsession(sess);
}
}
// should we destroy the _session too?
// yes, since the old lib did (and SAM wants it to, and i dont know why not)
if ( (_session != null) && (!_session.isClosed()) ) {
try {
_session.destroySession();
} catch (I2PSessionException ise) {
_log.warn("Unable to destroy the session", ise);
}
PcapWriter pcap = null;
synchronized(_pcapInitLock) {
pcap = pcapWriter;
}
if (pcap != null)
pcap.flush();
}
}
/**
* Has the socket manager been destroyed?
*
* @since 0.9.9
*/
public boolean isDestroyed() {
return _isDestroyed.get();
}
/**
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
*
* @return set of currently connected I2PSockets
*/
public Set<I2PSocket> listSockets() {
Set<Connection> connections = _connectionManager.listConnections();
Set<I2PSocket> rv = new HashSet<I2PSocket>(connections.size());
for (Connection con : connections) {
if (con.getSocket() != null)
rv.add(con.getSocket());
}
return rv;
}
/**
* For logging / diagnostics only
*/
public String getName() { return _name; }
/**
* For logging / diagnostics only
*/
public void setName(String name) { _name = name; }
public void addDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
_connectionManager.getMessageHandler().addDisconnectListener(lsnr);
}
public void removeDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
_connectionManager.getMessageHandler().removeDisconnectListener(lsnr);
}
private static final Object _pcapInitLock = new Object();
private static boolean _pcapInitialized;
static PcapWriter pcapWriter;
static final String PROP_PCAP = "i2p.streaming.pcap";
private static final String PCAP_FILE = "streaming.pcap";
private static void debugInit(I2PAppContext ctx) {
if (!ctx.getBooleanProperty(PROP_PCAP))
return;
synchronized(_pcapInitLock) {
if (!_pcapInitialized) {
try {
pcapWriter = new PcapWriter(ctx, PCAP_FILE);
} catch (java.io.IOException ioe) {
System.err.println("pcap init ioe: " + ioe);
}
_pcapInitialized = true;
}
}
}
}