Package freenet.io

Source Code of freenet.io.AddressTracker

/* Copyright 2007 Freenet Project Inc.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package freenet.io;

import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;

import freenet.io.comm.Peer;
import freenet.l10n.NodeL10n;
import freenet.node.FSParseException;
import freenet.node.ProgramDirectory;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.io.FileUtil;

/**
* Track packet traffic to/from specific peers and IP addresses, in order to
* determine whether we are open to the internet.
*
* Normally there would be one tracker per port i.e. per UdpSocketHandler.
* @author toad
*/
public class AddressTracker {

  /** PeerAddressTrackerItem's by Peer */
  private final HashMap<Peer, PeerAddressTrackerItem> peerTrackers;

  /** InetAddressAddressTrackerItem's by InetAddress */
  private final HashMap<InetAddress, InetAddressAddressTrackerItem> ipTrackers;

  /** Maximum number of Item's of either type */
  private int MAX_ITEMS = DEFAULT_MAX_ITEMS;
 
  static final int DEFAULT_MAX_ITEMS = 1000;
  static final int SEED_MAX_ITEMS = 10000;

  private long timeDefinitelyNoPacketsReceivedIP;
  private long timeDefinitelyNoPacketsSentIP;

  private long timeDefinitelyNoPacketsReceivedPeer;
  private long timeDefinitelyNoPacketsSentPeer;

  private long brokenTime;

  public static AddressTracker create(long lastBootID, ProgramDirectory runDir, int port) {
    File data = runDir.file("packets-"+port+".dat");
    File dataBak = runDir.file("packets-"+port+".bak");
    dataBak.delete();
    FileInputStream fis = null;
    try {
      fis = new FileInputStream(data);
      BufferedInputStream bis = new BufferedInputStream(fis);
      InputStreamReader ir = new InputStreamReader(bis, "UTF-8");
      BufferedReader br = new BufferedReader(ir);
      SimpleFieldSet fs = new SimpleFieldSet(br, false, true);
      return new AddressTracker(fs, lastBootID);
    } catch (IOException e) {
      // Fall through
    } catch (FSParseException e) {
      Logger.warning(AddressTracker.class, "Failed to load from disk for port "+port+": "+e, e);
      // Fall through
    } finally {
      if(fis != null)
        try {
          fis.close();
        } catch (IOException e) {
          // Ignore
        }
    }
    return new AddressTracker();
  }

  private AddressTracker() {
    timeDefinitelyNoPacketsReceivedIP = System.currentTimeMillis();
    timeDefinitelyNoPacketsSentIP = System.currentTimeMillis();
    timeDefinitelyNoPacketsReceivedPeer = System.currentTimeMillis();
    timeDefinitelyNoPacketsSentPeer = System.currentTimeMillis();
    peerTrackers = new HashMap<Peer, PeerAddressTrackerItem>();
    ipTrackers = new HashMap<InetAddress, InetAddressAddressTrackerItem>();
  }

  private AddressTracker(SimpleFieldSet fs, long lastBootID) throws FSParseException {
    int version = fs.getInt("Version");
    if(version != 2)
      throw new FSParseException("Unknown Version "+version);
    long savedBootID = fs.getLong("BootID");
    if(savedBootID != lastBootID) throw new FSParseException("Unable to load address tracker table, assuming an unclean shutdown: Last ID was " +
        lastBootID+" but stored "+savedBootID);
    // Sadly we don't know whether there were packets arriving during the gap,
    // and some insecure firewalls will use incoming packets to keep tunnels open
    //timeDefinitelyNoPacketsReceived = fs.getLong("TimeDefinitelyNoPacketsReceived");
    timeDefinitelyNoPacketsReceivedPeer = System.currentTimeMillis();
    timeDefinitelyNoPacketsReceivedIP = System.currentTimeMillis();
    timeDefinitelyNoPacketsSentPeer = fs.getLong("TimeDefinitelyNoPacketsSentPeer");
    timeDefinitelyNoPacketsSentIP = fs.getLong("TimeDefinitelyNoPacketsSentIP");
    peerTrackers = new HashMap<Peer, PeerAddressTrackerItem>();
    SimpleFieldSet peers = fs.subset("Peers");
    if(peers != null) {
    Iterator<String> i = peers.directSubsetNameIterator();
    if(i != null) {
    while(i.hasNext()) {
      SimpleFieldSet peer = peers.subset(i.next());
      PeerAddressTrackerItem item = new PeerAddressTrackerItem(peer);
      peerTrackers.put(item.peer, item);
    }
    }
    }
    ipTrackers = new HashMap<InetAddress, InetAddressAddressTrackerItem>();
    SimpleFieldSet ips = fs.subset("IPs");
    if(ips != null) {
    Iterator<String> i = ips.directSubsetNameIterator();
    if(i != null) {
    while(i.hasNext()) {
      SimpleFieldSet peer = ips.subset(i.next());
      InetAddressAddressTrackerItem item = new InetAddressAddressTrackerItem(peer);
      ipTrackers.put(item.addr, item);
    }
    }
    }
  }

  public void sentPacketTo(Peer peer) {
    packetTo(peer, true);
  }

  public void receivedPacketFrom(Peer peer) {
    packetTo(peer, false);
  }

  private void packetTo(Peer peer, boolean sent) {
    Peer peer2 = peer.dropHostName();
    if(peer2 == null) {
      Logger.error(this, "Impossible: No host name in AddressTracker.packetTo for "+peer);
      return;
    }
    peer = peer2;

    InetAddress ip = peer.getAddress();
    long now = System.currentTimeMillis();
    synchronized(this) {
      PeerAddressTrackerItem peerItem = peerTrackers.get(peer);
      if(peerItem == null) {
        peerItem = new PeerAddressTrackerItem(timeDefinitelyNoPacketsReceivedPeer, timeDefinitelyNoPacketsSentPeer, peer);
        if(peerTrackers.size() > MAX_ITEMS) {
          Logger.error(this, "Clearing peer trackers on "+this);
          peerTrackers.clear();
          ipTrackers.clear();
          timeDefinitelyNoPacketsReceivedPeer = now;
          timeDefinitelyNoPacketsSentPeer = now;
        }
        peerTrackers.put(peer, peerItem);
      }
      if(sent)
        peerItem.sentPacket(now);
      else
        peerItem.receivedPacket(now);
      InetAddressAddressTrackerItem ipItem = ipTrackers.get(ip);
      if(ipItem == null) {
        ipItem = new InetAddressAddressTrackerItem(timeDefinitelyNoPacketsReceivedIP, timeDefinitelyNoPacketsSentIP, ip);
        if(ipTrackers.size() > MAX_ITEMS) {
          Logger.error(this, "Clearing IP trackers on "+this);
          peerTrackers.clear();
          ipTrackers.clear();
          timeDefinitelyNoPacketsReceivedIP = now;
          timeDefinitelyNoPacketsSentIP = now;
        }
        ipTrackers.put(ip, ipItem);
      }
      if(sent)
        ipItem.sentPacket(now);
      else
        ipItem.receivedPacket(now);
    }
  }

  public synchronized void startReceive(long now) {
    timeDefinitelyNoPacketsReceivedIP = now;
    timeDefinitelyNoPacketsReceivedPeer = now;
  }

  public synchronized void startSend(long now) {
    timeDefinitelyNoPacketsSentIP = now;
    timeDefinitelyNoPacketsSentPeer = now;
  }

  public synchronized PeerAddressTrackerItem[] getPeerAddressTrackerItems() {
    PeerAddressTrackerItem[] items = new PeerAddressTrackerItem[peerTrackers.size()];
    return peerTrackers.values().toArray(items);
  }

  public synchronized InetAddressAddressTrackerItem[] getInetAddressTrackerItems() {
    InetAddressAddressTrackerItem[] items = new InetAddressAddressTrackerItem[ipTrackers.size()];
    return ipTrackers.values().toArray(items);
  }

  public enum Status {
    // Note: Order is important! We compare by ordinals in various places.
    // FIXME switch to using member methods.
    DEFINITELY_NATED,
    MAYBE_NATED,
    DONT_KNOW,
    MAYBE_PORT_FORWARDED,
    DEFINITELY_PORT_FORWARDED
  }
 
  /** If the minimum gap is at least this, we might be port forwarded.
   * RFC 4787 requires at least 2 minutes, but many NATs have shorter timeouts. */
  public final static long MAYBE_TUNNEL_LENGTH = MINUTES.toMillis(5) + SECONDS.toMillis(1);
  /** If the minimum gap is at least this, we are almost certainly port forwarded.
   * Some stateful firewalls do at least 30 minutes. Hopefully the below is
   * sufficiently over the top! */
  public final static long DEFINITELY_TUNNEL_LENGTH = HOURS.toMillis(12) + MINUTES.toMillis(1);
  /** Time after which we ignore evidence that we are port forwarded */
  public static final long HORIZON = HOURS.toMillis(24);

  public long getLongestSendReceiveGap() {
    return getLongestSendReceiveGap(HORIZON);
  }

  /**
   * Find the longest send/known-no-packets-sent ... receive gap.
   * It is highly unlikely that we are behind a NAT or symmetric
   * firewall with a timeout less than the returned length.
   */
  public long getLongestSendReceiveGap(long horizon) {
    long longestGap = -1;
    long now = System.currentTimeMillis();
    PeerAddressTrackerItem[] items = getPeerAddressTrackerItems();
    for(PeerAddressTrackerItem item: items) {
      if(item.packetsReceived() <= 0) continue;
      if(!item.peer.isRealInternetAddress(false, false, false)) continue;
      longestGap = Math.max(longestGap, item.longestGap(horizon, now));
    }
    return longestGap;

  }

  public Status getPortForwardStatus() {
    long minGap = getLongestSendReceiveGap(HORIZON);

    if(minGap > DEFINITELY_TUNNEL_LENGTH)
      return Status.DEFINITELY_PORT_FORWARDED;
    if(minGap > MAYBE_TUNNEL_LENGTH)
      return Status.MAYBE_PORT_FORWARDED;
    // Only take isBroken into account if we're not sure.
    // Somebody could be playing with us by sending bogus FNPSentPackets...
    synchronized(this) {
      if(isBroken()) return Status.DEFINITELY_NATED;
      if(minGap == 0 && timePresumeGuilty > 0 && System.currentTimeMillis() > timePresumeGuilty)
        return Status.MAYBE_NATED;
    }
    return Status.DONT_KNOW;
  }

  private boolean isBroken() {
    return System.currentTimeMillis() - brokenTime < HORIZON;
  }

  public static String statusString(Status status) {
    return NodeL10n.getBase().getString("ConnectivityToadlet.status."+status);
  }

  /** Persist the table to disk */
  public void storeData(long bootID, ProgramDirectory runDir, int port) {
    // Don't write to disk if we know we're NATed anyway!
    if(isBroken()) return;
    File data = runDir.file("packets-"+port+".dat");
    File dataBak = runDir.file("packets-"+port+".bak");
    dataBak.delete();
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(dataBak);
      BufferedOutputStream bos = new BufferedOutputStream(fos);
      OutputStreamWriter osw = new OutputStreamWriter(bos, "UTF-8");
      BufferedWriter bw = new BufferedWriter(osw);
      SimpleFieldSet fs = getFieldset(bootID);
      fs.writeTo(bw);
      bw.flush();
      bw.close();
      fos = null;
      FileUtil.renameTo(dataBak, data);
    } catch (IOException e) {
      Logger.error(this, "Cannot store packet tracker to disk");
      return;
    } finally {
      if(fos != null)
        try {
          fos.close();
        } catch (IOException e) {
          // Ignore
        }
    }
  }

  private synchronized SimpleFieldSet getFieldset(long bootID) {
    SimpleFieldSet sfs = new SimpleFieldSet(true);
    sfs.put("Version", 2);
    sfs.put("BootID", bootID);
    sfs.put("TimeDefinitelyNoPacketsReceivedPeer", timeDefinitelyNoPacketsReceivedPeer);
    sfs.put("TimeDefinitelyNoPacketsReceivedIP", timeDefinitelyNoPacketsReceivedIP);
    sfs.put("TimeDefinitelyNoPacketsSentPeer", timeDefinitelyNoPacketsSentPeer);
    sfs.put("TimeDefinitelyNoPacketsSentIP", timeDefinitelyNoPacketsSentIP);
    PeerAddressTrackerItem[] peerItems = getPeerAddressTrackerItems();
    SimpleFieldSet items = new SimpleFieldSet(true);
    if(peerItems.length > 0) {
      for(int i = 0; i < peerItems.length; i++)
        items.put(Integer.toString(i), peerItems[i].toFieldSet());
      sfs.put("Peers", items);
    }
    InetAddressAddressTrackerItem[] inetItems = getInetAddressTrackerItems();
    items = new SimpleFieldSet(true);
    if(inetItems.length > 0) {
        for(int i = 0; i < inetItems.length; i++)
      items.put(Integer.toString(i), inetItems[i].toFieldSet());
        sfs.put("IPs", items);
    }
    return sfs;
  }

  /** Called when something changes at a higher level suggesting that the status may be wrong */
  public void rescan() {
    // Do nothing for now, as we don't maintain any final state yet.
  }

  public synchronized void setBroken() {
    brokenTime = System.currentTimeMillis();
  }

  private long timePresumeGuilty = -1;

  public synchronized void setPresumedGuiltyAt(long l) {
    if(timePresumeGuilty <= 0)
      timePresumeGuilty = l;
  }

  public synchronized void setPresumedInnocent() {
    timePresumeGuilty = -1;
  }

  public synchronized void setHugeTracker() {
    MAX_ITEMS = SEED_MAX_ITEMS;
  }
}
TOP

Related Classes of freenet.io.AddressTracker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.