Router: Check for transport compatibility before direct store of RI;

send through tunnel if incompatible
Fix repeated store of RI when IPv6-only
Move connect checker to own class for use by netdb
Log tweaks
This commit is contained in:
zzz
2018-03-08 21:38:39 +00:00
parent 69aadaa46a
commit fac4f6c28f
4 changed files with 360 additions and 324 deletions

View File

@ -29,6 +29,7 @@ import net.i2p.router.OutNetMessage;
import net.i2p.router.ReplyJob;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo;
import net.i2p.router.tunnel.pool.ConnectChecker;
import net.i2p.util.Log;
import net.i2p.util.VersionComparator;
@ -46,6 +47,8 @@ class StoreJob extends JobImpl {
private final long _timeoutMs;
private final long _expiration;
private final PeerSelector _peerSelector;
private final ConnectChecker _connectChecker;
private final int _connectMask;
private final static int PARALLELIZATION = 4; // how many sent at a time
private final static int REDUNDANCY = 4; // we want the data sent to 6 peers
@ -75,6 +78,17 @@ class StoreJob extends JobImpl {
_timeoutMs = timeoutMs;
_expiration = context.clock().now() + timeoutMs;
_peerSelector = facade.getPeerSelector();
if (data.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
_connectChecker = null;
_connectMask = 0;
} else {
_connectChecker = new ConnectChecker(context);
RouterInfo us = context.router().getRouterInfo();
if (us != null)
_connectMask = _connectChecker.getOutboundMask(us);
else
_connectMask = ConnectChecker.ANY_V4;
}
}
public String getName() { return "Kademlia NetDb Store";}
@ -333,7 +347,11 @@ class StoreJob extends JobImpl {
sendStoreThroughClient(msg, peer, expiration);
} else {
getContext().statManager().addRateData("netDb.storeRouterInfoSent", 1);
sendDirect(msg, peer, expiration);
// if we can't connect to peer directly, just send it out an exploratory tunnel
if (_connectChecker.canConnect(_connectMask, peer))
sendDirect(msg, peer, expiration);
else
sendStoreThroughGarlic(msg, peer, expiration);
}
}
@ -347,9 +365,6 @@ class StoreJob extends JobImpl {
msg.setReplyToken(token);
msg.setReplyGateway(getContext().routerHash());
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": send(dbStore) w/ token expected " + token);
_state.addPending(peer.getIdentity().getHash());
SendSuccessJob onReply = new SendSuccessJob(getContext(), peer);
@ -357,7 +372,7 @@ class StoreJob extends JobImpl {
StoreMessageSelector selector = new StoreMessageSelector(getContext(), getJobId(), peer, token, expiration);
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending store directly to " + peer.getIdentity().getHash());
_log.debug(getJobId() + ": sending store directly to " + peer.getIdentity().getHash());
OutNetMessage m = new OutNetMessage(getContext(), msg, expiration, STORE_PRIORITY, peer);
m.setOnFailedReplyJob(onFail);
m.setOnFailedSendJob(onFail);
@ -388,7 +403,7 @@ class StoreJob extends JobImpl {
msg.setReplyGateway(replyTunnel.getPeer(0));
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": send(dbStore) w/ token expected " + token);
_log.debug(getJobId() + ": send store thru expl. tunnel to " + peer.getIdentity().getHash() + " w/ token expected " + token);
_state.addPending(to);
@ -618,8 +633,8 @@ class StoreJob extends JobImpl {
getContext().statManager().addRateData("netDb.ackTime", howLong, howLong);
if ( (_sendThrough != null) && (_msgSize > 0) ) {
if (_log.shouldLog(Log.INFO))
_log.info("sent a " + _msgSize + " byte netDb message through tunnel " + _sendThrough + " after " + howLong);
if (_log.shouldDebug())
_log.debug("sent a " + _msgSize + " byte netDb message through tunnel " + _sendThrough + " after " + howLong);
for (int i = 0; i < _sendThrough.getLength(); i++)
getContext().profileManager().tunnelDataPushed(_sendThrough.getPeer(i), howLong, _msgSize);
_sendThrough.incrementVerifiedBytesTransferred(_msgSize);

View File

@ -1581,8 +1581,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private boolean locked_needsRebuild() {
if (_needsRebuild) return true; // simple enough
if (_context.router().isHidden()) return false;
RouterAddress addr = getCurrentAddress(false);
if (introducersRequired()) {
boolean v6Only = getIPv6Config() == IPV6_ONLY;
RouterAddress addr = getCurrentAddress(v6Only);
if (!v6Only && introducersRequired()) {
UDPAddress ua = new UDPAddress(addr);
long now = _context.clock().now();
int valid = 0;
@ -2139,7 +2140,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
} else {
if (!introducersRequired()) {
RouterAddress cur = getCurrentExternalAddress(false);
boolean v6Only = getIPv6Config() == IPV6_ONLY;
RouterAddress cur = getCurrentExternalAddress(v6Only);
if (cur != null)
host = cur.getHost();
}

View File

@ -0,0 +1,328 @@
package net.i2p.router.tunnel.pool;
import java.util.Collection;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.Router;
import net.i2p.router.CommSystemFacade.Status;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.TransportManager;
import net.i2p.util.Log;
/**
* Tools to check transport compatibility.
*
* @since 0.9.34
*/
public class ConnectChecker {
protected final RouterContext ctx;
protected final Log log;
private static final int NTCP_V4 = 0x01;
private static final int SSU_V4 = 0x02;
public static final int ANY_V4 = NTCP_V4 | SSU_V4;
private static final int NTCP_V6 = 0x04;
private static final int SSU_V6 = 0x08;
private static final int ANY_V6 = NTCP_V6 | SSU_V6;
public ConnectChecker(RouterContext context) {
ctx = context;
log = ctx.logManager().getLog(getClass());
}
/**
* Is NTCP disabled?
* @since 0.9.34
*/
protected boolean isNTCPDisabled() {
return !TransportManager.isNTCPEnabled(ctx);
}
/**
* Is SSU disabled?
* @since 0.9.34
*/
protected boolean isSSUDisabled() {
return !ctx.getBooleanPropertyDefaultTrue(TransportManager.PROP_ENABLE_UDP);
}
/**
* Can "from" connect to "to" based on published addresses?
*
* This is intended for tunnel candidates, where we already have
* the RI. Will not force RI lookups.
* Either from or to may be us.
*
* This is best effort, as we can't know for sure.
* Published addresses or introducers may have changed.
* Even if a can't connect to b, they may already be connected
* as b connected to a.
*
* @return true if we don't have either RI
* @since 0.9.34
*/
public boolean canConnect(Hash from, Hash to) {
Hash us = ctx.routerHash();
if (us == null)
return true;
boolean usf = from.equals(us);
if (usf && ctx.commSystem().isEstablished(to))
return true;
boolean ust = to.equals(us);
if (ust && ctx.commSystem().isEstablished(from))
return true;
RouterInfo rt = ctx.netDb().lookupRouterInfoLocally(to);
if (rt == null)
return true;
RouterInfo rf = ctx.netDb().lookupRouterInfoLocally(from);
if (rf == null)
return true;
int ct;
if (ust) {
// to us
ct = getInboundMask(rt);
} else {
Collection<RouterAddress> at = rt.getAddresses();
// assume nothing if hidden
if (at.isEmpty())
return false;
ct = getConnectMask(at);
}
int cf;
if (usf) {
// from us
cf = getOutboundMask(rf);
} else {
Collection<RouterAddress> a = rf.getAddresses();
if (a.isEmpty()) {
// assume IPv4 if hidden
cf = NTCP_V4 | SSU_V4;
} else {
cf = getConnectMask(a);
}
}
boolean rv = (ct & cf) != 0;
if (!rv && log.shouldWarn()) {
log.warn("Cannot connect: " +
(usf ? "us" : from.toString()) + " with mask " + cf + "\nto " +
(ust ? "us" : to.toString()) + " with mask " + ct);
}
return rv;
}
/**
* Can we connect to "to" based on published addresses?
*
* This is intended for tunnel candidates, where we already have
* the RI. Will not force RI lookups.
*
* This is best effort, as we can't know for sure.
* Does not check isEstablished(); do that first.
*
* @since 0.9.34
*/
public boolean canConnect(int ourMask, RouterInfo to) {
Collection<RouterAddress> ra = to.getAddresses();
// assume nothing if hidden
if (ra.isEmpty())
return false;
int ct = getConnectMask(ra);
boolean rv = (ourMask & ct) != 0;
//if (!rv && log.shouldWarn())
// log.warn("Cannot connect: us with mask " + ourMask + " to " + to + " with mask " + ct);
return rv;
}
/**
* Can "from" connect to us based on published addresses?
*
* This is intended for tunnel candidates, where we already have
* the RI. Will not force RI lookups.
*
* This is best effort, as we can't know for sure.
* Does not check isEstablished(); do that first.
*
* @since 0.9.34
*/
public boolean canConnect(RouterInfo from, int ourMask) {
if (ourMask == 0)
return false;
Collection<RouterAddress> ra = from.getAddresses();
int cf;
// assume v4 if hidden
if (ra.isEmpty())
cf = NTCP_V4 | SSU_V4;
else
cf = getConnectMask(ra);
boolean rv = (cf & ourMask) != 0;
//if (!rv && log.shouldWarn())
// log.warn("Cannot connect: " + from + " with mask " + cf + " to us with mask " + ourMask);
return rv;
}
/**
* Our inbound mask.
* For most cases, we use what we published, i.e. getConnectMask()
*
* @return bitmask for accepting connections
* @since 0.9.34
*/
public int getInboundMask(RouterInfo us) {
// to us
int ct = 0;
Status status = ctx.commSystem().getStatus();
switch (status) {
case OK:
case IPV4_UNKNOWN_IPV6_OK:
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_SNAT_IPV6_OK:
case IPV4_SNAT_IPV6_UNKNOWN:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
case IPV4_UNKNOWN_IPV6_FIREWALLED:
case IPV4_OK_IPV6_FIREWALLED:
case DIFFERENT:
case REJECT_UNSOLICITED:
// use what we published
Collection<RouterAddress> at = us.getAddresses();
if (at.isEmpty())
return 0;
ct = getConnectMask(at);
break;
case IPV4_DISABLED_IPV6_OK:
case IPV4_DISABLED_IPV6_UNKNOWN:
// maybe should return zero for this one?
case IPV4_DISABLED_IPV6_FIREWALLED:
// TODO look at force-firewalled settings per-transport
if (!isNTCPDisabled())
ct |= NTCP_V6;
if (!isSSUDisabled())
ct |= SSU_V6;
break;
case IPV4_OK_IPV6_UNKNOWN:
case DISCONNECTED:
case HOSED:
case UNKNOWN:
default:
if (!isNTCPDisabled())
ct |= NTCP_V4;
if (!isSSUDisabled())
ct |= SSU_V4;
break;
}
return ct;
}
/**
* Our outbound mask.
* For most cases, we use our comm system status.
*
* @return bitmask for initiating connections
* @since 0.9.34
*/
public int getOutboundMask(RouterInfo us) {
// from us
int cf = 0;
Status status = ctx.commSystem().getStatus();
switch (status) {
case OK:
// use what we published, as the OK state doesn't tell us about IPv6
// Addresses.isConnectedIPv6() is too slow
Collection<RouterAddress> a = us.getAddresses();
if (a.isEmpty()) {
// we are hidden
// TODO ipv6
if (!isNTCPDisabled())
cf |= NTCP_V4;
if (!isSSUDisabled())
cf |= SSU_V4;
} else {
cf = getConnectMask(a);
}
break;
case IPV4_OK_IPV6_FIREWALLED:
case IPV4_UNKNOWN_IPV6_OK:
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_SNAT_IPV6_OK:
case IPV4_UNKNOWN_IPV6_FIREWALLED:
if (!isNTCPDisabled())
cf |= NTCP_V4 | NTCP_V6;
if (!isSSUDisabled())
cf |= SSU_V4 | SSU_V6;
break;
case IPV4_DISABLED_IPV6_OK:
case IPV4_DISABLED_IPV6_UNKNOWN:
case IPV4_DISABLED_IPV6_FIREWALLED:
if (!isNTCPDisabled())
cf |= NTCP_V6;
if (!isSSUDisabled())
cf |= SSU_V6;
break;
case DIFFERENT:
case IPV4_SNAT_IPV6_UNKNOWN:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
case REJECT_UNSOLICITED:
case IPV4_OK_IPV6_UNKNOWN:
case DISCONNECTED:
case HOSED:
case UNKNOWN:
default:
if (!isNTCPDisabled())
cf |= NTCP_V4;
if (!isSSUDisabled())
cf |= SSU_V4;
break;
}
return cf;
}
/** prevent object churn */
private static final String IHOST[] = { "ihost0", "ihost1", "ihost2" };
/**
* @param addrs non-empty, set your own default if empty
* @return bitmask of v4/v6 NTCP/SSU
* @since 0.9.34
*/
private static int getConnectMask(Collection<RouterAddress> addrs) {
int rv = 0;
for (RouterAddress ra : addrs) {
String style = ra.getTransportStyle();
String host = ra.getHost();
if ("NTCP".equals(style)) {
if (host != null) {
if (host.contains(":"))
rv |= NTCP_V6;
else
rv |= NTCP_V4;
}
} else if ("SSU".equals(style)) {
if (host == null) {
for (int i = 0; i < 2; i++) {
String ihost = ra.getOption(IHOST[i]);
if (ihost == null)
break;
if (ihost.contains(":"))
rv |= SSU_V6;
else
rv |= SSU_V4;
}
} else if (host.contains(":")) {
rv |= SSU_V6;
} else {
rv |= SSU_V4;
}
}
}
return rv;
}
}

View File

@ -12,20 +12,16 @@ import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.Router;
import net.i2p.router.CommSystemFacade.Status;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelPoolSettings;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.TransportManager;
import net.i2p.router.transport.TransportUtil;
import net.i2p.router.util.HashDistance;
import net.i2p.util.Log;
@ -35,23 +31,11 @@ import net.i2p.util.VersionComparator;
* Coordinate the selection of peers to go into a tunnel for one particular
* pool.
*
* Todo: there's nothing non-static in here
*/
public abstract class TunnelPeerSelector {
protected final RouterContext ctx;
protected final Log log;
private static final int NTCP_V4 = 0x01;
private static final int SSU_V4 = 0x02;
private static final int ANY_V4 = NTCP_V4 | SSU_V4;
private static final int NTCP_V6 = 0x04;
private static final int SSU_V6 = 0x08;
private static final int ANY_V6 = NTCP_V6 | SSU_V6;
public abstract class TunnelPeerSelector extends ConnectChecker {
protected TunnelPeerSelector(RouterContext context) {
ctx = context;
log = ctx.logManager().getLog(getClass());
super(context);
}
/**
@ -120,7 +104,7 @@ public abstract class TunnelPeerSelector {
if (opts != null) {
String peers = opts.getProperty("explicitPeers");
if (peers == null)
peers = I2PAppContext.getGlobalContext().getProperty("explicitPeers");
peers = ctx.getProperty("explicitPeers");
if (peers != null)
return true;
}
@ -139,7 +123,7 @@ public abstract class TunnelPeerSelector {
peers = opts.getProperty("explicitPeers");
if (peers == null)
peers = I2PAppContext.getGlobalContext().getProperty("explicitPeers");
peers = ctx.getProperty("explicitPeers");
List<Hash> rv = new ArrayList<Hash>();
StringTokenizer tok = new StringTokenizer(peers, ",");
@ -350,22 +334,6 @@ public abstract class TunnelPeerSelector {
return TransportUtil.getIPv6Config(ctx, "SSU") == TransportUtil.IPv6Config.IPV6_ONLY;
}
/**
* Is NTCP disabled?
* @since 0.9.34
*/
protected boolean isNTCPDisabled() {
return !TransportManager.isNTCPEnabled(ctx);
}
/**
* Is SSU disabled?
* @since 0.9.34
*/
protected boolean isSSUDisabled() {
return !ctx.getBooleanPropertyDefaultTrue(TransportManager.PROP_ENABLE_UDP);
}
/**
* Should we allow as OBEP?
* This just checks for IPv4 support.
@ -759,281 +727,4 @@ public abstract class TunnelPeerSelector {
}
return rv;
}
/**
* Can "from" connect to "to" based on published addresses?
*
* This is intended for tunnel candidates, where we already have
* the RI. Will not force RI lookups.
* Either from or to may be us.
*
* This is best effort, as we can't know for sure.
* Published addresses or introducers may have changed.
* Even if a can't connect to b, they may already be connected
* as b connected to a.
*
* @return true if we don't have either RI
* @since 0.9.34
*/
private boolean canConnect(Hash from, Hash to) {
Hash us = ctx.routerHash();
if (us == null)
return true;
boolean usf = from.equals(us);
if (usf && ctx.commSystem().isEstablished(to))
return true;
boolean ust = to.equals(us);
if (ust && ctx.commSystem().isEstablished(from))
return true;
RouterInfo rt = ctx.netDb().lookupRouterInfoLocally(to);
if (rt == null)
return true;
RouterInfo rf = ctx.netDb().lookupRouterInfoLocally(from);
if (rf == null)
return true;
int ct;
if (ust) {
// to us
ct = getInboundMask(rt);
} else {
Collection<RouterAddress> at = rt.getAddresses();
// assume nothing if hidden
if (at.isEmpty())
return false;
ct = getConnectMask(at);
}
int cf;
if (usf) {
// from us
cf = getOutboundMask(rf);
} else {
Collection<RouterAddress> a = rf.getAddresses();
if (a.isEmpty()) {
// assume IPv4 if hidden
cf = NTCP_V4 | SSU_V4;
} else {
cf = getConnectMask(a);
}
}
boolean rv = (ct & cf) != 0;
if (!rv && log.shouldWarn()) {
log.warn("Cannot connect: " +
(usf ? "us" : from.toString()) + " with mask " + cf + "\nto " +
(ust ? "us" : to.toString()) + " with mask " + ct);
}
return rv;
}
/**
* Can we connect to "to" based on published addresses?
*
* This is intended for tunnel candidates, where we already have
* the RI. Will not force RI lookups.
*
* This is best effort, as we can't know for sure.
* Does not check isEstablished(); do that first.
*
* @since 0.9.34
*/
private boolean canConnect(int ourMask, RouterInfo to) {
Collection<RouterAddress> ra = to.getAddresses();
// assume nothing if hidden
if (ra.isEmpty())
return false;
int ct = getConnectMask(ra);
boolean rv = (ourMask & ct) != 0;
//if (!rv && log.shouldWarn())
// log.warn("Cannot connect: us with mask " + ourMask + " to " + to + " with mask " + ct);
return rv;
}
/**
* Can "from" connect to us based on published addresses?
*
* This is intended for tunnel candidates, where we already have
* the RI. Will not force RI lookups.
*
* This is best effort, as we can't know for sure.
* Does not check isEstablished(); do that first.
*
* @since 0.9.34
*/
private boolean canConnect(RouterInfo from, int ourMask) {
if (ourMask == 0)
return false;
Collection<RouterAddress> ra = from.getAddresses();
int cf;
// assume v4 if hidden
if (ra.isEmpty())
cf = NTCP_V4 | SSU_V4;
else
cf = getConnectMask(ra);
boolean rv = (cf & ourMask) != 0;
//if (!rv && log.shouldWarn())
// log.warn("Cannot connect: " + from + " with mask " + cf + " to us with mask " + ourMask);
return rv;
}
/**
* Our inbound mask.
* For most cases, we use what we published, i.e. getConnectMask()
*
* @return bitmask for accepting connections
* @since 0.9.34
*/
private int getInboundMask(RouterInfo us) {
// to us
int ct = 0;
Status status = ctx.commSystem().getStatus();
switch (status) {
case OK:
case IPV4_UNKNOWN_IPV6_OK:
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_SNAT_IPV6_OK:
case IPV4_SNAT_IPV6_UNKNOWN:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
case IPV4_UNKNOWN_IPV6_FIREWALLED:
case IPV4_OK_IPV6_FIREWALLED:
case DIFFERENT:
case REJECT_UNSOLICITED:
// use what we published
Collection<RouterAddress> at = us.getAddresses();
if (at.isEmpty())
return 0;
ct = getConnectMask(at);
break;
case IPV4_DISABLED_IPV6_OK:
case IPV4_DISABLED_IPV6_UNKNOWN:
// maybe should return zero for this one?
case IPV4_DISABLED_IPV6_FIREWALLED:
// TODO look at force-firewalled settings per-transport
if (!isNTCPDisabled())
ct |= NTCP_V6;
if (!isSSUDisabled())
ct |= SSU_V6;
break;
case IPV4_OK_IPV6_UNKNOWN:
case DISCONNECTED:
case HOSED:
case UNKNOWN:
default:
if (!isNTCPDisabled())
ct |= NTCP_V4;
if (!isSSUDisabled())
ct |= SSU_V4;
break;
}
return ct;
}
/**
* Our outbound mask.
* For most cases, we use our comm system status.
*
* @return bitmask for initiating connections
* @since 0.9.34
*/
private int getOutboundMask(RouterInfo us) {
// from us
int cf = 0;
Status status = ctx.commSystem().getStatus();
switch (status) {
case OK:
// use what we published, as the OK state doesn't tell us about IPv6
// Addresses.isConnectedIPv6() is too slow
Collection<RouterAddress> a = us.getAddresses();
if (a.isEmpty()) {
// we are hidden
// TODO ipv6
if (!isNTCPDisabled())
cf |= NTCP_V4;
if (!isSSUDisabled())
cf |= SSU_V4;
} else {
cf = getConnectMask(a);
}
break;
case IPV4_OK_IPV6_FIREWALLED:
case IPV4_UNKNOWN_IPV6_OK:
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_SNAT_IPV6_OK:
case IPV4_UNKNOWN_IPV6_FIREWALLED:
if (!isNTCPDisabled())
cf |= NTCP_V4 | NTCP_V6;
if (!isSSUDisabled())
cf |= SSU_V4 | SSU_V6;
break;
case IPV4_DISABLED_IPV6_OK:
case IPV4_DISABLED_IPV6_UNKNOWN:
case IPV4_DISABLED_IPV6_FIREWALLED:
if (!isNTCPDisabled())
cf |= NTCP_V6;
if (!isSSUDisabled())
cf |= SSU_V6;
break;
case DIFFERENT:
case IPV4_SNAT_IPV6_UNKNOWN:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
case REJECT_UNSOLICITED:
case IPV4_OK_IPV6_UNKNOWN:
case DISCONNECTED:
case HOSED:
case UNKNOWN:
default:
if (!isNTCPDisabled())
cf |= NTCP_V4;
if (!isSSUDisabled())
cf |= SSU_V4;
break;
}
return cf;
}
/** prevent object churn */
private static final String IHOST[] = { "ihost0", "ihost1", "ihost2" };
/**
* @param addrs non-empty, set your own default if empty
* @return bitmask of v4/v6 NTCP/SSU
* @since 0.9.34
*/
private static int getConnectMask(Collection<RouterAddress> addrs) {
int rv = 0;
for (RouterAddress ra : addrs) {
String style = ra.getTransportStyle();
String host = ra.getHost();
if ("NTCP".equals(style)) {
if (host != null) {
if (host.contains(":"))
rv |= NTCP_V6;
else
rv |= NTCP_V4;
}
} else if ("SSU".equals(style)) {
if (host == null) {
for (int i = 0; i < 2; i++) {
String ihost = ra.getOption(IHOST[i]);
if (ihost == null)
break;
if (ihost.contains(":"))
rv |= SSU_V6;
else
rv |= SSU_V4;
}
} else if (host.contains(":")) {
rv |= SSU_V6;
} else {
rv |= SSU_V4;
}
}
}
return rv;
}
}