/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java) * (c) 2003 - 2004 mihi */ package net.i2p.i2ptunnel.udpTunnel; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.util.concurrent.atomic.AtomicLong; import net.i2p.I2PAppContext; import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.crypto.SigType; import net.i2p.data.Destination; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.i2ptunnel.I2PTunnelTask; import net.i2p.i2ptunnel.Logging; import net.i2p.i2ptunnel.udp.*; import net.i2p.util.EventDispatcher; /** * Base client class that sets up an I2P Datagram client destination. * The UDP side is not implemented here, as there are at least * two possibilities: * * 1) UDP side is a "server" * Example: Streamr Consumer * - Configure a destination host and port * - External application sends no data * - Extending class must have a constructor with host and port arguments * * 2) UDP side is a client/server * Example: SOCKS UDP (DNS requests?) * - configure an inbound port and a destination host and port * - External application sends and receives data * - Extending class must have a constructor with host and 2 port arguments * * So the implementing class must create a UDPSource and/or UDPSink, * and must call setSink(). * * @author zzz with portions from welterde's streamr */ public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements Source, Sink { protected I2PAppContext _context; protected Logging l; static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000; private static final AtomicLong __clientId = new AtomicLong(); protected long _clientId; private final Object startLock = new Object(); private final I2PSession _session; private final Source _i2pSource; private final Sink _i2pSink; private final Destination _otherDest; /** * @throws IllegalArgumentException if the I2CP configuration is b0rked so * badly that we cant create a socketManager */ public I2PTunnelUDPClientBase(String destination, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) throws IllegalArgumentException { super("UDPServer", notifyThis, tunnel); _clientId = __clientId.incrementAndGet();; this.l = l; _context = tunnel.getContext(); tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true"); // create i2pclient and destination I2PClient client = I2PClientFactory.createClient(); byte[] key; try { ByteArrayOutputStream out = new ByteArrayOutputStream(1024); SigType stype = I2PClient.DEFAULT_SIGTYPE; String st = tunnel.getClientOptions().getProperty(I2PClient.PROP_SIGTYPE); if (st != null) { SigType type = SigType.parseSigType(st); if (type != null) stype = type; else l.log("Unsupported sig type " + st); } client.createDestination(out, stype); key = out.toByteArray(); } catch(Exception exc) { throw new RuntimeException("failed to create i2p-destination", exc); } // create a session try { ByteArrayInputStream in = new ByteArrayInputStream(key); // FIXME this may not pick up non-default I2CP host/port settings from tunnel _session = client.createSession(in, tunnel.getClientOptions()); connected(_session); } catch(Exception exc) { throw new RuntimeException("failed to create session", exc); } // Setup the source. Always expect raw unverified datagrams. _i2pSource = new I2PSource(_session, false, true); // Setup the sink. Always send repliable datagrams. if (destination != null && destination.length() > 0) { _otherDest = _context.namingService().lookup(destination); if (_otherDest == null) { l.log("Could not resolve " + destination); throw new RuntimeException("failed to create session - could not resolve " + destination); } _i2pSink = new I2PSink(_session, _otherDest, false); } else { _otherDest = null; _i2pSink = new I2PSinkAnywhere(_session, false); } } /** * Actually start working on outgoing connections. * Classes should override to start UDP side as well. * * Not specified in I2PTunnelTask but used in both * I2PTunnelClientBase and I2PTunnelServer so let's * implement it here too. */ public void startRunning() { synchronized (startLock) { try { _session.connect(); } catch(I2PSessionException exc) { throw new RuntimeException("failed to connect session", exc); } start(); startLock.notify(); } open = true; } /** * I2PTunnelTask Methods * * Classes should override to close UDP side as well */ public boolean close(boolean forced) { if (!open) return true; if (_session != null) { try { _session.destroySession(); } catch (I2PSessionException ise) {} } l.log("Closing client " + toString()); open = false; return true; } /** * Source Methods * * Sets the receiver of the UDP datagrams from I2P * Subclass must call this after constructor * and before start() */ public void setSink(Sink s) { _i2pSource.setSink(s); } /** start the source */ public void start() { _i2pSource.start(); } /** * Sink Methods * * @param to - ignored if configured for a single destination * (we use the dest specified in the constructor) * @throws RuntimeException if session is closed */ public void send(Destination to, byte[] data) { _i2pSink.send(to, data); } }