Compare commits

...

35 Commits

Author SHA1 Message Date
idk
1c67f06ffc Since service installs always have an i2p.dir.config set, they don't need to have a default in WorkingDir.java around line 110. However, since user installs do not have an i2p.dir.config set, at around line 80-110 we have to determine whether to use the legacy config in AppData/Roaming or the new default in AppData/Local. Use programdata environment variable becaue it is consistent with the actual path that we should be using. 2022-09-16 14:06:41 -04:00
idk
0ce816acd1 change path validation for service installs when install location contains system32 2022-09-13 11:06:43 -04:00
idk
2fd01c8390 add correct programdata path to default eepsite 2022-09-10 11:16:35 -04:00
idk
16eb1577f9 remove extraneous logging 2022-09-10 11:12:19 -04:00
idk
ecd58a833c If default computed path contains %programdata% or system32 we're running as a daemon on Windows, and %programdata%/Application Data/ does not exist anymore(since ~Win7?). Don't append /Application Data/ in default, command, or shortcut, that should all go directly into %programdata%/i2p now 2022-09-10 10:26:06 -04:00
idk
b533ccaabd remove Application Data from path to I2P service config on Windows 2022-09-08 17:03:39 -04:00
zzz
0f560139f3 unused field 2022-09-07 10:11:39 -04:00
zzz
b610b7a695 Transports: Don't rekey noise params on testnet 2022-09-07 10:09:05 -04:00
zzz
26f882edd5 SSU: Fallback processing for MTU detection
take the minimum of all interfaces if specific interface not found
Update CLI to support address args for testing
2022-09-07 10:01:32 -04:00
idk
f00e020f2b Fix unexpanded styles on addressbook pages #365 2022-09-06 18:09:58 -04:00
idk
edd0dd0b0d fix padding on expanded elements in SusiDNS 2022-09-06 15:54:44 -04:00
idk
031fa45eb8 fix incorrect background color on expanding element in SusiDNS 2022-09-06 15:39:45 -04:00
zzz
a2eee5a673 NetDB: Fix reseeding when clock is skewed
reported, fixed, and tested by zlatinb
2022-09-06 11:56:33 -04:00
zzz
45c160f27a Data: Cache SSU2 string 2022-09-06 09:39:14 -04:00
zzz
7ba59b4338 SSU2: Don't publish or connect if our MTU becomes too small
after initial check in constructor
Don't rectify 1280 IPv4 MTU down to 1276
Add note about replay detector
2022-09-06 09:35:29 -04:00
zzz
554b17fe9a SusiDNS: Fix adding to empty address book
by doing form processing even if book is empty.
Do all form processing before displays so the
displays reflect any form add/delete.
Broken in 0.9.50 when form processing call was moved.
Hide replace and add alternate buttons if book is empty.
Ref: https://www.reddit.com/r/i2p/comments/wu7nac/how_do_i_add_to_the_private_address_book/
2022-09-04 08:14:19 -04:00
zzz
64632eed4a UPnP: Fix opening IPv6 ports 2022-09-03 12:01:59 -04:00
zzz
49299f3f28 NetDB: Query connected peers for their RI directly
Negative cache router hash when expiring RI
Log tweaks
2022-09-03 11:59:39 -04:00
zzz
00774590b0 SSU2: Fix ISE after sending retry for clock skew in session request
log tweaks
2022-09-01 12:21:33 -04:00
zzz
52b640b582 Router: Ensure database store message is processed before reply job (Gitlab #364)
by processing DSM inline in InNetMessagePool.
Caused random lookup failures of RI and LS.
Made worse by previous change removing duplicate store from reply job.
thanks obscuratus and drzed
2022-09-01 11:55:00 -04:00
zzz
911e69e3ae Emphasize checklist step to try Maven jars build before release
to not break Android build
ref: Gitlab MR !64
2022-08-31 07:56:13 -04:00
zzz
d809d6653d SSU2: Implement ack-immediate flag
Use a single ack timer for each session rather than
creating a new one for every ack
Log tweaks
2022-08-29 16:47:19 -04:00
idk
8df81fc0a1 Merge branch '1.9.0-java7-classpath-fix' into 'master'
explicitly set types of some objects so that the code can be compiled with a Java 7 bootclasspth

See merge request i2p-hackers/i2p.i2p!64
2022-08-29 15:45:55 +00:00
zzz
ed76829562 Console: Don't lookup RI if banned 2022-08-28 15:28:17 -04:00
zzz
4f4044c3f0 Console: Add notification and summary bar info on deadlock
Check for deadlock more frequently
2022-08-28 15:24:34 -04:00
zzz
3a4bfc9c07 Util: Add option to gzip router logs
Primarily for devs. No UI.
remove shutdown hook ID
2022-08-28 15:07:30 -04:00
zzz
b25c207e9a Console: Linkify router hash even if not in netdb
as the console will do the lookup if not
2022-08-28 11:22:31 -04:00
idk
fcae43547b explicitly set types of some objects so that the code can be compiled with a Java 7 bootclasspth 2022-08-27 19:26:03 -04:00
zzz
b34b0cc399 SSU2: Fix packets exceeding MTU by up to 3 bytes 2022-08-25 10:58:14 -04:00
zzz
f4875d12fa SSU2: Immediately fail session request containing zero token
i2pd will send this after retry with zero token and termination
2022-08-25 10:56:34 -04:00
zzz
2f06e9bebf SSU2: Implement path challenge and connection migration
mark session dead after receiving destroy
log tweaks
2022-08-25 08:57:02 -04:00
zzz
9b6dde008d Router: Hopefully fix deadlock via rebuildRouterAddress() and UDPTransport 2022-08-25 08:46:25 -04:00
zzz
6ddaa72a86 Add deadlocks to event log 2022-08-23 13:01:32 -04:00
zzz
d064de1913 Debian files for 1.9.0 2022-08-23 11:36:06 -04:00
zzz
97013f8874 blocklist update 2022-08-22 15:15:20 -04:00
47 changed files with 891 additions and 152 deletions

View File

@@ -3,10 +3,15 @@ package net.i2p.router.web;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientAppManager;
import net.i2p.app.NotificationService;
import net.i2p.router.RouterContext;
import net.i2p.router.util.EventLog;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.SystemVersion;
/**
* Periodic check
@@ -15,16 +20,17 @@ import net.i2p.util.SimpleTimer2;
* In routerconsole because java.lang.management is
* not available in Android.
*
* @since 0.9.55
* @since 0.9.55, public since 0.9.56
*/
class DeadlockDetector extends SimpleTimer2.TimedEvent {
public class DeadlockDetector extends SimpleTimer2.TimedEvent {
private final I2PAppContext _context;
private final RouterContext _context;
private final Log _log;
private static final String PROP_INTERVAL = "router.deadlockDetectIntervalHours";
private static final long DEFAULT_INTERVAL = 24;
private static final long DEFAULT_INTERVAL = SystemVersion.isSlow() ? 12 : 4;
private static final AtomicBoolean _isDeadlocked = new AtomicBoolean();
public DeadlockDetector(I2PAppContext ctx) {
public DeadlockDetector(RouterContext ctx) {
super(ctx.simpleTimer2());
_context = ctx;
_log = _context.logManager().getLog(DeadlockDetector.class);
@@ -41,6 +47,7 @@ class DeadlockDetector extends SimpleTimer2.TimedEvent {
public void timeReached() {
long start = System.currentTimeMillis();
boolean detected = detect();
// only reschedule if not detected
if (!detected) {
long time = System.currentTimeMillis() - start;
if (_log.shouldDebug())
@@ -55,7 +62,9 @@ class DeadlockDetector extends SimpleTimer2.TimedEvent {
return detect(_context);
}
public static boolean detect(I2PAppContext ctx) {
public static boolean detect(RouterContext ctx) {
if (_isDeadlocked.get())
return true;
try {
ThreadMXBean mxb = ManagementFactory.getThreadMXBean();
long[] ids = mxb.findDeadlockedThreads();
@@ -72,7 +81,8 @@ class DeadlockDetector extends SimpleTimer2.TimedEvent {
infos = mxb.getThreadInfo(ids, Integer.MAX_VALUE);
}
StringBuilder buf = new StringBuilder(2048);
buf.append("Deadlock detected, please report\n\n");
String msg1 = Messages.getString("Deadlock detected", ctx) + " - " + Messages.getString("Please report", ctx);
buf.append(msg1).append("\n\n");
for (int i = 0; i < infos.length; i++) {
ThreadInfo info = infos[i];
if (info == null)
@@ -86,9 +96,20 @@ class DeadlockDetector extends SimpleTimer2.TimedEvent {
}
buf.append('\n');
}
buf.append("\nAfter reporting, please restart your router!\n");
String msg2 = Messages.getString("After reporting, please restart your router", ctx);
buf.append('\n').append(msg2).append('\n');
Log log = ctx.logManager().getLog(DeadlockDetector.class);
log.log(Log.CRIT, buf.toString());
ctx.router().eventLog().addEvent(EventLog.DEADLOCK, infos.length + " threads");
_isDeadlocked.set(true);
ClientAppManager cmgr = ctx.clientAppManager();
if (cmgr != null) {
NotificationService ns = (NotificationService) cmgr.getRegisteredApp("desktopgui");
if (ns != null) {
ns.notify("Router", null, Log.CRIT, Messages.getString("Router", ctx),
msg1 + '\n' + msg2, null);
}
}
} catch (Throwable t) {
// class not found, unsupportedoperation, ...
Log log = ctx.logManager().getLog(DeadlockDetector.class);
@@ -98,6 +119,15 @@ class DeadlockDetector extends SimpleTimer2.TimedEvent {
return true;
}
/**
* Return the results of the last test. Does not run a new test.
*
* @since 0.9.56
*/
public static boolean isDeadlocked() {
return _isDeadlocked.get();
}
/*
public static void main(String[] args) {
final Object o1 = new Object();

View File

@@ -36,6 +36,7 @@ public class EventLogHelper extends FormHandler {
EventLog.CLOCK_SHIFT, _x("Clock shifted"),
EventLog.CRASHED, _x("Crashed"),
EventLog.CRITICAL, _x("Critical error"),
EventLog.DEADLOCK, _x("Deadlock detected"),
EventLog.INSTALLED, _x("Installed new version"),
EventLog.INSTALL_FAILED, _x("Install failed"),
EventLog.NETWORK, _x("Network error"),

View File

@@ -121,24 +121,27 @@ class NetDbRenderer {
if (h != null && h.length == Hash.HASH_LENGTH) {
Hash hash = new Hash(h);
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(hash);
boolean banned = false;
if (ri == null) {
// remote lookup
LookupWaiter lw = new LookupWaiter();
_context.netDb().lookupRouterInfo(hash, lw, lw, 8*1000);
// just wait right here in the middle of the rendering, sure
synchronized(lw) {
try { lw.wait(9*1000); } catch (InterruptedException ie) {}
banned = _context.banlist().isBanlisted(hash);
if (!banned) {
// remote lookup
LookupWaiter lw = new LookupWaiter();
_context.netDb().lookupRouterInfo(hash, lw, lw, 8*1000);
// just wait right here in the middle of the rendering, sure
synchronized(lw) {
try { lw.wait(9*1000); } catch (InterruptedException ie) {}
}
ri = _context.netDb().lookupRouterInfoLocally(hash);
}
ri = _context.netDb().lookupRouterInfoLocally(hash);
}
if (ri != null) {
renderRouterInfo(buf, ri, false, true);
} else {
buf.append("<div class=\"netdbnotfound\">");
buf.append(_t("Router")).append(' ');
if (routerPrefix != null)
buf.append(routerPrefix);
buf.append(' ').append(_t("not found in network database"));
buf.append(routerPrefix);
buf.append(' ').append(banned ? "is banned" : _t("not found in network database"));
buf.append("</div>");
}
} else {

View File

@@ -26,6 +26,7 @@ import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.networkdb.reseed.ReseedChecker;
import net.i2p.router.transport.TransportUtil;
import net.i2p.router.web.CSSHelper;
import net.i2p.router.web.DeadlockDetector;
import net.i2p.router.web.HelperBase;
import net.i2p.router.web.NewsHelper;
import net.i2p.router.web.WebAppStarter;
@@ -951,6 +952,15 @@ public class SummaryHelper extends HelperBase {
.append("</a></h4>");
}
if (DeadlockDetector.isDeadlocked()) {
buf.append("<div class=\"sb_notice\"><b>")
.append(_t("Deadlock detected"))
.append(" - <a href=\"/logs\">")
.append(_t("Please report"))
.append("</a> - ").append(_t("After reporting, please restart your router"))
.append("</b></div>");
}
ReseedChecker checker = _context.netDb().reseedChecker();
String status = checker.getStatus();
if (status.length() > 0) {

View File

@@ -77,6 +77,13 @@
<h3 id="addrtitle"><%=intl._t("Address book")%>: <%=intl._t(book.getBook())%></h3>
<h4 id="storagepath"><%=intl._t("Storage")%>: ${book.displayName}</h4>
<%
// This is what does the form processing.
// We need to do this before any notEmpty test and before loadBookMessages() which displays the entry count.
// Messages will be displayed below.
String formMessages = book.getMessages();
%>
${book.loadBookMessages}
<% if (book.getBook().equals("private")) { %>
@@ -99,7 +106,7 @@ ${book.loadBookMessages}
<% if (book.getBook().equals("local")) { %>
<div id="bookdescription">
<p class="bookdescription">
<%=intl._t("This is the Local Address Book.")%>
<%=intl._t("This is the Local Address Book.")%>
<%=intl._t("Use it for sites that you host on this computer.")%>
</p>
</div>
@@ -128,15 +135,18 @@ ${book.loadBookMessages}
</div>
</form>
<% } /* book.getEntries().length() > 0 */ %>
</c:if><% /* book.notEmpty */ %>
</div>
</div><% /* headline */ %>
<div id="messages">${book.messages}<%
<% /* need this whether book is empty or not to display the form messages */ %>
<div id="messages"><%=formMessages%><%
if (importMessages != null) {
%><%=importMessages%><%
}
%></div>
<c:if test="${book.notEmpty}">
<div id="filter">
<c:if test="${book.hasFilter}">
<span><%=intl._t("Current filter")%>: <b>${book.filter}</b>
@@ -274,7 +284,7 @@ ${book.loadBookMessages}
<input type="hidden" name="begin" value="0">
<input type="hidden" name="end" value="49">
<div id="add">
<h3 id="addnewaddr" ><%=intl._t("Add new destination")%></h3>
<h3 id="addnewaddr" class="unexpanded"><%=intl._t("Add new destination")%></h3>
<table id="addnewaddrtable">
<tr>
<td><b><%=intl._t("Hostname")%></b></td>
@@ -287,10 +297,12 @@ ${book.loadBookMessages}
</table>
<p class="buttons" id="addnewaddrbutton">
<input class="cancel" type="reset" value="<%=intl._t("Cancel")%>" >
<c:if test="${book.notEmpty}">
<input class="accept" type="submit" name="action" value="<%=intl._t("Replace")%>" >
<% if (!book.getBook().equals("published")) { %>
<input class="add" type="submit" name="action" value="<%=intl._t("Add Alternate")%>" >
<% } %>
</c:if><% /* book.notEmpty */ %>
<input class="add" type="submit" name="action" value="<%=intl._t("Add")%>" >
</p>
</div>
@@ -303,7 +315,7 @@ ${book.loadBookMessages}
<input type="hidden" name="begin" value="0">
<input type="hidden" name="end" value="49">
<div id="import">
<h3 id="importhosts"><%=intl._t("Import from hosts.txt file")%></h3>
<h3 id="importhosts" class="unexpanded"><%=intl._t("Import from hosts.txt file")%></h3>
<table id="importhostsform">
<tr>
<td><b><%=intl._t("File")%></b></td>

View File

@@ -576,9 +576,17 @@ tr.list0 {
}
.expanded {
background: url(/themes/console/light/images/dropdown_active.png) #f8f8ff !important;
background: url(/themes/console/dark/images/dropdown_active.png) !important;
background-repeat: no-repeat !important;
background-position: .5%, 15% !important;
padding-left: 2% !important;
}
.unexpanded {
background: url(/themes/console/dark/images/dropdown.png) !important;
background-repeat: no-repeat !important;
background-position: .5%, 15% !important;
padding-left: 2% !important;
}
#addrtitle.expanded {

View File

@@ -9,11 +9,19 @@ package net.i2p.util;
*
*/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.zip.GZIPOutputStream;
import net.i2p.data.DataHelper;
/**
* File-based log writer thread that pulls log records from the LogManager,
@@ -93,12 +101,31 @@ class FileLogWriter extends LogWriter {
* @since 0.9.19 renamed from closeFile()
*/
protected void closeWriter() {
closeWriter(_currentFile, false);
}
/**
* Gzip the closed file
*
* @param threadGzipper if true, spin off a thread
* @since 0.9.55
*/
private void closeWriter(File currentFile, boolean threadGzipper) {
Writer out = _currentOut;
if (out != null) {
try {
out.close();
} catch (IOException ioe) {}
}
if (_manager.shouldGzip() && currentFile != null && currentFile.length() >= _manager.getMinGzipSize()) {
Thread gzipper = new Gzipper(currentFile);
if (threadGzipper) {
gzipper.setPriority(Thread.MIN_PRIORITY);
gzipper.start(); // rotate
} else {
gzipper.run(); // shutdown
}
}
}
/**
@@ -107,6 +134,7 @@ class FileLogWriter extends LogWriter {
* Caller must synch
*/
private void rotateFile() {
File old = _currentFile;
File f = getNextFile();
_currentFile = f;
_numBytesInCurrentFile = 0;
@@ -125,7 +153,9 @@ class FileLogWriter extends LogWriter {
//System.exit(0);
}
}
closeWriter();
closeWriter(old, true);
if (_manager.shouldGzip())
(new File(f.getPath() + ".gz")).delete();
try {
_currentOut = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8"));
} catch (IOException ioe) {
@@ -180,7 +210,8 @@ class FileLogWriter extends LogWriter {
f = new File(base, replace(pattern, i));
else
f = new File(replace(pattern, i));
if (!f.exists()) {
// check for file or file.gz
if (!f.exists() && !(_manager.shouldGzip() && (new File(f.getPath() + ".gz").exists()))) {
_rotationNum = i;
return f;
}
@@ -197,7 +228,18 @@ class FileLogWriter extends LogWriter {
if (oldest == null) {
oldest = f;
} else {
if (f.lastModified() < oldest.lastModified()) {
// set file or file.gz for last mod check
File ff, oo;
if (!_manager.shouldGzip() || f.exists())
ff = f;
else
ff = new File(f.getPath() + ".gz");
if (!_manager.shouldGzip() || oldest.exists())
oo = oldest;
else
oo = new File(oldest.getPath() + ".gz");
if (ff.lastModified() < oo.lastModified()) {
_rotationNum = i;
oldest = f;
}
@@ -218,4 +260,34 @@ class FileLogWriter extends LogWriter {
}
return buf.toString();
}
/**
* @since 0.9.55
*/
private static class Gzipper extends I2PAppThread {
private final File _f;
public Gzipper(File f) {
super("Log file compressor");
_f = f;
}
public void run() {
File to = new File(_f.getPath() + ".gz");
InputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(_f));
out = new BufferedOutputStream(new GZIPOutputStream(new SecureFileOutputStream(to)));
DataHelper.copy(in, out);
} catch (IOException ioe) {
System.out.println("Error compressing log file " + _f);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
to.setLastModified(_f.lastModified());
_f.delete();
}
}
}
}

View File

@@ -38,6 +38,8 @@ import net.i2p.data.DataHelper;
* This also fires off a LogWriter thread that pulls pending records off and
* writes them where appropriate.
*
* As of 0.9.41, this class may be overridden via I2PAppContext.setLogManager()
*
*/
public class LogManager implements Flushable {
public final static String CONFIG_LOCATION_PROP = "loggerConfigLocation";
@@ -65,6 +67,10 @@ public class LogManager implements Flushable {
private static final String PROP_DUP = "logger.dropDuplicates";
/** @since 0.9.18 */
private static final String PROP_FLUSH = "logger.flushInterval";
/** @since 0.9.56 */
private static final String PROP_GZIP = "logger.gzip";
/** @since 0.9.56 */
private static final String PROP_MIN_GZIP_SIZE = "logger.minGzipSize";
public final static String PROP_RECORD_PREFIX = "logger.record.";
public final static String DEFAULT_FORMAT = DATE + " " + PRIORITY + " [" + THREAD + "] " + CLASS + ": " + MESSAGE;
@@ -79,6 +85,9 @@ public class LogManager implements Flushable {
public final static String DEFAULT_DEFAULTLEVEL = Log.STR_ERROR;
public final static String DEFAULT_ONSCREENLEVEL = Log.STR_CRIT;
private static final int MIN_FILESIZE_LIMIT = 16*1024;
private final static boolean DEFAULT_GZIP = false;
private static final int DEFAULT_MIN_GZIP_SIZE = 64*1024;
private final I2PAppContext _context;
private final Log _log;
@@ -133,6 +142,8 @@ public class LogManager implements Flushable {
private final AtomicLong _droppedRecords = new AtomicLong();
// in seconds
private int _flushInterval = (int) (LogWriter.FLUSH_INTERVAL / 1000);
private boolean _gzip;
private long _minGzipSize;
private boolean _alreadyNoticedMissingConfig;
@@ -452,6 +463,17 @@ public class LogManager implements Flushable {
String str = config.getProperty(PROP_DUP);
_dropDuplicates = str == null || Boolean.parseBoolean(str);
str = config.getProperty(PROP_GZIP);
_gzip = str != null ? Boolean.parseBoolean(str) : DEFAULT_GZIP;
if (_gzip) {
_minGzipSize = DEFAULT_MIN_GZIP_SIZE;
try {
str = config.getProperty(PROP_MIN_GZIP_SIZE);
if (str != null)
_minGzipSize = Long.parseLong(str);
} catch (NumberFormatException nfe) {}
}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Log set to use the base log file as " + _baseLogfilename);
@@ -673,6 +695,20 @@ public class LogManager implements Flushable {
return _rotationLimit;
}
/**
* @since 0.9.56
*/
boolean shouldGzip() {
return _gzip;
}
/**
* @since 0.9.56
*/
long getMinGzipSize() {
return _gzip ? _minGzipSize : Long.MAX_VALUE;
}
/** @return success */
public synchronized boolean saveConfig() {
Properties props = createConfig();
@@ -712,6 +748,8 @@ public class LogManager implements Flushable {
rv.setProperty(PROP_DISPLAYONSCREENLEVEL, Log.toLevelString(_onScreenLimit));
rv.setProperty(PROP_CONSOLEBUFFERSIZE, Integer.toString(_consoleBufferSize));
rv.setProperty(PROP_FLUSH, Integer.toString(_flushInterval));
rv.setProperty(PROP_GZIP, Boolean.toString(_gzip));
rv.setProperty(PROP_MIN_GZIP_SIZE, Long.toString(_minGzipSize));
for (LogLimit lim : _limits) {
rv.setProperty(PROP_RECORD_PREFIX + lim.getRootName(), Log.toLevelString(lim.getLimit()));
@@ -812,16 +850,10 @@ public class LogManager implements Flushable {
_consoleBuffer.clear();
}
private static final AtomicInteger __id = new AtomicInteger();
private class ShutdownHook extends I2PAppThread {
private final int _id;
public ShutdownHook() {
_id = __id.incrementAndGet();
}
@Override
public void run() {
setName("Log " + _id + " shutdown ");
setName("Log shutdown");
shutdown();
}
}

View File

@@ -34,6 +34,14 @@ public class ObjectCounter<K> implements Serializable {
return 1;
}
/**
* Set a high value
* @since 0.9.56
*/
public void max(K h) {
map.put(h, new AtomicInteger(Integer.MAX_VALUE / 2));
}
/**
* @return current count
*/

View File

@@ -1,3 +1,9 @@
i2p (1.9.0-1~bionic+1) bionic; urgency=medium
* New upstream version 1.9.0
-- zzz on i2p (key signing) <zzz@i2pmail.org> Tue, 23 Aug 2022 12:12:12 +0000
i2p (1.8.0-1~bionic+1) bionic; urgency=medium
* New upstream version 1.8.0

View File

@@ -1,3 +1,9 @@
i2p (1.9.0-1ubuntu1) focal; urgency=medium
* New upstream version 1.9.0
-- zzz on i2p (key signing) <zzz@i2pmail.org> Tue, 23 Aug 2022 12:12:12 +0000
i2p (1.8.0-1ubuntu1) focal; urgency=medium
* New upstream version 1.8.0

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
i2p (1.9.0-1ubuntu1) focal; urgency=medium
* New upstream version 1.9.0
-- zzz on i2p (key signing) <zzz@i2pmail.org> Tue, 23 Aug 2022 12:12:12 +0000
i2p (1.8.0-1ubuntu1) focal; urgency=medium
* New upstream version 1.8.0

2
debian/control vendored
View File

@@ -79,7 +79,7 @@ Architecture: all
Section: net
Priority: optional
Depends: ${misc:Depends}, ${java:Depends}, ${shlibs:Depends},
openjdk-18-jre-headless | openjdk-17-jre-headless | openjdk-16-jre-headless | openjdk-15-jre-headless | openjdk-11-jre-headless | default-jre-headless | java18-runtime-headless | java17-runtime-headless | java16-runtime-headless | java15-runtime-headless | java11-runtime-headless,
openjdk-18-jre-headless | openjdk-17-jre-headless | openjdk-11-jre-headless | default-jre-headless | java18-runtime-headless | java17-runtime-headless | java11-runtime-headless,
geoip-database,
gettext-base,
libgetopt-java,

View File

@@ -1,3 +1,36 @@
2022-09-06 zzz
* NetDB: Fix reseeding when clock is skewed
* SSU2: Don't publish or connect if our MTU becomes too small
2022-09-04 zzz
* SusiDNS: Fix adding to empty address book
2022-09-03 zzz
* NetDB: Query connected peers for their RI directly
* UPnP: Fix opening IPv6 ports
2022-09-01 zzz
* Router: Ensure database store message is processed before reply job (Gitlab #364)
2022-08-29 zzz
* SSU2: Implement ack-immediate flag
2022-08-28 zzz
* Console:
- Add notification and summary bar info on deadlock
- Linkify router hash even if not in netdb
* Util: Add option to gzip router logs
2022-08-25 zzz
* Router: Fix deadlock via rebuildRouterAddress() and UDPTransport
* SSU2:
- Implement path challenge and connection migration
- Fix packets exceeding MTU by up to 3 bytes
- Immediately fail session request containing zero token
2022-08-23 zzz
* Router: Add deadlocks to event log
2022-08-22 1.9.0 released
2022-08-10 zzz

View File

@@ -137,3 +137,4 @@ Sybil:2400;8500;1302;824;a150;95;144;951
Sybil:2400;8500;1302;825;150;95;147;89
Sybil:2400;8500;1302;828;a150;95;153;2202
Tunnels:JbifzqZZqeTXtxK6KDqNUPWaW-phKqeS~tfJT82SIYI=
Tunnels:QPUV1bW6arN2zp3gTBMvOEvgSuKbXUqk2oqHkb~UoSw=

View File

@@ -31,10 +31,13 @@
- Trial Debian build: Run 'ant debcheckpatch' and fix any issues.
Build and test a preliminary Debian build with 'ant debian' and fix any issues
- Javadoc test: 'ant javadoc' and 'ant mavenCentral.deps'
- Javadoc test: 'ant javadoc'
with a recent Oracle JDK (12+), and fix any issues.
Oracle JDK will error on things that OpenJDK does not!
- Java 7 test: 'ant mavenCentral.deps' to ensure
that Android will build correcly; fix any issues
## A day or two before

View File

@@ -51,8 +51,9 @@
Package install, running as a service: <code dir="ltr">/var/lib/i2p/i2p-config/eepsite/docroot/</code>
</li>
<li><b>Windows</b><br>
<code dir="ltr">%LOCALAPPDATA%\I2P\eepsite\docroot\</code>
</li>
Standard install: <code dir="ltr">%LOCALAPPDATA%\I2P\eepsite\docroot\</code>
Windows Service install: <code dir="ltr">%PROGRAMDATA%\I2P\eepsite\docroot\</code>
</li>
<li><b>Mac</b><br>
<code dir="ltr">/Users/(user)/Library/Application Support/i2p</code>
</li>
@@ -95,15 +96,15 @@
<li>Enter the name and paste the destination into your <a href="http://127.0.0.1:7657/susidns/addressbook?book=router&amp;filter=none">address book</a>.
Click "Add" to save the new entry.
</li>
<li>In your browser, enter in your website name (<i>something</i>.i2p) and you should be right back here.
<li>In your browser, enter in your website name (<i>something</i>.i2p) and you should be right back here.
Hopefully it worked!
</li>
<li>Before you tell the world about your new website, you should add some content.
Go to the server's root directory <a href="#dir">listed above</a> and replace the index.html redirect page with your own content.
</li>
</ul>
</ul>
<p>Directory listings are enabled, so you may host files from a subdirectory without providing a page with links to the files.
<p>Directory listings are enabled, so you may host files from a subdirectory without providing a page with links to the files.
You may change the appearance of the directory by supplying an edited <a href="lib/jetty-dir.css">jetty-dir.css</a> file for each directory.
The <a href="lib/">lib subdirectory</a> demonstrates a custom style.
The <a href="lib/resources">resources subdirectory</a> demonstrates the default style.
@@ -120,14 +121,14 @@
<h2>Register your own I2P Domain</h2>
<p>Now it is time to add your website to an I2P address book hosted by a site such as <a href="http://stats.i2p/" target="_blank">stats.i2p</a>.
<p>Now it is time to add your website to an I2P address book hosted by a site such as <a href="http://stats.i2p/" target="_blank">stats.i2p</a>.
You must enter your website name and Destination (or Registration Authentication string) on one or more of these sites.
</p><p>Some registration sites require the Destination.
If so, highlight and copy the entire Local destination on the <a href="http://127.0.0.1:7657/i2ptunnel/edit?tunnel=3">Hidden Service Configuration page</a>.
Make sure you copy the whole thing, it is over 500 characters.
</p><p>Some registration sites, including <a href="http://stats.i2p/i2p/addkey.html" target="_blank">stats.i2p</a>, require the Registration Authentication string.
It is found on the <a href="http://127.0.0.1:7657/i2ptunnel/register?tunnel=3">Registration Authentication page</a> linked from the <a href="http://127.0.0.1:7657/i2ptunnel/edit?tunnel=3">Hidden Service configuration page</a>.
Copy the entire authentication string and paste it on the registration page.
Copy the entire authentication string and paste it on the registration page.
Make sure you copy the whole thing, it is over 500 characters.
Describe your site briefly if the site requests this information.
If your site is a HTTP service, leave the checkbox selected; if not, unselect it.
@@ -146,14 +147,14 @@
<li><code dir="ltr">http://stats.i2p/cgi-bin/newhosts.txt</code> (<a href="http://stats.i2p/cgi-bin/newhosts.txt" target="_blank">stats.i2p</a>)
</li>
</ul>
<p>If you are in a hurry and can't wait a few hours, you can tell people to use a "jump" address helper redirection service.
<p>If you are in a hurry and can't wait a few hours, you can tell people to use a "jump" address helper redirection service.
This will work within a few minutes of your registering your hostname on the same site.
Test it yourself first by entering <code dir="ltr">http://stats.i2p/cgi-bin/jump.cgi?a=<i>something</i>.i2p</code> into your browser.
Test it yourself first by entering <code dir="ltr">http://stats.i2p/cgi-bin/jump.cgi?a=<i>something</i>.i2p</code> into your browser.
Once it is working, you can tell others to use it.
</p>
<p>Some people check website lists such as <a href="http://identiguy.i2p/" target="_blank">Identiguy's eepsite status list</a> or <a href="http://no.i2p/browse/" target="_blank">no.i2p's active host list</a> for new eepsites, so your site may start getting some traffic.
<p>Some people check website lists such as <a href="http://identiguy.i2p/" target="_blank">Identiguy's eepsite status list</a> or <a href="http://no.i2p/browse/" target="_blank">no.i2p's active host list</a> for new eepsites, so your site may start getting some traffic.
There are plenty of other ways to tell people. Here are a few ideas:
</p>
@@ -171,21 +172,21 @@
<h2>Using an alternative webserver to host your site</h2>
<p>This site (and the I2P router console) is running on the <a href="https://en.wikipedia.org/wiki/Jetty_(web_server)" target="_blank">Jetty webserver</a>, but you may wish to use a different webserver to host your content.
<p>This site (and the I2P router console) is running on the <a href="https://en.wikipedia.org/wiki/Jetty_(web_server)" target="_blank">Jetty webserver</a>, but you may wish to use a different webserver to host your content.
To maintain anonymity, be sure that your webserver is configured to only allow connections from localhost (127.0.0.1), and check the documentation to ensure your webserver isn't advertising details that may compromise your anonymity.
</p>
<p>To configure your webserver for use on I2P, you may either use the existing webserver tunnel and <a href="http://127.0.0.1:7657/configclients">disable the default webserver</a> from running, or create a new HTTP Server tunnel in the <a href="http://127.0.0.1:7657/i2ptunnelmgr">Hidden Services Manager</a>.
Ensure that the listening port configured for the webserver (7658 by default) is also configured in the Hidden Services settings.
For example, if your webserver is listening by default on address 127.0.0.1 port 80, you'd need to also ensure that the Target port in the Hidden Service Manager settings page for the service is also configured to port 80.
</p>
For example, if your webserver is listening by default on address 127.0.0.1 port 80, you'd need to also ensure that the Target port in the Hidden Service Manager settings page for the service is also configured to port 80.
</p>
<p>Beware that a poorly configured webserver or web appplication can leak potentially compromising information such as
<p>Beware that a poorly configured webserver or web appplication can leak potentially compromising information such as
your real IP address or server details that may reduce your anonymity or assist a hacker.
We recommend using the default server unless you feel comfortable doing server administration.
Please ensure your web server is secure prior to placing it online.
There are plenty of guides online, for example if you search for "nginx security hardening guide" you will find a number of guides that have good recommendations.
</p>
</p>
<p>There is one important issue with the Apache web server.
The <code dir="ltr">mod_status</code> and <code dir="ltr">mod_info</code> Apache modules are enabled by default on some operating systems.

View File

@@ -11,7 +11,7 @@ if "%1"=="uninstall" (
) else (
FINDSTR /I "^wrapper.java.additional.5=-Di2p.dir.config=" %_WRAPPER_CONF%
if not errorlevel 1 goto end
echo wrapper.java.additional.5=-Di2p.dir.config="%ALLUSERSPROFILE%\Application Data\i2p" >> %_WRAPPER_CONF%
echo wrapper.java.additional.5=-Di2p.dir.config="%PROGRAMDATA%\i2p" >> %_WRAPPER_CONF%
goto end
)

View File

@@ -3,7 +3,7 @@
<programGroup defaultName="I2P" location="startMenu" />
<shortcut name="Open I2P Profile Folder (service)"
target="explorer"
commandLine="&quot;%allusersprofile%\Application Data\i2p&quot;"
commandLine="&quot;%programdata%\i2p&quot;"
iconFile="%systemroot%\system32\shell32.dll"
iconIndex="3"
initialState="normal"

View File

@@ -286,10 +286,12 @@ public class RouterAddress extends DataStructureImpl {
// reduce Object proliferation
if (_transportStyle.equals("SSU"))
_transportStyle = "SSU";
else if (_transportStyle.equals("NTCP"))
_transportStyle = "NTCP";
else if (_transportStyle.equals("NTCP2"))
_transportStyle = "NTCP2";
else if (_transportStyle.equals("NTCP"))
_transportStyle = "NTCP";
else if (_transportStyle.equals("SSU2"))
_transportStyle = "SSU2";
DataHelper.readProperties(in, _options);
}

View File

@@ -17,6 +17,7 @@ import net.i2p.data.Hash;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.DeliveryStatusMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.TunnelDataMessage;
@@ -206,6 +207,34 @@ public class InNetMessagePool implements Service {
allowMatches = false;
break;
// If a DSM has a reply job, run the DSM inline
// so the entry is stored in the netdb before the reply job runs.
// FloodOnlyLookupMatchJob no longer stores the entry
case DatabaseStoreMessage.MESSAGE_TYPE:
List<OutNetMessage> origMessages = _context.messageRegistry().getOriginalMessages(messageBody);
HandlerJobBuilder dsmbuilder = _handlerJobBuilders[DatabaseStoreMessage.MESSAGE_TYPE];
Job dsmjob = dsmbuilder.createJob(messageBody, fromRouter, fromRouterHash);
int sz = origMessages.size();
if (sz > 0) {
// DSM inline, reply jobs on queue
if (dsmjob != null)
dsmjob.runJob();
for (int i = 0; i < sz; i++) {
OutNetMessage omsg = origMessages.get(i);
ReplyJob job = omsg.getOnReplyJob();
if (job != null) {
job.setMessage(messageBody);
_context.jobQueue().addJob(job);
}
}
} else {
// DSM on queue, no reply jobs
if (dsmjob != null)
_context.jobQueue().addJob(dsmjob);
}
allowMatches = false;
break;
default:
// why don't we allow type 0? There used to be a message of type 0 long ago...
if ( (type > 0) && (type < _handlerJobBuilders.length) ) {

View File

@@ -41,6 +41,7 @@ import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.CommSystemFacade.Status;
import net.i2p.router.crypto.FamilyKeyCrypto;
@@ -1003,9 +1004,11 @@ public class Router implements RouterClock.ClockShiftListener {
public void rebuildRouterInfo(boolean blockingRebuild) {
if (_log.shouldLog(Log.INFO))
_log.info("Rebuilding new routerInfo, publish inline? " + blockingRebuild, new Exception("I did it"));
// deadlock thru createAddresses() thru SSU REA... moved outside lock
List<RouterAddress> addresses = _context.commSystem().createAddresses();
_routerInfoLock.writeLock().lock();
try {
locked_rebuildRouterInfo(blockingRebuild);
locked_rebuildRouterInfo(addresses);
} finally {
_routerInfoLock.writeLock().unlock();
}
@@ -1016,9 +1019,8 @@ public class Router implements RouterClock.ClockShiftListener {
* Rebuild and republish our routerInfo since something significant
* has changed.
*
* @param blockingRebuild ignored, always nonblocking
*/
private void locked_rebuildRouterInfo(boolean blockingRebuild) {
private void locked_rebuildRouterInfo(List<RouterAddress> addresses) {
RouterInfo ri;
if (_routerInfo != null)
ri = new RouterInfo(_routerInfo);
@@ -1030,8 +1032,7 @@ public class Router implements RouterClock.ClockShiftListener {
Properties stats = _context.statPublisher().publishStatistics();
ri.setOptions(stats);
// deadlock thru createAddresses() thru SSU REA... move outside lock?
ri.setAddresses(_context.commSystem().createAddresses());
ri.setAddresses(addresses);
SigningPrivateKey key = _context.keyManager().getSigningPrivateKey();
if (key == null) {

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Git";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 0;
public final static long BUILD = 7;
/** for example "-test" */
public final static String EXTRA = "";

View File

@@ -0,0 +1,79 @@
package net.i2p.router.networkdb.kademlia;
import net.i2p.data.Hash;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.Job;
import net.i2p.router.RouterContext;
import net.i2p.router.OutNetMessage;
import net.i2p.util.Log;
/**
* Ask a connected peer for his RI.
* Modified from SingleSearchJob.
*
* Mainly for older routers. As of 0.9.55, transports will
* periodically send their RI.
* Some old routers may not respond or may send DSRM,
* e.g. if hidden (and i2pd?)
*
* @since 0.9.56
*/
class DirectLookupJob extends FloodOnlySearchJob {
private OutNetMessage _onm;
private final RouterInfo _oldRI;
private static final int TIMEOUT = 8*1000;
/**
* @param peer for Router Info only
*/
public DirectLookupJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash peer, RouterInfo oldRI, Job onFind, Job onFail) {
super(ctx, facade, peer, onFind, onFail, TIMEOUT);
_oldRI = oldRI;
}
@Override
public String getName() { return "NetDb direct RI request"; }
@Override
public boolean shouldProcessDSRM() { return false; } // don't loop
@Override
public void runJob() {
RouterContext ctx = getContext();
_onm = ctx.messageRegistry().registerPending(_replySelector, _onReply, _onTimeout);
DatabaseLookupMessage dlm = new DatabaseLookupMessage(ctx, true);
dlm.setFrom(ctx.routerHash());
long exp = ctx.clock().now() + 5*1000;
dlm.setMessageExpiration(exp);
dlm.setSearchKey(_key);
dlm.setSearchType(DatabaseLookupMessage.Type.RI);
OutNetMessage m = new OutNetMessage(ctx, dlm, exp,
OutNetMessage.PRIORITY_MY_NETDB_LOOKUP, _oldRI);
ctx.commSystem().processMessage(m);
_lookupsRemaining.set(1);
}
@Override
void failed() {
RouterContext ctx = getContext();
ctx.messageRegistry().unregisterPending(_onm);
ctx.profileManager().dbLookupFailed(_key);
_facade.complete(_key);
for (Job j : _onFailed) {
ctx.jobQueue().addJob(j);
}
}
@Override
void success() {
// don't give him any credit
//getContext().profileManager().dbLookupSuccessful(_to, System.currentTimeMillis()-_created);
_facade.complete(_key);
RouterContext ctx = getContext();
for (Job j : _onFind) {
ctx.jobQueue().addJob(j);
}
}
}

View File

@@ -0,0 +1,34 @@
package net.i2p.router.networkdb.kademlia;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.RouterContext;
/**
* Override to not call failed() in setMessage(),
* as it will be called from runJob()
*
* @since 0.9.56
*/
class DirectLookupMatchJob extends FloodOnlyLookupMatchJob {
public DirectLookupMatchJob(RouterContext ctx, FloodSearchJob job) {
super(ctx, job);
}
@Override
public String getName() { return "Direct lookup match"; }
/**
* Override to not call failed() in setMessage(),
* as it will be called from runJob()
*/
@Override
public void setMessage(I2NPMessage message) {
if (message.getType() != DatabaseStoreMessage.MESSAGE_TYPE)
return;
DatabaseStoreMessage dsm = (DatabaseStoreMessage)message;
if (dsm.getKey().equals(_search.getKey()))
_success = true;
}
}

View File

@@ -11,9 +11,9 @@ import net.i2p.router.RouterContext;
import net.i2p.util.Log;
class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
private final Log _log;
private final FloodSearchJob _search;
private volatile boolean _success;
protected final Log _log;
protected final FloodSearchJob _search;
protected volatile boolean _success;
public FloodOnlyLookupMatchJob(RouterContext ctx, FloodSearchJob job) {
super(ctx);
@@ -50,8 +50,8 @@ class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
return;
DatabaseStoreMessage dsm = (DatabaseStoreMessage)message;
if (_log.shouldLog(Log.INFO))
_log.info(_search.getJobId() + ": got a DSM for "
if (_log.shouldDebug())
_log.debug(_search.getJobId() + ": got a DSM for "
+ dsm.getKey().toBase64());
// This store will handled by HFDSMJ.
// Just note success here.

View File

@@ -56,9 +56,24 @@ abstract class FloodOnlySearchJob extends FloodSearchJob {
_timeoutMs = Math.min(timeoutMs, SearchJob.PER_FLOODFILL_PEER_TIMEOUT);
_expiration = _timeoutMs + ctx.clock().now();
_unheardFrom = new HashSet<Hash>(CONCURRENT_SEARCHES);
_replySelector = new FloodOnlyLookupSelector(getContext(), this);
_onReply = new FloodOnlyLookupMatchJob(getContext(), this);
_onTimeout = new FloodOnlyLookupTimeoutJob(getContext(), this);
_replySelector = new FloodOnlyLookupSelector(ctx, this);
_onReply = new FloodOnlyLookupMatchJob(ctx, this);
_onTimeout = new FloodOnlyLookupTimeoutJob(ctx, this);
}
/**
* For DirectLookupJob extension, RI only, different match job
*
* @since 0.9.56
*/
protected FloodOnlySearchJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash key, Job onFind, Job onFailed, int timeoutMs) {
super(ctx, facade, key, onFind, onFailed, timeoutMs, false);
_timeoutMs = timeoutMs;
_expiration = _timeoutMs + ctx.clock().now();
_unheardFrom = new HashSet<Hash>(1);
_replySelector = new FloodOnlyLookupSelector(ctx, this);
_onReply = new DirectLookupMatchJob(ctx, this);
_onTimeout = new FloodOnlyLookupTimeoutJob(ctx, this);
}
public boolean shouldProcessDSRM() { return _shouldProcessDSRM; }

View File

@@ -464,12 +464,12 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
}
if (isNew) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("this is the first search for that key, fire off the FloodSearchJob");
if (_log.shouldDebug())
_log.debug("New ISJ for " + key.toBase64());
_context.jobQueue().addJob(searchJob);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Deferring flood search for " + key.toBase64() + " with " + _activeFloodQueries.size() + " in progress");
if (_log.shouldDebug())
_log.debug("Wait for pending ISJ for " + key.toBase64());
searchJob.addDeferred(onFindJob, onFailedLookupJob, timeoutMs, isLease);
// not necessarily LS
_context.statManager().addRateData("netDb.lookupDeferred", 1, searchJob.getExpiration()-_context.clock().now());
@@ -579,6 +579,32 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
*/
@Override
protected void lookupBeforeDropping(Hash peer, RouterInfo info) {
if (_context.commSystem().isEstablished(peer)) {
// see DirectLookupJob
boolean isNew = false;
FloodSearchJob searchJob;
Job onFindJob = new DropLookupFoundJob(_context, peer, info);
Job onFailedLookupJob = new DropLookupFailedJob(_context, peer, info);
synchronized (_activeFloodQueries) {
searchJob = _activeFloodQueries.get(peer);
if (searchJob == null) {
searchJob = new DirectLookupJob(_context, this, peer, info, onFindJob, onFailedLookupJob);
_activeFloodQueries.put(peer, searchJob);
isNew = true;
}
}
if (isNew) {
if (_log.shouldDebug())
_log.debug("Direct RI lookup for " + peer.toBase64());
_context.jobQueue().addJob(searchJob);
} else {
if (_log.shouldDebug())
_log.debug("Pending Direct RI lookup for " + peer.toBase64());
searchJob.addDeferred(onFindJob, onFailedLookupJob, 10*1000, false);
}
return;
}
// following are some special situations, we don't want to
// drop the peer in these cases
// yikes don't do this - stack overflow // getFloodfillPeers().size() == 0 ||
@@ -609,17 +635,17 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
// entry locally, firing no job if it gets a reply with an updated value (meaning
// we shouldn't drop them but instead use the new data), or if they all time out,
// firing the dropLookupFailedJob, which actually removes out local reference
if (_log.shouldDebug())
_log.debug("ISJ lookup before dropping for " + peer.toBase64() + ' ' + info.getPublished());
search(peer, new DropLookupFoundJob(_context, peer, info), new DropLookupFailedJob(_context, peer, info), 10*1000, false);
}
private class DropLookupFailedJob extends JobImpl {
private final Hash _peer;
private final RouterInfo _info;
public DropLookupFailedJob(RouterContext ctx, Hash peer, RouterInfo info) {
super(ctx);
_peer = peer;
_info = info;
}
public String getName() { return "Lookup on failure of netDb peer timed out"; }
public void runJob() {
@@ -639,10 +665,8 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
public String getName() { return "Lookup on failure of netDb peer matched"; }
public void runJob() {
RouterInfo updated = lookupRouterInfoLocally(_peer);
if ( (updated != null) && (updated.getPublished() > _info.getPublished()) ) {
// great, a legitimate update
} else {
// they just sent us what we already had. kill 'em both
if (updated == null || updated.getPublished() <= _info.getPublished()) {
// they just sent us what we already had
dropAfterLookupFailed(_peer);
}
}

View File

@@ -1379,6 +1379,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad
*/
void dropAfterLookupFailed(Hash peer) {
_context.peerManager().removeCapabilities(peer);
_negativeCache.cache(peer);
_kb.remove(peer);
//if (removed) {
// if (_log.shouldLog(Log.INFO))

View File

@@ -36,6 +36,15 @@ class NegativeLookupCache {
this.counter.increment(h);
}
/**
* Negative cache the hash until the next clean time.
*
* @since 0.9.56
*/
public void cache(Hash h) {
this.counter.max(h);
}
public boolean isCached(Hash h) {
if (counter.count(h) >= _maxFails)
return true;

View File

@@ -366,7 +366,7 @@ public class PersistentDataStore extends TransientDataStore {
requeue(READ_DELAY);
return;
}
long now = getContext().clock().now();
long now = System.currentTimeMillis();
// check directory mod time to save a lot of object churn in scanning all the file names
long lastMod = _dbDir.lastModified();
// if size() (= RI + LS) is too low, call anyway to check for reseed

View File

@@ -80,14 +80,14 @@ public class WorkingDir {
} else {
String home = System.getProperty("user.home");
if (isWindows) {
String appdata = System.getenv("LOCALAPPDATA");
if (appdata != null) {
home = appdata;
String localappdata = System.getenv("LOCALAPPDATA");
if (localappdata != null) {
home = localappdata;
}
// Don't mess with existing Roaming Application Data installs,
// in case somebody is using roaming appdata for a reason
// already. In new installs, use local appdata by default. -idk
appdata = System.getenv("APPDATA");
String appdata = System.getenv("APPDATA");
if (appdata != null) {
File checkOld = new File(appdata, WORKING_DIR_DEFAULT_WINDOWS);
if (checkOld.exists() && checkOld.isDirectory()){
@@ -106,6 +106,7 @@ public class WorkingDir {
if (routerConfig.exists() && clientAppsConfig.exists())
home = appdata;
}
System.err.println("System is Windows: " + home);
}
}
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_WINDOWS);
@@ -391,7 +392,7 @@ public class WorkingDir {
}
out.println(s);
}
System.err.println("Copied " + oldFile + " with modifications");
System.err.println("Copied file " + oldFile + " with modifications");
if (out.checkError())
throw new IOException("Failed write to " + newFile);
return true;
@@ -456,7 +457,7 @@ public class WorkingDir {
System.err.println("FAILED copy " + src.getPath());
return false;
}
System.err.println("Created " + targetDir.getPath());
System.err.println("Created Directory " + targetDir.getPath());
}
// SecureDirectory is a File so this works for non-directories too
File targetFile = new SecureDirectory(targetDir, src.getName());
@@ -473,7 +474,7 @@ public class WorkingDir {
System.err.println("FAILED copy " + src.getPath());
return false;
}
System.err.println("Created " + targetFile.getPath());
System.err.println("Created File " + targetFile.getPath());
}
boolean rv = true;
for (int i = 0; i < children.length; i++) {
@@ -497,7 +498,7 @@ public class WorkingDir {
in = new FileInputStream(src);
out = new SecureFileOutputStream(dst);
DataHelper.copy(in, out);
System.err.println("Copied " + src.getPath());
System.err.println("Copied File " + src.getPath());
} catch (IOException ioe) {
System.err.println("FAILED copy " + src.getPath() + ": " + ioe);
rv = false;

View File

@@ -632,17 +632,12 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
buf.append("<img height=\"11\" width=\"16\" alt=\"").append(c.toUpperCase(Locale.US)).append("\" title=\"");
buf.append(countryName);
buf.append("\" src=\"/flags.jsp?c=").append(c).append("\"> ");
}
else
} else {
buf.append("<img class=\"unknownflag\" height=\"11\" width=\"16\" alt=\"??\" src=\"/flags.jsp?c=a0\" title=\"").append(_t("unknown")).append("\"> ");
buf.append("<tt>");
boolean found = _context.netDb().lookupRouterInfoLocally(peer) != null;
if (found)
buf.append("<a title=\"").append(_t("NetDb entry")).append("\" href=\"netdb?r=").append(h).append("\">");
}
buf.append("<tt><a title=\"").append(_t("NetDb entry")).append("\" href=\"netdb?r=").append(h).append("\">");
buf.append(h, 0, 4);
if (found)
buf.append("</a>");
buf.append("</tt>");
buf.append("</a></tt>");
return buf.toString();
}

View File

@@ -828,7 +828,7 @@ public class TransportManager implements TransportEventListener {
config != TransportUtil.IPv6Config.IPV6_DISABLED &&
!t.isIPv6Firewalled()) {
RouterAddress ra = t.getCurrentAddress(true);
if (ra == null) {
if (ra == null || ra.getHost() == null) {
if (t.getStyle().equals(UDPTransport.STYLE)) {
UDPTransport udp = (UDPTransport) t;
ra = udp.getCurrentExternalAddress(true);

View File

@@ -250,7 +250,7 @@ public class NTCPTransport extends TransportImpl {
String s = null;
// try to determine if we've been down for 30 days or more
long minDowntime = _context.router().isHidden() ? MIN_DOWNTIME_TO_REKEY_HIDDEN : MIN_DOWNTIME_TO_REKEY;
boolean shouldRekey = _context.getEstimatedDowntime() >= minDowntime;
boolean shouldRekey = !allowLocal() && _context.getEstimatedDowntime() >= minDowntime;
if (!shouldRekey) {
s = ctx.getProperty(PROP_NTCP2_SP);
if (s != null) {

View File

@@ -76,7 +76,6 @@ class OutboundNTCP2State implements EstablishState {
private final HandshakeState _handshakeState;
private final RouterInfo _aliceRI;
private final int _aliceRISize;
private int _padlen1;
private int _padlen2;
private final int _padlen3;
private final SessionKey _bobHash;

View File

@@ -432,7 +432,10 @@ class EstablishmentManager {
}
if (version == 2) {
int mtu = addr.getMTU();
if (mtu > 0 && mtu < PeerState2.MIN_MTU) {
boolean isIPv6 = TransportUtil.isIPv6(ra);
int ourMTU = _transport.getMTU(isIPv6);
if ((mtu > 0 && mtu < PeerState2.MIN_MTU) ||
(ourMTU > 0 && ourMTU < PeerState2.MIN_MTU)) {
if (ra.getTransportStyle().equals("SSU2")) {
_transport.markUnreachable(toHash);
_transport.failed(msg, "MTU too small");
@@ -677,7 +680,12 @@ class EstablishmentManager {
return;
}
/**** TODO
/****
// A token request or session request with a bad token is
// inexpensive to reply to.
// A token can only be used once, so a replayed session request
// will only generate a retry.
// So probably don't need a replay detector at all
if (_replayFilter.add(state.getReceivedX(), 0, 8)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Duplicate X in session request from: " + from);

View File

@@ -121,6 +121,8 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
(ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) {
if (_log.shouldInfo())
_log.info("Invalid token " + token + " in session request from: " + _aliceSocketAddress);
if (token == 0)
throw new GeneralSecurityException("Zero token in session request from: " + _aliceSocketAddress);
_currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED;
_sendHeaderEncryptKey2 = introKey;
// Generate token for the retry.
@@ -162,14 +164,17 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
// termination block received
throw new GeneralSecurityException("Termination block in Session/Token Request");
}
if (_timeReceived == 0)
if (_timeReceived == 0) {
_currentState = InboundState.IB_STATE_FAILED;
throw new GeneralSecurityException("No DateTime block in Session/Token Request");
}
_skew = _establishBegin - _timeReceived;
if (_skew > MAX_SKEW || _skew < 0 - MAX_SKEW) {
_currentState = InboundState.IB_STATE_FAILED;
// send retry with termination
UDPPacket retry = _transport.getBuilder2().buildRetryPacket(this, SSU2Util.REASON_SKEW);
_transport.send(retry);
throw new GeneralSecurityException("Skew exceeded in Session/Token Request: " + _skew);
throw new GeneralSecurityException("Skew exceeded in Session/Token Request (retry sent): " + _skew);
}
packetReceived();
if (_log.shouldDebug())
@@ -185,7 +190,8 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
if (_log.shouldDebug())
_log.debug("Processed " + blocks + " blocks on " + this);
} catch (Exception e) {
_log.error("IES2 payload error\n" + net.i2p.util.HexDump.dump(payload, 0, length), e);
if (_log.shouldWarn())
_log.warn("IES2 payload error\n" + net.i2p.util.HexDump.dump(payload, 0, length), e);
throw new GeneralSecurityException("IES2 payload error", e);
}
}
@@ -472,6 +478,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
/** note that we just sent a Retry packet */
public synchronized void retryPacketSent() {
// retry after clock skew
if (_currentState == InboundState.IB_STATE_FAILED)
return;
if (_currentState != InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED &&
_currentState != InboundState.IB_STATE_TOKEN_REQUEST_RECEIVED)
throw new IllegalStateException("Bad state for Retry Sent: " + _currentState);

View File

@@ -90,7 +90,7 @@ class IntroductionManager {
/** map of relay tag to PeerState who have given us introduction tags */
private final Map<Long, PeerState> _inbound;
/** map of relay nonce to alice PeerState who requested it */
private final Map<Long, PeerState2> _nonceToAlice;
private final ConcurrentHashMap<Long, PeerState2> _nonceToAlice;
private final Set<InetAddress> _recentHolePunches;
private long _lastHolePunchClean;

View File

@@ -4,7 +4,9 @@ import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
@@ -60,6 +62,8 @@ public class MTU {
return 0;
}
if (ifcs != null) {
// save for fallback loop below, so we don't have to call getNetworkInterfaces() again
List<NetworkInterface> interfaces = new ArrayList<NetworkInterface>();
while (ifcs.hasMoreElements()) {
NetworkInterface ifc = ifcs.nextElement();
try {
@@ -72,6 +76,7 @@ public class MTU {
} catch (SocketException e) {
continue;
}
interfaces.add(ifc);
for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress addr = addrs.nextElement();
if (ia.equals(addr)) {
@@ -105,6 +110,10 @@ public class MTU {
}
if (isSSU2)
return Math.min(mtu, PeerState2.MAX_MTU);
// don't rectify 1280 down to 1276 because that
// borks a shared SSU/SSU2 address
if (mtu == PeerState2.MIN_MTU)
return PeerState2.MIN_MTU;
return rectify(isIPv6, mtu);
} catch (SocketException se) {
// ignore
@@ -116,6 +125,59 @@ public class MTU {
}
}
}
// didn't find a match, probably behind a NAT
// try again, looping through all the interfaces that were up,
// just get the minimum of all interfaces with addresses of that type v4/v6
boolean isIPv6 = ia instanceof Inet6Address;
int rv = 1501;
outer:
for (NetworkInterface ifc : interfaces) {
for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress addr = addrs.nextElement();
if (isIPv6 != (addr instanceof Inet6Address))
continue;
if (addr.isLinkLocalAddress() ||
addr.isMulticastAddress() ||
addr.isAnyLocalAddress() ||
addr.isLoopbackAddress())
continue;
if (isIPv6) {
// ygg
byte[] ip = addr.getAddress();
if ((ip[0] & 0xfe) == 0x02)
continue outer;
}
int mtu;
try {
mtu = ifc.getMTU();
} catch (Throwable t) {
continue outer;
}
if (mtu < 0)
continue outer;
if (isIPv6 && mtu > 1420) {
byte[] ip = addr.getAddress();
if (mtu > 1472 &&
ip[0] == 0x20 && ip[1] == 0x01 &&
ip[2] == 0x04 && ip[3] == 0x70)
mtu = 1472;
if (ip[0] == 0x2a && ip[1] == 0x06 &&
ip[2] == (byte) 0xa0 && ip[3] == 0x04)
mtu = 1420;
}
if (mtu < rv)
rv = mtu;
continue outer;
}
}
if (rv < 1501) {
if (isSSU2)
return rv;
if (rv == PeerState2.MIN_MTU)
return rv;
return rectify(isIPv6, rv);
}
}
return 0;
}
@@ -140,18 +202,20 @@ public class MTU {
}
public static void main(String args[]) {
/****
System.out.println("Cmd line interfaces:");
for (int i = 0; i < args.length; i++) {
try {
InetAddress test = InetAddress.getByName(args[i]);
System.out.println("MTU of " + args[i] + " is " + getMTU(test));
} catch (Exception e) {
e.printStackTrace();
if (args.length > 0) {
System.out.println("Cmd line interfaces:");
for (int i = 0; i < args.length; i++) {
try {
InetAddress test = InetAddress.getByName(args[i]);
System.out.println("I2P MTU of " + args[i] + " is " + getMTU(test, false) +
"; SSU2 MTU is " + getMTU(test, true));
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println("All interfaces:");
****/
try {
Enumeration<NetworkInterface> ifcs = NetworkInterface.getNetworkInterfaces();
if (ifcs != null) {

View File

@@ -463,8 +463,11 @@ class OutboundMessageFragments {
if (_log.shouldDebug())
_log.debug("Building packet for " + next + " to " + peer);
int curTotalDataSize = state.fragmentSize(next.num);
if (next.num > 0 && peer.getVersion() > 1)
curTotalDataSize += SSU2Util.DATA_FOLLOWON_EXTRA_SIZE;
if (peer.getVersion() > 1) {
curTotalDataSize += SSU2Util.FIRST_FRAGMENT_HEADER_SIZE;
if (next.num > 0)
curTotalDataSize += SSU2Util.DATA_FOLLOWON_EXTRA_SIZE;
}
// now stuff in more fragments if they fit
if (i +1 < toSend.size()) {
int maxAvail;

View File

@@ -275,6 +275,11 @@ class PacketBuilder2 {
//if (_log.shouldDebug())
// _log.debug("Packet " + pktNum + " before encryption:\n" + HexDump.dump(data, 0, off));
// ack immediate flag
if (numFragments > 0) {
data[SHORT_HEADER_FLAGS_OFFSET] = peer.getFlags();
}
encryptDataPacket(packet, peer.getSendCipher(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
//if (_log.shouldDebug())
@@ -343,7 +348,7 @@ class PacketBuilder2 {
*
*/
public UDPPacket buildACK(PeerState2 peer) {
return buildPacket(Collections.emptyList(), peer);
return buildPacket(Collections.<Fragment>emptyList(), peer);
}
/**
@@ -351,8 +356,8 @@ class PacketBuilder2 {
* This will also include acks, a new token block, and padding.
*/
public UDPPacket buildSessionDestroyPacket(int reason, PeerState2 peer) {
if (_log.shouldWarn())
_log.warn("Sending termination " + reason + " to : " + peer);
if (_log.shouldDebug())
_log.debug("Sending termination " + reason + " to : " + peer);
List<Block> blocks = new ArrayList<Block>(2);
if (peer.getKeyEstablishedTime() - _context.clock().now() > EstablishmentManager.IB_TOKEN_EXPIRATION / 2 &&
!_context.router().gracefulShutdownInProgress()) {
@@ -363,7 +368,7 @@ class PacketBuilder2 {
}
Block block = new SSU2Payload.TerminationBlock(reason, peer.getReceivedMessages().getHighestSet());
blocks.add(block);
UDPPacket packet = buildPacket(Collections.emptyList(), blocks, peer);
UDPPacket packet = buildPacket(Collections.<Fragment>emptyList(), blocks, peer);
packet.setMessageType(TYPE_DESTROY);
return packet;
}
@@ -652,7 +657,7 @@ class PacketBuilder2 {
*/
public UDPPacket buildPeerTestFromAlice(byte[] signedData, PeerState2 bob) {
Block block = new SSU2Payload.PeerTestBlock(1, 0, null, signedData);
UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), bob);
UDPPacket rv = buildPacket(Collections.<Fragment>emptyList(), Collections.singletonList(block), bob);
rv.setMessageType(TYPE_TFA);
return rv;
}
@@ -688,7 +693,7 @@ class PacketBuilder2 {
*/
public UDPPacket buildPeerTestToAlice(int code, Hash charlieHash, byte[] signedData, PeerState2 alice) {
Block block = new SSU2Payload.PeerTestBlock(4, code, charlieHash, signedData);
UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), alice);
UDPPacket rv = buildPacket(Collections.<Fragment>emptyList(), Collections.singletonList(block), alice);
rv.setMessageType(TYPE_TTA);
return rv;
}
@@ -724,7 +729,7 @@ class PacketBuilder2 {
*/
public UDPPacket buildPeerTestToCharlie(Hash aliceHash, byte[] signedData, PeerState2 charlie) {
Block block = new SSU2Payload.PeerTestBlock(2, 0, aliceHash, signedData);
UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), charlie);
UDPPacket rv = buildPacket(Collections.<Fragment>emptyList(), Collections.singletonList(block), charlie);
rv.setMessageType(TYPE_TBC);
return rv;
}
@@ -737,7 +742,7 @@ class PacketBuilder2 {
*/
public UDPPacket buildPeerTestToBob(int code, byte[] signedData, PeerState2 bob) {
Block block = new SSU2Payload.PeerTestBlock(3, code, null, signedData);
UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), bob);
UDPPacket rv = buildPacket(Collections.<Fragment>emptyList(), Collections.singletonList(block), bob);
rv.setMessageType(TYPE_TCB);
return rv;
}
@@ -751,7 +756,7 @@ class PacketBuilder2 {
*/
UDPPacket buildRelayRequest(byte[] signedData, PeerState2 bob) {
Block block = new SSU2Payload.RelayRequestBlock(signedData);
UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), bob);
UDPPacket rv = buildPacket(Collections.<Fragment>emptyList(), Collections.singletonList(block), bob);
rv.setMessageType(TYPE_RREQ);
rv.setPriority(PRIORITY_HIGH);
return rv;
@@ -766,7 +771,7 @@ class PacketBuilder2 {
*/
UDPPacket buildRelayIntro(byte[] signedData, PeerState2 charlie) {
Block block = new SSU2Payload.RelayIntroBlock(signedData);
UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), charlie);
UDPPacket rv = buildPacket(Collections.<Fragment>emptyList(), Collections.singletonList(block), charlie);
rv.setMessageType(TYPE_INTRO);
return rv;
}
@@ -781,7 +786,7 @@ class PacketBuilder2 {
*/
UDPPacket buildRelayResponse(byte[] signedData, PeerState2 state) {
Block block = new SSU2Payload.RelayResponseBlock(signedData);
UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), state);
UDPPacket rv = buildPacket(Collections.<Fragment>emptyList(), Collections.singletonList(block), state);
rv.setMessageType(TYPE_RESP);
return rv;
}

View File

@@ -208,7 +208,7 @@ public class PeerState {
protected final UDPTransport _transport;
/** have we migrated away from this peer to another newer one? */
private volatile boolean _dead;
protected volatile boolean _dead;
/** The minimum number of outstanding messages (NOT fragments/packets) */
private static final int MIN_CONCURRENT_MSGS = 8;
@@ -2214,6 +2214,17 @@ public class PeerState {
}
}
/**
* SSU 2 only
*
* @since 0.9.56
*/
protected boolean shouldRequestImmediateAck() {
synchronized(_sendWindowBytesRemainingLock) {
return _sendWindowBytesRemaining < _sendWindowBytes / 3;
}
}
/**
* Transfer the basic activity/state from the old peer to the current peer
*

View File

@@ -5,6 +5,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -24,7 +25,10 @@ import net.i2p.data.i2np.I2NPMessageException;
import net.i2p.data.i2np.I2NPMessageImpl;
import net.i2p.router.RouterContext;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.TransportUtil;
import net.i2p.router.transport.udp.InboundMessageFragments.ModifiableLong;
import net.i2p.router.transport.udp.PacketBuilder.Fragment;
import static net.i2p.router.transport.udp.SSU2Util.*;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
@@ -59,6 +63,8 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
*/
private final SSU2Bitfield _ackedMessages;
private final ConcurrentHashMap<Long, List<PacketBuilder.Fragment>> _sentMessages;
private final ACKTimer _ackTimer;
private long _sentMessagesLastExpired;
private byte[] _ourIP;
private int _ourPort;
@@ -68,6 +74,28 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
private long _sessConfSentTime;
private int _sessConfSentCount;
// Connection Migration, synch on _migrationLock
private enum MigrationState {
MIGRATION_STATE_NONE,
MIGRATION_STATE_PENDING,
// unused below here
MIGRATION_STATE_CANCELLED,
MIGRATION_STATE_FAILED,
MIGRATION_STATE_SUCCESS
}
private final Object _migrationLock = new Object();
private MigrationState _migrationState = MigrationState.MIGRATION_STATE_NONE;
private long _migrationStarted;
private long _migrationNextSendTime;
private byte[] _pathChallengeData;
private long _pathChallengeSendCount;
private RemoteHostId _pendingRemoteHostId;
private RemoteHostId _previousRemoteHostId;
private static final int MAX_PATH_CHALLENGE_SENDS = 4;
private static final long MAX_PATH_CHALLENGE_TIME = 30*1000;
private static final long PATH_CHALLENGE_DELAY = 5*1000;
// As SSU
public static final int MIN_SSU_IPV4_MTU = 1292;
public static final int MAX_SSU_IPV4_MTU = 1484;
@@ -114,6 +142,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
// For outbound, SessionConfirmed is packet 0
_packetNumber.set(1);
}
_ackTimer = new ACKTimer();
}
// SSU 1 overrides
@@ -171,7 +200,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
protected synchronized void messagePartiallyReceived(long now) {
if (_wantACKSendSince <= 0) {
_wantACKSendSince = now;
new ACKTimer();
_ackTimer.schedule();
}
}
@@ -318,15 +347,9 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
*
* @param from source address
* @param packet fully encrypted, header and body decryption will be done here
* @since 0.9.56
* @since 0.9.55
*/
void receivePacket(RemoteHostId from, UDPPacket packet) {
if (!from.equals(_remoteHostId)) {
if (_log.shouldWarn())
_log.warn("Got packet from " + from + " expected " + _remoteHostId + " on " + this);
// Connection Migration TODO
}
DatagramPacket dpacket = packet.getPacket();
byte[] data = dpacket.getData();
int off = dpacket.getOffset();
@@ -386,17 +409,120 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
_log.warn("dup pkt rcvd: " + n + " on " + this);
return;
}
int payloadLen = len - (SHORT_HEADER_SIZE + MAC_LEN);
if (_log.shouldDebug())
_log.debug("New " + len + " byte pkt " + n + " rcvd on " + this);
SSU2Payload.processPayload(_context, this, data, off + SHORT_HEADER_SIZE, payloadLen, false, from);
packetReceived(payloadLen);
if (!_dead) {
// Connection Migration
// We process packets regardless of source.
// This is after all the header checks, decryption, and payload processing.
// Any failures will have thrown or returned before here.
// Path Response callback is before this and will reset the state if successful.
boolean limitSending = false;
synchronized(_migrationLock) {
switch(_migrationState) {
case MIGRATION_STATE_NONE:
if (!from.equals(_remoteHostId)) {
// QUIC: Must be highest set to protect against reordered packets
if (SSU2Util.ENABLE_PATH_CHALLENGE &&
from.getIP().length == _remoteHostId.getIP().length &&
n == _receivedMessages.getHighestSet() &&
TransportUtil.isValidPort(from.getPort()) &&
_transport.isValid(from.getIP())) {
// send challenge
if (_log.shouldWarn())
_log.warn("Start migration to " + from + " on " + this);
_migrationState = MigrationState.MIGRATION_STATE_PENDING;
_migrationStarted = _context.clock().now();
_migrationNextSendTime = _migrationStarted + PATH_CHALLENGE_DELAY;
_pathChallengeData = new byte[8];
_context.random().nextBytes(_pathChallengeData);
_pathChallengeSendCount = 1;
_pendingRemoteHostId = from;
sendPathChallenge(dpacket.getAddress(), from.getPort());
} else {
// don't attempt to switch
if (_log.shouldWarn())
_log.warn("Not migrating to " + from + " on " + this);
}
limitSending = true;
}
break;
case MIGRATION_STATE_PENDING:
if (from.equals(_remoteHostId)) {
// cancel
_migrationState = MigrationState.MIGRATION_STATE_NONE;
if (_log.shouldWarn())
_log.warn("Cancel migration on " + this);
} else {
// still waiting
long now = _context.clock().now();
if (now > _migrationStarted + MAX_PATH_CHALLENGE_TIME ||
_pathChallengeSendCount > MAX_PATH_CHALLENGE_SENDS) {
// time exceeded
_migrationState = MigrationState.MIGRATION_STATE_NONE;
if (_log.shouldWarn())
_log.warn("Migration failed on " + this);
} else if (from.equals(_pendingRemoteHostId)) {
if (_log.shouldWarn())
_log.warn("Migration pending, got another packet from " + from + " on " + this);
if (now > _migrationNextSendTime) {
// retransmit challenge
_migrationNextSendTime = now + (PATH_CHALLENGE_DELAY << _pathChallengeSendCount);
_pathChallengeSendCount++;
sendPathChallenge(dpacket.getAddress(), from.getPort());
}
limitSending = true;
} else {
// a third ip/port ???
if (_log.shouldWarn())
_log.warn("Migration pending, got packet from 3rd address " + from + " on " + this);
limitSending = true;
}
}
break;
}
}
if (limitSending)
ECNReceived();
} //// !_dead
boolean ackImmediate = (header.data[SHORT_HEADER_FLAGS_OFFSET] & 0x01) != 0 && _context.getBooleanProperty("ssu2.ackImmediate");
if (ackImmediate) {
_ackTimer.scheduleImmediate();
}
} catch (Exception e) {
if (_log.shouldWarn())
_log.warn("Bad encrypted packet on: " + this + '\n' + HexDump.dump(data, off, len), e);
}
}
/**
* Caller must synch on _migrationState
* @since 0.9.56
*/
private void sendPathChallenge(InetAddress toIP, int toPort) {
if (_log.shouldWarn())
_log.warn("Send path challenge to " + toIP + ' ' + toPort + " on " + this);
List<SSU2Payload.Block> blocks = new ArrayList<SSU2Payload.Block>(3);
blocks.add(new SSU2Payload.DateTimeBlock(_context));
blocks.add(new SSU2Payload.AddressBlock(toIP.getAddress(), toPort));
blocks.add(new SSU2Payload.PathChallengeBlock(_pathChallengeData));
UDPPacket packet = _transport.getBuilder2().buildPacket(Collections.emptyList(), blocks, this);
// fix up IP/port
DatagramPacket pkt = packet.getPacket();
pkt.setAddress(toIP);
pkt.setPort(toPort);
_transport.send(packet);
}
/////////////////////////////////////////////////////////
// begin payload callbacks
/////////////////////////////////////////////////////////
@@ -410,8 +536,8 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
}
public void gotRI(RouterInfo ri, boolean isHandshake, boolean flood) throws DataFormatException {
if (_log.shouldInfo())
_log.info("Got RI in data phase " + ri + "\non: " + this);
if (_log.shouldDebug())
_log.debug("Got RI in data phase " + ri + "\non: " + this);
try {
Hash h = ri.getHash();
if (h.equals(_context.routerHash()))
@@ -457,7 +583,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
}
if (tag > 0) {
SSU2Payload.Block block = new SSU2Payload.RelayTagBlock(tag);
UDPPacket pkt = _transport.getBuilder2().buildPacket(Collections.emptyList(),
UDPPacket pkt = _transport.getBuilder2().buildPacket(Collections.<Fragment>emptyList(),
Collections.singletonList(block),
this);
_transport.send(pkt);
@@ -643,13 +769,14 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
_transport.send(pkt);
}
_transport.getEstablisher().receiveSessionDestroy(_remoteHostId, this);
_dead = true;
}
public void gotPathChallenge(RemoteHostId from, byte[] data) {
if (_log.shouldInfo())
_log.info("Got PATH CHALLENGE block, length: " + data.length + " on " + this);
SSU2Payload.Block block = new SSU2Payload.PathResponseBlock(data);
UDPPacket pkt = _transport.getBuilder2().buildPacket(Collections.emptyList(),
UDPPacket pkt = _transport.getBuilder2().buildPacket(Collections.<Fragment>emptyList(),
Collections.singletonList(block),
this);
// TODO send to from address?
@@ -659,13 +786,51 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
public void gotPathResponse(RemoteHostId from, byte[] data) {
if (_log.shouldInfo())
_log.info("Got PATH RESPONSE block, length: " + data.length + " on " + this);
// TODO
synchronized(_migrationLock) {
switch(_migrationState) {
case MIGRATION_STATE_PENDING:
if (from.equals(_pendingRemoteHostId) && DataHelper.eq(data, _pathChallengeData)) {
// success
_migrationState = MigrationState.MIGRATION_STATE_NONE;
_pathChallengeData = null;
if (_log.shouldWarn())
_log.warn("Migration successful, changed address from " + _remoteHostId + " to " + from + " for " + this);
_transport.changePeerAddress(this, from);
_mtu = MIN_MTU;
EstablishmentManager.Token token = _transport.getEstablisher().getInboundToken(from);
SSU2Payload.Block block = new SSU2Payload.NewTokenBlock(token.token, token.expires);
UDPPacket pkt = _transport.getBuilder2().buildPacket(Collections.emptyList(),
Collections.singletonList(block),
this);
_transport.send(pkt);
} else {
// caller will handle
// ACK-eliciting
messagePartiallyReceived();
}
break;
default:
messagePartiallyReceived();
break;
}
}
}
/////////////////////////////////////////////////////////
// end payload callbacks
/////////////////////////////////////////////////////////
/**
* Caller should sync; UDPTransport must remove and add to peersByRemoteHost map
* @since 0.9.56
*/
void changeAddress(RemoteHostId id) {
_previousRemoteHostId = _remoteHostId;
_remoteHostId = id;
_remotePort = id.getPort();
}
/**
* Do what MessageReceiver does, but inline and for SSU2.
* Will always be more than one fragment.
@@ -801,16 +966,50 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
return rv;
}
/**
* Flag byte to be sent in header
*
* @since 0.9.56
*/
byte getFlags() {
return shouldRequestImmediateAck() ? (byte) 0x01 : 0;
}
/**
* A timer to send an ack-only packet.
*/
private class ACKTimer extends SimpleTimer2.TimedEvent {
/**
* Caller must schedule
*/
public ACKTimer() {
super(_context.simpleTimer2());
}
/**
* Ack soon, based on the current RTT
*
* @since 0.9.56
*/
public void schedule() {
long delta = Math.max(10, Math.min(_rtt/6, ACK_FREQUENCY));
if (_log.shouldDebug())
_log.debug("Sending delayed ack in " + delta + ": " + PeerState2.this);
schedule(delta);
reschedule(delta, true);
}
/**
* Ack almost immediately
*
* @since 0.9.56
*/
public void scheduleImmediate() {
_wantACKSendSince = _context.clock().now();
long delta = Math.min(_rtt/16, 5);
if (_log.shouldDebug())
_log.debug("Sending immediate ack in " + delta + ": " + PeerState2.this);
reschedule(delta, true);
}
/**

View File

@@ -21,7 +21,7 @@ final class SSU2Util {
// features
public static final boolean ENABLE_RELAY = true;
public static final boolean ENABLE_PEER_TEST = true;
public static final boolean ENABLE_PATH_CHALLENGE = false;
public static final boolean ENABLE_PATH_CHALLENGE = true;
// lengths
/** 32 */

View File

@@ -428,7 +428,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
String s = null;
// try to determine if we've been down for 30 days or more
long minDowntime = _context.router().isHidden() ? MIN_DOWNTIME_TO_REKEY_HIDDEN : MIN_DOWNTIME_TO_REKEY;
boolean shouldRekey = _context.getEstimatedDowntime() >= minDowntime;
boolean shouldRekey = !allowLocal() && _context.getEstimatedDowntime() >= minDowntime;
if (!shouldRekey) {
s = ctx.getProperty(PROP_SSU2_SP);
if (s != null) {
@@ -1645,7 +1645,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
/**
* Get the state by SSU2 connection ID
* @since 0.9.56
* @since 0.9.55
*/
PeerState2 getPeerState(long rcvConnID) {
return _peersByConnID.get(Long.valueOf(rcvConnID));
@@ -1671,7 +1671,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
return _peersByIdent.keySet();
}
/**
/**
* Remove and add to peersByRemoteHost map
* @since 0.9.3
*/
@@ -1690,6 +1690,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_log.info("Changed port from " + oldPort + " to " + newPort + " for " + peer);
}
/**
* Remove and add to peersByRemoteHost map
* @since 0.9.56
*/
void changePeerAddress(PeerState2 peer, RemoteHostId newAddress) {
RemoteHostId oldAddress;
synchronized (_addDropLock) {
oldAddress = peer.getRemoteHostId();
if (!oldAddress.equals(newAddress)) {
_peersByRemoteHost.remove(oldAddress);
peer.changeAddress(newAddress);
_peersByRemoteHost.put(newAddress, peer);
}
}
if (_log.shouldInfo() && !oldAddress.equals(newAddress))
_log.info("Changed address from " + oldAddress + " to " + newAddress + " for " + peer);
}
/**
* For IntroductionManager
* @return may be null if not started
@@ -2772,7 +2790,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
options.setProperty(UDPAddress.PROP_CAPACITY, caps);
if (mtu != PeerState.LARGE_MTU && mtu > 0)
options.setProperty(UDPAddress.PROP_MTU, Integer.toString(mtu));
if (_enableSSU2)
if (_enableSSU2 && (mtu >= PeerState2.MIN_MTU || mtu == 0))
addSSU2Options(options);
RouterAddress current = getCurrentAddress(false);
RouterAddress addr = new RouterAddress(STYLE, options, SSU_OUTBOUND_COST);
@@ -2864,7 +2882,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
else if (config == IPV6_NOT_PREFERRED)
cost++;
}
if (_enableSSU2)
if (_enableSSU2 && (mtu >= PeerState2.MIN_MTU || mtu == 0))
addSSU2Options(options);
RouterAddress addr = new RouterAddress(STYLE, options, cost);
@@ -2934,7 +2952,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
opts.setProperty(UDPAddress.PROP_CAPACITY, isIPv6 ? CAP_IPV6 : CAP_IPV4);
if (mtu != PeerState.LARGE_MTU && mtu > 0)
opts.setProperty(UDPAddress.PROP_MTU, Integer.toString(mtu));
if (_enableSSU2)
if (_enableSSU2 && (mtu >= PeerState2.MIN_MTU || mtu == 0))
addSSU2Options(opts);
RouterAddress addr = new RouterAddress(STYLE, opts, SSU_OUTBOUND_COST);
RouterAddress current = getCurrentAddress(isIPv6);

View File

@@ -40,6 +40,7 @@ public class EventLog {
public static final String CLOCK_SHIFT = "clockShift";
public static final String CRASHED = "crashed";
public static final String CRITICAL = "critical";
public static final String DEADLOCK = "deadlock";
public static final String INSTALLED = "installed";
public static final String INSTALL_FAILED = "installFailed";
public static final String NETWORK = "network";