diff --git a/core/java/src/net/i2p/client/naming/AddressDB.java b/core/java/src/net/i2p/client/naming/AddressDB.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ad38c3e22706eb8aa540d2804f9111d3b8c0260
--- /dev/null
+++ b/core/java/src/net/i2p/client/naming/AddressDB.java
@@ -0,0 +1,59 @@
+package net.i2p.client.naming;
+
+import java.lang.reflect.Constructor;
+import java.util.Collection;
+
+import net.i2p.I2PAppContext;
+import net.i2p.util.Log;
+import net.i2p.data.Address;
+
+public abstract class AddressDB {
+    
+    private final static Log _log = new Log(NamingService.class);
+    protected I2PAppContext _context;
+    
+    /** what classname should be used as the address db impl? */
+    public static final String PROP_IMPL = "i2p.addressdb.impl";
+    private static final String DEFAULT_IMPL = "net.i2p.client.naming.FilesystemAddressDB";
+    
+    /** 
+     * The address db should only be constructed and accessed through the 
+     * application context.  This constructor should only be used by the 
+     * appropriate application context itself.
+     *
+     */
+    protected AddressDB(I2PAppContext context) {
+        _context = context;
+    }
+    
+    private AddressDB() { // nop
+    }
+    
+    /**
+     * Get an address db instance. This method ensures that there
+     * will be only one address db instance (singleton) as well as
+     * choose the implementation from the "i2p.addressdb.impl" system
+     * property.
+     */
+    public static final synchronized AddressDB createInstance(I2PAppContext context) {
+        AddressDB instance = null;
+        String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
+        try {
+            Class cls = Class.forName(impl);
+            Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
+            instance = (AddressDB)con.newInstance(new Object[] { context });
+        } catch (Exception ex) {
+            _log.error("Cannot load address db implementation", ex);
+            instance = new DummyAddressDB(context); // fallback
+        }
+        return instance;
+    }
+    
+    public abstract Address get(String hostname);
+    public abstract Address put(Address address);
+    public abstract Address remove(String hostname);
+    public abstract Address remove(Address address);
+    public abstract boolean contains(Address address);
+    public abstract boolean contains(String hostname);
+    public abstract Collection hostnames();
+}
diff --git a/core/java/src/net/i2p/client/naming/AddressDBNamingService.java b/core/java/src/net/i2p/client/naming/AddressDBNamingService.java
new file mode 100644
index 0000000000000000000000000000000000000000..04abba456fc05b32a283f5f7c19e687c0d5a9bd0
--- /dev/null
+++ b/core/java/src/net/i2p/client/naming/AddressDBNamingService.java
@@ -0,0 +1,42 @@
+package net.i2p.client.naming;
+
+import java.util.Iterator;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.Destination;
+import net.i2p.data.Address;
+
+public class AddressDBNamingService extends NamingService {
+    
+    private AddressDB _addressdb;
+    
+    public AddressDBNamingService(I2PAppContext context) {
+        super(context);
+        _addressdb = AddressDB.createInstance(context);
+    }
+    
+    private AddressDBNamingService() {
+        super(null);
+    }
+
+    public Destination lookup(String hostname) {
+        Address addr = _addressdb.get(hostname);
+        if (addr != null) {
+            return addr.getDestination();
+        } else {
+            // If we can't find hostname in the addressdb, assume it's a key.
+            return lookupBase64(hostname);
+        }
+    }
+
+    public String reverseLookup(Destination dest) {
+        Iterator iter = _addressdb.hostnames().iterator();
+        while (iter.hasNext()) {
+            Address addr = _addressdb.get((String)iter.next());
+            if (addr != null && addr.getDestination().equals(dest)) {
+                return addr.getHostname();
+            }
+        }
+        return null;        
+    }
+}
diff --git a/core/java/src/net/i2p/client/naming/DummyAddressDB.java b/core/java/src/net/i2p/client/naming/DummyAddressDB.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d151b587afe18806d81836474c0bae715097839
--- /dev/null
+++ b/core/java/src/net/i2p/client/naming/DummyAddressDB.java
@@ -0,0 +1,42 @@
+package net.i2p.client.naming;
+
+import java.util.Collection;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.Address;
+
+public class DummyAddressDB extends AddressDB {
+
+    public DummyAddressDB(I2PAppContext context) {
+        super(context);
+    }
+
+    public Address get(String hostname) {
+        return null;
+    }
+
+    public Address put(Address address) {
+        return null;
+    }
+
+    public Address remove(String hostname) {
+        return null;
+    }
+
+    public Address remove(Address address) {
+        return null;
+    }
+
+    public boolean contains(Address address) {
+        return false;
+    }
+
+    public boolean contains(String hostname) {
+        return false;
+    }
+    
+    public Collection hostnames() {
+        return null;
+    }
+
+}
diff --git a/core/java/src/net/i2p/client/naming/FilesystemAddressDB.java b/core/java/src/net/i2p/client/naming/FilesystemAddressDB.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a2e37e2799d267fe7dc3a385812a27e669fa663
--- /dev/null
+++ b/core/java/src/net/i2p/client/naming/FilesystemAddressDB.java
@@ -0,0 +1,118 @@
+package net.i2p.client.naming;
+
+import java.util.Collection;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.Iterator;
+import java.io.*;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.Address;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+import net.i2p.util.Log;
+
+public class FilesystemAddressDB extends AddressDB {
+
+    public final static String PROP_ADDRESS_DIR = "i2p.addressdir";
+    public final static String DEFAULT_ADDRESS_DIR = "addressDb";
+    private final static Log _log = new Log(FilesystemAddressDB.class);
+    
+    public FilesystemAddressDB(I2PAppContext context) {
+        super(context);
+        
+        //If the address db directory doesn't exist, create it, using the 
+        //contents of hosts.txt.
+        String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
+        File addrDir = new File(dir);
+        if (!addrDir.exists()) {
+            addrDir.mkdir();
+            Properties hosts = new Properties();
+            File hostsFile = new File("hosts.txt");
+            if (hostsFile.exists() && hostsFile.canRead()) {
+                try {
+                    DataHelper.loadProps(hosts, hostsFile);
+                } catch (IOException ioe) {
+                    _log.error("Error loading hosts file " + hostsFile, ioe);
+                }
+            }
+            Iterator iter = hosts.keySet().iterator();
+            while (iter.hasNext()) {
+                String hostname = (String)iter.next();
+                Address addr = new Address();
+                addr.setHostname(hostname);
+                addr.setDestination(hosts.getProperty(hostname));
+                put(addr);
+            }
+        }
+    }
+
+    public Address get(String hostname) {
+        String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
+        File f = new File(dir, hostname);
+        if (f.exists() && f.canRead()) {
+            Address addr = new Address();
+            try {
+                addr.readBytes(new FileInputStream(f));
+            } catch (FileNotFoundException exp) {
+                return null;
+            } catch (DataFormatException exp) {
+                _log.error(f.getPath() + " is not a valid address file.");
+                return null;
+            } catch (IOException exp) {
+                _log.error("Error reading " + f.getPath());
+                return null;
+            }
+            return addr;
+        } else {
+            _log.warn(f.getPath() + " does not exist.");
+            return null;
+        }
+    }
+
+    public Address put(Address address) {
+        Address previous = get(address.getHostname());
+        
+        String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
+        File f = new File(dir, address.getHostname());
+        try {
+            address.writeBytes(new FileOutputStream(f));
+        } catch (Exception exp) {
+            _log.error("Error writing " + f.getPath(), exp);
+        }
+        return previous;
+    }
+
+    public Address remove(String hostname) {
+        Address previous = get(hostname);
+        
+        String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);       
+        File f = new File(dir, hostname);
+        f.delete();
+        return previous;
+    }
+
+    public Address remove(Address address) {
+        if (contains(address)) {
+            return remove(address.getHostname());
+        } else {
+            return null;
+        }
+    }
+
+    public boolean contains(Address address) {
+        Address inDb = get(address.getHostname());
+        return inDb.equals(address);
+    }
+
+    public boolean contains(String hostname) {
+        return hostnames().contains(hostname);
+    }
+
+    public Collection hostnames() {
+        String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
+        File f = new File(dir);
+        return Arrays.asList(f.list());
+    }
+
+}
diff --git a/core/java/src/net/i2p/data/Address.java b/core/java/src/net/i2p/data/Address.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d0358a820716be11c56fa58e661f07991a46bd4
--- /dev/null
+++ b/core/java/src/net/i2p/data/Address.java
@@ -0,0 +1,81 @@
+package net.i2p.data;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import net.i2p.util.Log;
+
+public class Address extends DataStructureImpl {
+    private final static Log _log = new Log(Address.class);
+    private String _hostname;
+    private Destination _destination;
+    
+    public Address() {
+        _hostname = null;
+        _destination = null;
+    }
+
+    public String getHostname() {
+        return _hostname;
+    }
+    
+    public void setHostname(String hostname) {
+        _hostname = hostname;
+    }
+    
+    public Destination getDestination() {
+        return _destination;
+    }
+    
+    public void setDestination(Destination destination) {
+        _destination = destination;
+    }
+    
+    public void setDestination(String base64) {
+        try {
+            Destination result = new Destination();
+            result.fromBase64(base64);
+            _destination = result;
+        } catch (DataFormatException dfe) {
+            _destination = null;
+        }
+    }
+    
+    public void readBytes(InputStream in) throws DataFormatException,
+            IOException {
+        _hostname = DataHelper.readString(in);
+        _destination = new Destination();
+        _destination.readBytes(in);
+    }
+
+    public void writeBytes(OutputStream out) throws DataFormatException,
+            IOException {
+        if ((_hostname == null) || (_destination == null)) 
+            throw new DataFormatException("Not enough data to write address");
+        DataHelper.writeString(out, _hostname);
+        _destination.writeBytes(out);
+    }
+    
+    public boolean equals(Object obj) {
+        if ((obj == null) || !(obj instanceof Address)) return false;
+        Address addr = (Address) obj;
+        return DataHelper.eq(_hostname, addr.getHostname())
+        && DataHelper.eq(_destination, addr.getDestination());
+    }
+    
+    public int hashCode() {
+        return DataHelper.hashCode(getHostname()) 
+        + DataHelper.hashCode(getDestination());
+    }
+    
+    public String toString() {
+        StringBuffer buf = new StringBuffer(64);
+        buf.append("[Address: ");
+        buf.append("\n\tHostname: ").append(getHostname());
+        buf.append("\n\tDestination: ").append(getDestination());
+        buf.append("]");
+        return buf.toString();
+    }
+
+}