propagate from branch 'i2p.i2p.zzz.test' (head dc29b32afe515f704985a4f92cda6e28a65ccdc5)

to branch 'i2p.i2p' (head fb38016f22528778128e22269b8f256c8c640466)
This commit is contained in:
zzz
2010-01-17 13:42:33 +00:00
40 changed files with 1869 additions and 2054 deletions

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">
<file>file:/usblv/NetBeansProjects/i2p.i2p/apps/BOB/src/net/i2p/BOB/BOB.java</file>
</open-files>
</project-private>

View File

@@ -561,7 +561,7 @@ public class SnarkManager implements Snark.CompleteListener {
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
return _("Too many files in \"{0}\" ({1}), deleting it!", info.getName(), files.size());
} else if ( (files == null) && (info.getName().endsWith(".torrent")) ) {
return _("Torrent file \"{0}\" cannot end in '.torrent', deleting it!", info.getName());
return _("Torrent file \"{0}\" cannot end in \".torrent\", deleting it!", info.getName());
} else if (info.getPieces() <= 0) {
return _("No pieces in \"{0}\", deleting it!", info.getName());
} else if (info.getPieces() > Storage.MAX_PIECES) {

View File

@@ -1,72 +0,0 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Read what i2ptunnel logs, and expose it in a buffer
*
*/
class BufferLogger implements Logging {
private final static Log _log = new Log(BufferLogger.class);
private ByteArrayOutputStream _baos; // FIXME should be final and use a factory. FIXME
private boolean _ignore;
/**
* Constructs a buffered logger.
*/
public BufferLogger() {
_baos = new ByteArrayOutputStream(512);
_ignore = false;
}
private final static String EMPTY = "";
/**
* Retrieves the buffer
* @return the buffer
*/
public String getBuffer() {
if (_ignore)
return EMPTY;
return new String(_baos.toByteArray());
}
/**
* We don't care about anything else the logger receives. This is useful
* for loggers passed in to servers and clients, since they will continue
* to add info to the logger, but if we're instantiated by the tunnel manager,
* its likely we only care about the first few messages it sends us.
*
*/
public void ignoreFurtherActions() {
_ignore = true;
synchronized (_baos) {
_baos.reset();
}
_baos = null;
}
/**
* Pass in some random data
* @param s String containing what we're logging.
*/
public void log(String s) {
if (_ignore) return;
if (s != null) {
_log.debug("logging [" + s + "]");
try {
_baos.write(s.getBytes());
_baos.write('\n');
} catch (IOException ioe) {
_log.error("Error logging [" + s + "]");
}
}
}
}

View File

@@ -236,6 +236,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
runServer(args, l);
} else if ("httpserver".equals(cmdname)) {
runHttpServer(args, l);
} else if ("httpbidirserver".equals(cmdname)) {
runHttpBidirServer(args, l);
} else if ("ircserver".equals(cmdname)) {
runIrcServer(args, l);
} else if ("textserver".equals(cmdname)) {
@@ -300,6 +302,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("ping <args>");
l.log("server <host> <port> <privkeyfile>");
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
l.log("httpbidirserver <host> <port> <proxyport> <spoofedhost> <privkeyfile>");
l.log("textserver <host> <port> <privkey>");
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
@@ -503,6 +506,80 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
/**
* Run the HTTP server pointing at the host and port specified using the private i2p
* destination loaded from the specified file, replacing the HTTP headers
* so that the Host: specified is the one spoofed. Also runs an HTTP proxy for
* bidirectional communications on the same tunnel destination.<p />
*
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
*
* @param args {hostname, portNumber, proxyPortNumber, spoofedHost, privKeyFilename}
* @param l logger to receive events and output
*/
public void runHttpBidirServer(String args[], Logging l) {
if (args.length == 5) {
InetAddress serverHost = null;
int portNum = -1;
int port2Num = -1;
File privKeyFile = null;
try {
serverHost = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
try {
port2Num = Integer.parseInt(args[2]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[2], nfe);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
String spoofedHost = args[3];
privKeyFile = new File(args[4]);
if (!privKeyFile.isAbsolute())
privKeyFile = new File(_context.getConfigDir(), args[4]);
if (!privKeyFile.canRead()) {
l.log("private key file does not exist");
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[4]);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
I2PTunnelHTTPBidirServer serv = new I2PTunnelHTTPBidirServer(serverHost, portNum, port2Num, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", Integer.valueOf(serv.getId()));
return;
} else {
l.log("httpserver <host> <port> <proxyport> <spoofedhost> <privkeyfile>");
l.log(" creates a bidirectional HTTP server that sends all incoming data\n"
+ " of its destination to host:port., filtering the HTTP\n"
+ " headers so it looks like the request is to the spoofed host,"
+ " and listens to host:proxyport to proxy HTTP requests.");
notifyEvent("serverTaskId", Integer.valueOf(-1));
}
}
/**
* Run the server pointing at the host and port specified using the private i2p
* destination loaded from the given base64 stream. <p />

View File

@@ -67,7 +67,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// private Object conLock = new Object();
/** List of Socket for those accept()ed but not yet started up */
private List _waitingSockets = new ArrayList(); // FIXME should be final and use a factory. FIXME
protected final List _waitingSockets = new ArrayList(4); // FIXME should be final and use a factory. FIXME
/** How many connections will we allow to be in the process of being built at once? */
private int _numConnectionBuilders;
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
@@ -89,12 +89,52 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private static final int DEFAULT_NUM_CONNECTION_BUILDERS = 5;
private static final int DEFAULT_MAX_WAIT_TIME = 30*1000;
//public I2PTunnelClientBase(int localPort, boolean ownDest,
// Logging l) {
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
//}
// true if we are chained from a server.
private boolean chained = false;
public I2PTunnelClientBase(int localPort, Logging l, I2PSocketManager sktMgr,
I2PTunnel tunnel, EventDispatcher notifyThis, long clientId )
throws IllegalArgumentException {
super(localPort + " (uninitialized)", notifyThis, tunnel);
chained = true;
sockMgr = sktMgr;
_clientId = clientId;
this.localPort = localPort;
this.l = l;
this.handlerName = handlerName + _clientId;
_ownDest = true; // == ! shared client
_context = tunnel.getContext();
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
Thread t = new I2PAppThread(this);
t.setName("Client " + _clientId);
listenerReady = false;
t.start();
open = true;
synchronized (this) {
while (!listenerReady && open) {
try {
wait();
} catch (InterruptedException e) {
// ignore
}
}
}
configurePool(tunnel);
if (open && listenerReady) {
l.log("Ready! Port " + getLocalPort());
notifyEvent("openBaseClientResult", "ok");
} else {
l.log("Error listening - please see the logs!");
notifyEvent("openBaseClientResult", "error");
}
}
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l,
EventDispatcher notifyThis, String handlerName,
I2PTunnel tunnel) throws IllegalArgumentException {
@@ -188,7 +228,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
*
*/
private void configurePool(I2PTunnel tunnel) {
_waitingSockets = new ArrayList(4);
//_waitingSockets = new ArrayList(4);
Properties opts = tunnel.getClientOptions();
String maxWait = opts.getProperty(PROP_MAX_WAIT_TIME, DEFAULT_MAX_WAIT_TIME+"");
@@ -559,10 +599,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
return false;
}
I2PSession session = sockMgr.getSession();
if (session != null) {
getTunnel().removeSession(session);
}
if (!chained) {
I2PSession session = sockMgr.getSession();
if (session != null) {
getTunnel().removeSession(session);
}
} // else the app chaining to this one closes it!
}
l.log("Closing client " + toString());
open = false;

View File

@@ -0,0 +1,54 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
* http://sam.zoy.org/wtfpl/
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
*/
package net.i2p.i2ptunnel;
// import java.util.ArrayList;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.util.EventDispatcher;
/**
* Reuse HTTP server's I2PSocketManager for a proxy with no outproxy capability.
*
* @author sponge
*/
public class I2PTunnelHTTPBidirProxy extends I2PTunnelHTTPClient implements Runnable {
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelHTTPBidirProxy(int localPort, Logging l, I2PSocketManager sockMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) {
super(localPort, l, sockMgr, tunnel, notifyThis, clientId);
// proxyList = new ArrayList();
setName(getLocalPort() + " -> HTTPClient [NO PROXIES]");
startRunning();
notifyEvent("openHTTPClientResult", "ok");
}
}

View File

@@ -0,0 +1,44 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.File;
import java.io.InputStream;
import java.net.InetAddress;
import net.i2p.util.EventDispatcher;
import net.i2p.util.Log;
public class I2PTunnelHTTPBidirServer extends I2PTunnelHTTPServer {
private final static Log log = new Log(I2PTunnelHTTPBidirServer.class);
public I2PTunnelHTTPBidirServer(InetAddress host, int port, int proxyport, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, spoofHost, l, notifyThis, tunnel);
finishSetupI2PTunnelHTTPBidirServer(l, proxyport);
}
public I2PTunnelHTTPBidirServer(InetAddress host, int port, int proxyport, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privkey, privkeyname, spoofHost, l, notifyThis, tunnel);
finishSetupI2PTunnelHTTPBidirServer(l, proxyport);
}
public I2PTunnelHTTPBidirServer(InetAddress host, int port, int proxyport, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, privkeyname, spoofHost, l, notifyThis, tunnel);
finishSetupI2PTunnelHTTPBidirServer(l, proxyport);
}
private void finishSetupI2PTunnelHTTPBidirServer(Logging l, int proxyport) {
localPort = proxyport;
bidir = true;
/* start the httpclient */
task = new I2PTunnelHTTPBidirProxy(localPort, l, sockMgr, getTunnel(), getEventDispatcher(), __serverId);
sockMgr.setName("Server"); // TO-DO: Need to change this to "Bidir"!
getTunnel().addSession(sockMgr.getSession());
l.log("Ready!");
notifyEvent("openServerResult", "ok");
}
}

View File

@@ -24,6 +24,7 @@ import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
@@ -56,7 +57,7 @@ import net.i2p.util.Translate;
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
private final List proxyList;
protected final List proxyList = new ArrayList();
private HashMap addressHelpers = new HashMap();
@@ -150,7 +151,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
private static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
public I2PTunnelHTTPClient(int localPort, Logging l, I2PSocketManager sockMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) {
super(localPort, l, sockMgr, tunnel, notifyThis, clientId);
// proxyList = new ArrayList();
setName(getLocalPort() + " -> HTTPClient [NO PROXIES]");
startRunning();
notifyEvent("openHTTPClientResult", "ok");
}
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
@@ -160,7 +169,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
proxyList = new ArrayList();
//proxyList = new ArrayList(); // We won't use outside of i2p
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openHTTPClientResult", "error");
return;
@@ -251,6 +260,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
/**
* Overridden to close internal socket too.
*/
@Override
public boolean close(boolean forced) {
boolean rv = super.close(forced);
if (this.isr != null)

View File

@@ -39,21 +39,21 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel", new long[] { 60*1000, 10*60*1000 });
setupI2PTunnelHTTPServer(spoofHost);
}
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
setupI2PTunnelHTTPServer(spoofHost);
}
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
setupI2PTunnelHTTPServer(spoofHost);
}
private void setupI2PTunnelHTTPServer(String spoofHost) {
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
}

View File

@@ -48,26 +48,29 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
protected long readTimeout = DEFAULT_READ_TIMEOUT;
private static final boolean DEFAULT_USE_POOL = false;
protected static volatile long __serverId = 0;
private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount";
private static final int DEFAULT_HANDLER_COUNT = 10;
protected I2PTunnelTask task = null;
protected boolean bidir = false;
private int DEFAULT_LOCALPORT = 4488;
protected int localPort = DEFAULT_LOCALPORT;
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
SetUsePool(tunnel);
init(host, port, bais, privData, l);
}
public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l,
EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
SetUsePool(tunnel);
FileInputStream fis = null;
try {
fis = new FileInputStream(privkey);
@@ -83,14 +86,18 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
SetUsePool(tunnel);
init(host, port, privData, privkeyname, l);
}
private void SetUsePool(I2PTunnel Tunnel) {
String usePool = Tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
init(host, port, privData, privkeyname, l);
}
private void init(InetAddress host, int port, InputStream privData, String privkeyname, Logging l) {
this.l = l;
this.remoteHost = host;
@@ -117,6 +124,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
sockMgr.setName("Server");
getTunnel().addSession(sockMgr.getSession());
l.log("Ready!");
@@ -125,8 +133,6 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
private static volatile long __serverId = 0;
/**
* Start running the I2PTunnelServer.
*
@@ -158,6 +164,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
public boolean close(boolean forced) {
if (!open) return true;
if (task != null) {
task.close(forced);
}
synchronized (lock) {
if (!forced && sockMgr.listSockets().size() != 0) {
l.log("There are still active connections!");
@@ -181,9 +190,6 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
}
private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount";
private static final int DEFAULT_HANDLER_COUNT = 10;
protected int getHandlerCount() {
int rv = DEFAULT_HANDLER_COUNT;
String cnt = getTunnel().getClientOptions().getProperty(PROP_HANDLER_COUNT);

View File

@@ -154,6 +154,8 @@ public class TunnelController implements Logging {
startServer();
} else if ("httpserver".equals(type)) {
startHttpServer();
} else if ("httpbidirserver".equals(type)) {
startHttpBidirServer();
} else if ("ircserver".equals(type)) {
startIrcServer();
} else if ("streamrserver".equals(type)) {
@@ -294,6 +296,16 @@ public class TunnelController implements Logging {
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
}
private void startHttpBidirServer() {
setListenOn();
String targetHost = getTargetHost();
String targetPort = getTargetPort();
String listenPort = getListenPort();
String spoofedHost = getSpoofedHost();
String privKeyFile = getPrivKeyFile();
_tunnel.runHttpBidirServer(new String[] { targetHost, targetPort, listenPort, spoofedHost, privKeyFile }, this);
}
private void startIrcServer() {
String targetHost = getTargetHost();
String targetPort = getTargetPort();

View File

@@ -16,7 +16,7 @@ public class Pinger implements Source, Runnable {
public void start() {
this.running = true;
this.waitlock = new Object();
//this.waitlock = new Object();
this.thread.start();
}
@@ -54,6 +54,6 @@ public class Pinger implements Source, Runnable {
protected Sink sink;
protected Thread thread;
protected Object waitlock; // FIXME should be final and use a factory. FIXME
private final Object waitlock = new Object();
protected boolean running;
}

View File

@@ -29,7 +29,7 @@ public class I2PSink implements Sink {
// create maker
if (!raw)
this.maker = new I2PDatagramMaker(this.sess);
this.maker.setI2PDatagramMaker(this.sess);
}
/** @param src ignored */
@@ -54,20 +54,8 @@ public class I2PSink implements Sink {
}
}
protected boolean raw;
protected I2PSession sess;
protected Destination dest;
protected I2PDatagramMaker maker; // FIXME should be final and use a factory. FIXME
protected final I2PDatagramMaker maker= new I2PDatagramMaker(); // FIXME should be final and use a factory. FIXME
}

View File

@@ -28,7 +28,7 @@ public class I2PSinkAnywhere implements Sink {
// create maker
if (!raw)
this.maker = new I2PDatagramMaker(this.sess);
this.maker.setI2PDatagramMaker(this.sess);
}
/** @param to - where it's going */
@@ -52,20 +52,8 @@ public class I2PSinkAnywhere implements Sink {
}
}
protected boolean raw;
protected I2PSession sess;
protected Destination dest;
protected I2PDatagramMaker maker; // FIXME should be final and use a factory. FIXME
protected final I2PDatagramMaker maker = new I2PDatagramMaker();
}

View File

@@ -389,6 +389,7 @@ public class IndexBean {
else if ("ircserver".equals(internalType)) return _("IRC server");
else if ("streamrclient".equals(internalType)) return _("Streamr client");
else if ("streamrserver".equals(internalType)) return _("Streamr server");
else if ("httpbidirserver".equals(internalType)) return _("HTTP bidir");
else return internalType;
}
@@ -779,8 +780,11 @@ public class IndexBean {
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
else if (_reachableBy != null)
config.setProperty("interface", _reachableBy);
else
config.setProperty("interface", "");
config.setProperty("sharedClient", _sharedClient + "");
for (String p : _booleanClientOpts)
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
@@ -806,11 +810,22 @@ public class IndexBean {
} else if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
} else if ("httpserver".equals(_type)) {
} else if ("httpserver".equals(_type) || "httpbidirserver".equals(_type)) {
if (_spoofedHost != null)
config.setProperty("spoofedHost", _spoofedHost);
}
if ("httpbidirserver".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else if (_reachableBy != null)
config.setProperty("interface", _reachableBy);
else if (_targetHost != null)
config.setProperty("interface", _targetHost);
else
config.setProperty("interface", "");
}
return config;
}

View File

@@ -113,11 +113,58 @@
<input type="text" size="6" maxlength="5" id="targetPort" name="targetPort" title="Target Port Number" value="<%=editBean.getTargetPort(curTunnel)%>" class="freetext" />
</div>
<% if ("httpbidirserver".equals(tunnelType)) {
%>
<div class="subdivider">
<hr />
</div>
<div id="accessField" class="rowItem">
<label><%=intl._("Access Point")%>:</label>
</div>
<div id="portField" class="rowItem">
<label for="port" accesskey="P">
<span class="accessKey">P</span>ort:
<% String value4 = editBean.getClientPort(curTunnel);
if (value4 == null || "".equals(value4.trim())) {
out.write(" <font color=\"red\">(");
out.write(intl._("required"));
out.write(")</font>");
}
%>
</label>
<input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />
</div>
<% String otherInterface = "";
String clientInterface = editBean.getClientInterface(curTunnel);
%>
<div id="reachField" class="rowItem">
<label for="reachableBy" accesskey="r">
<%=intl._("Reachable by")%>(<span class="accessKey">R</span>):
</label>
<select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox">
<% if (!("127.0.0.1".equals(clientInterface)) &&
!("0.0.0.0".equals(clientInterface)) &&
(clientInterface != null) &&
(clientInterface.trim().length() > 0)) {
otherInterface = clientInterface;
}
%><option value="127.0.0.1"<%=("127.0.0.1".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Locally (127.0.0.1)")%></option>
<option value="0.0.0.0"<%=("0.0.0.0".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Everyone (0.0.0.0)")%></option>
<option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>><%=intl._("LAN Hosts (Please specify your LAN address)")%></option>
</select>
</div>
<div id="otherField" class="rowItem">
<label for="reachableByOther" accesskey="O">
<%=intl._("Other")%>(<span class="accessKey">O</span>):
</label>
<input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" />
</div>
<% } %>
<div class="subdivider">
<hr />
</div>
<% if ("httpserver".equals(tunnelType)) {
<% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) {
%><div id="websiteField" class="rowItem">
<label for="spoofedHost" accesskey="W">
<%=intl._("Website name")%>(<span class="accessKey">W</span>):
@@ -129,8 +176,8 @@
%><div id="privKeyField" class="rowItem">
<label for="privKeyFile" accesskey="k">
<%=intl._("Private key file")%>(<span class="accessKey">k</span>):
<% String value2 = editBean.getPrivateKeyFile(curTunnel);
if (value2 == null || "".equals(value2.trim())) {
<% String value3 = editBean.getPrivateKeyFile(curTunnel);
if (value3 == null || "".equals(value3.trim())) {
out.write(" <font color=\"red\">(");
out.write(intl._("required"));
out.write(")</font>");
@@ -139,6 +186,7 @@
</label>
<input type="text" size="30" id="privKeyFile" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />
</div>
<% if (!"streamrserver".equals(tunnelType)) { %>
<div id="profileField" class="rowItem">
<label for="profile" accesskey="f">

View File

@@ -107,9 +107,9 @@
</div>
<div class="targetField rowItem">
<%
if ("httpserver".equals(indexBean.getInternalType(curServer)) && indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
if (("httpserver".equals(indexBean.getInternalType(curServer)) || ("httpbidirserver".equals(indexBean.getInternalType(curServer)))) && indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><label><%=intl._("Preview")%>:</label>
<a class="control" title="Test HTTP server through I2P" href="http://<%=indexBean.getDestHashBase32(curServer)%>.b32.i2p"><%=intl._("Preview")%></a>
<a class="control" title="Test HTTP server through I2P" href="http://<%=indexBean.getDestHashBase32(curServer)%>.b32.i2p"><%=intl._("Preview")%></a>
<%
} else if (indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><span class="text"><%=intl._("Base32 Address")%>:<br /><%=indexBean.getDestHashBase32(curServer)%>.b32.i2p</span>
@@ -164,6 +164,7 @@
<select name="type">
<option value="server"><%=intl._("Standard")%></option>
<option value="httpserver">HTTP</option>
<option value="httpbidirserver">HTTP bidir</option>
<option value="ircserver">IRC</option>
<option value="streamrserver">Streamr</option>
</select>

View File

@@ -43,9 +43,9 @@ public class ConfigLoggingHelper extends HelperBase {
buf.append(prefix).append('=').append(level).append('\n');
}
buf.append("</textarea><br>\n");
buf.append("<i>Add additional logging statements above. Example: net.i2p.router.tunnel=WARN</i><br>");
buf.append("<i>Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN</i><br>");
buf.append("<i>Valid levels are DEBUG, INFO, WARN, ERROR, CRIT</i>\n");
buf.append("<i>" + _("Add additional logging statements above. Example: net.i2p.router.tunnel=WARN") + "</i><br>");
buf.append("<i>" + _("Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN") + "</i><br>");
buf.append("<i>" + _("Valid levels are DEBUG, INFO, WARN, ERROR, CRIT") + "</i>\n");
return buf.toString();
}

View File

@@ -32,8 +32,7 @@
</div>
<% if (System.getProperty("wrapper.version") != null) { %>
<p><%=intl._("If you want the router to restart itself after shutting down, you can choose one of the following.")%>
<%=intl._("This is useful in some situations")%> -
<%=intl._("for example, if you changed some settings that client applications only read at startup, such as the routerconsole password or the interface it listens on.")%>
<%=intl._("This is useful in some situations - for example, if you changed some settings that client applications only read at startup, such as the routerconsole password or the interface it listens on.")%>
<%=intl._("A graceful restart will take a few minutes (but your peers will appreciate your patience), while a hard restart does so immediately.")%>
<%=intl._("After tearing down the router, it will wait 1 minute before starting back up again.")%></p>
<hr><div class="formaction">
@@ -50,8 +49,7 @@
<input type="submit" name="action" value="<%=intl._("Show systray icon")%>" >
<input type="submit" name="action" value="<%=intl._("Hide systray icon")%>" >
</div><h3><%=intl._("Run on startup")%></h3>
<p><%=intl._("You can control whether I2P is run on startup or not by selecting one of the following options")%> -
<%=intl._("I2P will install (or remove) a service accordingly.")%>
<p><%=intl._("You can control whether I2P is run on startup or not by selecting one of the following options - I2P will install (or remove) a service accordingly.")%>
<%=intl._("If you prefer the command line, you can also run the ")%> <code>install_i2p_service_winnt.bat</code> (<%=intl._("or")%>
<code>uninstall_i2p_service_winnt.bat</code>).</p>
<hr><div class="formaction">
@@ -63,9 +61,7 @@
<% if (System.getProperty("wrapper.version") != null) { %>
<h3><%=intl._("Debugging")%></h3>
<p> At times, it may be helpful to debug I2P by getting a thread dump.
To do so, please select the following option and review the thread dumped to
<a href="logs.jsp#servicelogs">wrapper.log</a>.</p>
<p><%=intl._("At times, it may be helpful to debug I2P by getting a thread dump. To do so, please select the following option and review the thread dumped to <a href=\"logs.jsp#servicelogs\">wrapper.log</a>.")%></p>
<hr><div class="formaction">
<input type="submit" name="action" value="<%=intl._("Dump threads")%>" >
<% } %></div>

View File

@@ -0,0 +1,173 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%
/*
* Do not tag this file for translation - copy it to help_xx.jsp and translate inline.
*/
%>
<html><head><title>Консоль маршрутизатора I2P - справка</title>
<%@include file="css.jsi" %>
</head><body>
<%@include file="summary.jsi" %>
<h1>Справка маршрутизатора I2P</h1>
<div class="main" id="main">
<p> Если Вы хотите помочь в улучшении или переводе документации, если у Вас есть идеи, как еще помочь проекту, пожалуйста, загляните в раздел документации
<a href="http://www.i2p2.i2p/getinvolved.html">как стать участником</a>. </p>
<p>Дальнейшие инструкции доступны в <a href="http://www.i2p2.i2p/faq.html">FAQ на www.i2p2.i2p</a>
<br>Также, имеет смысл зайти на <a href="http://forum.i2p/">форум I2P</a> и IRC-каналы проекта.</p>
<h2>Описание статусной панели</h2>
<p>
Для большинства параметров на статусной панели можно <a href="configstats.jsp">настроить</a> построение <a href="graphs.jsp">графиков</a> в целях более подробного анализа.
</p>
<h3>Общая информация</h3><ul>
<li class="tidylist"><b>Локальный идентификатор</b>
Первые 4 символа (24 бита) из Вашего 44-символьного (256-битного) Base64 хеша маршрутизатора. Полный хеш показывается на странице <a href="netdb.jsp?r=.">информации о маршрутизаторе</a>. Никогда никому не показывайте хеш своего маршрутизатора, так как в нем содержится информация о Вашем IP-адресе.
<li class="tidylist"><b>Версия:</b>
Версия Вашего I2P маршрутизатора.
<li class="tidylist"><b>Время:</b>
Текущее время (UTC) и величина рассинхронизации времени (если есть). Для правильной работы I2P нужно точное системное время. Пожалуйста, поправьте системное время, если расхождение приближается к 1-ой минуте.
<li class="tidylist"><b>Доступность:</b>
Результат проверки Вашим маршрутизатором, насколько он открыт для входящих соединений от маршрутизаторов других пользователей. Подробнее смотрите на <a href="config.jsp#help">странице сетевых настроек</a>.
</ul>
<h3>Пиры</h3><ul>
<li class="tidylist"><b>Активные:</b>
Первое число — это количество пиров, с которыми происходил обмен сообщениями за последние несколько минут. Значение может меняться от 8-10 до нескольких сотен в зависимости от Вашего общего трафика, доли транзитного трафика, локально создаваемого трафика. Второе число — это количество пиров, наблюдавшихся за последний час. Не волнуйтесь, если эти числа сильно меняются. Это нормально. <a href="configstats.jsp#router.activePeers">[Включить построение графика]</a>.
<li class="tidylist"><b>Быстрые:</b>
Количество пиров, которые используются Вашим маршрутизатором для построения клиентских туннелей. В общем случае это значение будет в диапазоне 8-15. Список быстрых пиров можно посмотреть на странице <a href="profiles.jsp">профили</a>. <a href="configstats.jsp#router.fastPeers">[Включить построение графика]</a>.
<li class="tidylist"><b>Высокоёмкие:</b>
Количество пиров, которые используются Вашим маршрутизатором для построения части зондирующих туннелей. В общем случае это значение будет в диапазоне 8-25. Быстрые пиры входят в группу высокоёмких. Список высокоёмких пиров можно посмотреть на странице <a href="profiles.jsp">профили</a>. <a href="configstats.jsp#router.highCapacityPeers">[Включить построение графика]</a>.
<li class="tidylist"><b>Хорошо интегрированные:</b>
Количество пиров, которые используются Вашим маршрутизатором для запросов к сетевой базе данных. Обычно в таком качестве выступают «floodfill» пиры. Список хорошо интегрированных пиров можно посмотреть в конце страницы <a href="profiles.jsp">профили</a>.
<li class="tidylist"><b>Известные:</b>
Это общее количество пиров известных Вашему маршрутизатору. Их список показывается на странице <a href="netdb.jsp">обзор сетевой базы данных</a>. Это значение может варьироваться от десятков до тысяч. Значение не соответствует реальному полному размеру сети, так как маршрутизатору в сети I2P достаточно знать лишь часть других маршрутизаторов. Значение зависит от Вашего общего трафика, доли транзитного трафика, локально создаваемого трафика.
</ul>
<h3>Трафик (входящий/исходящий)</h3>
<div align="justify">
Все значения показаны в байтах/секунду. Настроить ограничения трафика можно на странице <a href="config.jsp">сетевых настроек</a>.
Для трафика по умолчанию включено <a href="graphs.jsp">построение графиков</a>.</div>
<h3>Локальные туннели</h3>
<div align="justify">
Локальные приложения, выходящие в I2P сеть через Ваш маршрутизатор. Это могут быть клиенты, запущенные через <a href="i2ptunnel/index.jsp">менеджер туннелей</a>, внешние программы, подключающиеся через интерфейсы SAM, BOB или напрямую через I2CP.
</div>
<h3>Туннели (входящие/исходящие)</h3>
<div align="justify">
Список туннелей можно посмотреть на странице <a href="tunnels.jsp">обзор туннелей</a>.</div>
<ul>
<li class="tidylist"><div align="justify"><b>Зондирующие:</b>
Туннели, созданные Вашим маршрутизатором для связи с floodfill-пирами, тестирования уже существующих туннелей и построения новых.</div>
<li class="tidylist"><b>Клиентские:</b>
Туннели, созданные Вашим маршрутизатором для каждого локального клиента.
<li class="tidylist"><b>Транзитные:</b>
Туннели, построенные другими маршрутизаторами, проходящие через Ваш маршрутизатор. Их количество может сильно варьироваться в зависимости от потребностей сети, настроенной доли транзитного трафика и объема локально создаваемого трафика. Рекомендуемый способ ограничения количества транзитных туннелей — настроить долю транзитного трафика на странице <a href="config.jsp#help">сетевых настроек</a>. Также можно задать точный ограничитель количества через параметр <tt>router.maxParticipatingTunnels=nnn</tt> на странице <a href="configadvanced.jsp">дополнительных настроек</a>.
<a href="configstats.jsp#tunnel.participatingTunnels">[Включить построение графика]</a>.
</ul>
<h3>Занятость</h3>
<div align="justify">Некоторые базовые индикаторы перегруженности маршрутизатора:</div>
<ul>
<li class="tidylist"><b>Задержка заданий:</b>
Как долго задания ожидают выполнения. Содержимое очереди можно посмотреть на странице <a href="jobs.jsp">очередь заданий</a>. К сожалению, есть ещё несколько внутренних очередей, статус которых в консоли не показывается. Задержка заданий в нормальной ситуации должна быть нулевой. Если она систематически выше 500ms, то либо Ваш компьютер слишком медленный, либо с Вашим маршрутизатором проблемы.
<a href="configstats.jsp#jobQueue.jobLag">[Включить построение графика]</a>.
<li class="tidylist"><b>Задержка сообщений:</b>
Как долго исходящие сообщения находятся в очереди. В нормальном случае эта задержка должна быть не выше нескольких сотен миллисекунд. Если она систематически выше 1000ms, то либо Ваш компьютер слишком медленный, либо Вам следует перенастроить ограничение скорости, либо локальные клиенты (чаще всего bittorrent) посылают слишком много данных. Для таких клиентов имеет смысл ограничить скорость.
<a href="configstats.jsp#transport.sendProcessingTime">[Включить построение графика]</a> (transport.sendProcessingTime).
<li class="tidylist"><b>Задержка туннелей:</b>
Время прохождения сигнала при проверке туннеля (сообщение посылается от клиентского туннеля до зондирующего или в обратном направлении). Это значение в нормальном случае должно быть ниже 5 секунд. Если оно систематически выше, то либо Ваш компьютер слишком медленный, либо Вам следует перенастроить ограничение скорости, либо с сетью что-то не в порядке.
<a href="configstats.jsp#tunnel.testSuccessTime">[Включить построение графика]</a> (tunnel.testSuccessTime).
<li class="tidylist"><b>Очередь запросов:</b>
Количество пока необработанных запросов от других маршрутизаторов о построении транзитных туннелей через Ваш маршрутизатор. В нормальном случае это значение должно быть около нуля. Если оно систематически выше, то Ваш компьютер слишком медленный и Вам следует настроить меньшую долю транзитного трафика.
<li class="tidylist"><b>Принимаем/Не принимаем туннели:</b>
Состояние Вашего маршрутизатора по приему или отклонению запросов от других маршрутизаторов о построении туннелей. Ваш маршрутизатор может принимать все запросы, принимать/отклонять часть запросов или отклонять все запросы, в зависимости от сетевой загрузки, нагрузки на процессор и необходимости резервировать полосу пропускания для локальных клиентов.
</ul>
<h2>Лицензии</h2>
<p>Код I2P-маршрутизатора (router.jar) и его SDK (i2p.jar) находятся в общественном достоянии с некоторыми исключениями:</p>
<ul>
<li class="tidylist">Код для алгоритмов ElGamal и DSA — под лицензией BSD, автор: TheCrypto</li>
<li class="tidylist">Код для алгоритмов SHA256 и HMAC-SHA256 — под лицензией MIT, автор: Legion из Bouncycastle</li>
<li class="tidylist">Код для алгоритма AES — под лицензией Cryptix (MIT), авторы: Cryptix team</li>
<li class="tidylist">Код для SNTP — под лицензией BSD, автор: Adam Buckley</li>
<li class="tidylist">Всё остальное полностью в общественном достоянии, авторы: jrandom, mihi, hypercubus, oOo, ugha, duck, shendaras, и другие.</li>
</ul>
<p>Поверх I2P маршрутизатора работают различные приложения-клиенты, каждое со своим набором лицензий и зависимостей. Например, эта страница входит в приложение консоли маршрутизатора, которое сделано из усеченной версии <a href="http://jetty.mortbay.com/jetty/index.html">Jetty</a> (в сборку не включены демонстрационные приложения и прочие дополнения, настройки упрощены). Jetty позволяет запускать в составе маршрутизатора стандартные JSP/сервлеты. Jetty использует javax.servlet.jar разработанный в составе проекта Apache (http://www.apache.org/).
</p>
<p>Ещё одно приложение на этой странице — <a href="http://www.i2p2.i2p/i2ptunnel">I2PTunnel</a> (а тут <a href="i2ptunnel/" target="_blank">его вебинтерфейс</a>). Автор mihi, лицензия GPL. I2PTunnel занимается туннелированнием обычного TCP/IP трафика через I2P (может применяться для eepproxy и irc-прокси). <a href="http://susi.i2p/">susimail</a> — почтовый клиент с <a href="susimail/susimail">вебинтерфейсом</a>, автор susi23, лицензия GPL. Адресная книга помогает управлять содержимым Ваших hosts.txt файлов (подробнее см. ./addressbook/), автор <a href="http://ragnarok.i2p/">Ragnarok</a>.</p>
<p>В поставку маршрутизатора включен <a href="http://www.i2p2.i2p/sam">SAM</a> интерфейс, автор human, приложение в общественном достоянии. SAM предназначен для использования приложениями-клиентами, такими как <a href="http://duck.i2p/i2p-bt/">bittorrent-клиенты</a>. Маршрутизатором используется оптимизированная под разные PC-архитектуры библиотека для вычислений с большими числами jbigi, которая в свою очередь использует библиотеку <a href="http://swox.com/gmp/">GMP</a> (LGPL лицензия). Вспомогательные приложения для Windows созданы с использованием <a href="http://launch4j.sourceforge.net/">Launch4J</a>, а инсталлятор собран при помощи <a href="http://www.izforge.com/izpack/">IzPack</a>. Подробнее о других доступных приложениях и их лицензиях смотрите на странице <a href="http://www.i2p2.i2p/licenses">I2P Software Licenses</a>. Исходный код I2P маршрутизатора и идущих в комплекте приложений можно найти на нашей <a href="http://www.i2p2.i2p/download">странице загрузки</a>. </p>
<h2>История версий</h2>
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="contenthelper" scope="request" />
<% java.io.File fpath = new java.io.File(net.i2p.I2PAppContext.getGlobalContext().getBaseDir(), "history.txt"); %>
<jsp:setProperty name="contenthelper" property="page" value="<%=fpath.getAbsolutePath()%>" />
<jsp:setProperty name="contenthelper" property="maxLines" value="256" />
<jsp:setProperty name="contenthelper" property="startAtBeginning" value="true" />
<jsp:getProperty name="contenthelper" property="textContent" />
<p>Более подробный список изменений можно найти в файле history.txt в каталоге Вашего I2P.
</p><hr></div></body></html>

File diff suppressed because it is too large Load Diff