package plan9.lib; import java.lang.Integer; import java.util.Map; import java.util.HashMap; import java.util.StringTokenizer; import java.nio.channels.SocketChannel; import java.net.*; // Plan 9/Inferno Dial interface to make network connections // from styx-n-9p.googlecode.com (MIT licence) /** * Dial makes network connections, using a network-independent textual form of addressing services on hosts. * It is based on Plan 9's dial(2). The current implementation supports only TCP/IP, * but the same interface can support non-Internet protocols without change. * It encapsulates the translation of textual forms of host and service names into IP addresses and port numbers, * allowing network connections to be made with a single call. Unlike new Socket(hostname, port), * which is also a single operation, Dial guarantees that it will try all addresses for a host, has an interface not limited to IP networking, and will translate * Plan 9 service names to port numbers. *

* It knows the correct port numbers for various Plan 9 services, such as 9fs, exportfs and ticket. *

* Instead of raising exceptions, Dial returns an error value and makes the diagnostic available in a per-thread error string. *

* There is no constructor: Dial contains only static values and methods. *

* * For example, combining dial and netmkaddr to connect to a 9fs service: *

 *	ByteChannel fd = Dial.dial(Dial.netmkaddr(args[0], "net", "9fs"), null);
 *	if(fd == null)
 *		error(String.format("can't dial %s: %s", args[0], Dial.errstr());
 * 
*/ public class Dial { private Dial(){} private static ThreadLocal lasterror = new ThreadLocal(); /** Return the per-thread error string: the diagnostic from the last failed operation */ public final static String errstr() { return lasterror.get(); } /** Set the per-thread error string to the string s */ public final static void werrstr(String s) { lasterror.set(s); } private static Map portmap = new HashMap(){{ put("tcp!9fs", 564); put("tcp!whoami", 565); put("tcp!guard", 566); put("tcp!ticket", 567); put("tcp!exportfs", 17007); put("tcp!rexexec", 17009); put("tcp!ncpu", 17010); put("tcp!cpu", 17013); put("tcp!venti", 17034); put("tcp!wiki", 17035); put("tcp!secstore", 5356); put("udp!dns", 53); put("tcp!dns", 53); put("tcp!styx", 6666); put("tcp!infsched", 6678); // owen }}; /** * Dial (make a network connection to) the given destination network address. * The address (or "dial string") has the form net!host!service, * where *
*
net
is a network or protocol. Use net to represent any network on which the host is found. * Use tcp to force the use of TCP/IP. *
host
is a symbolic name (eg, host name or DNS name), or a network address in textual form (eg, an IP address such as 127.0.0.1). *
service
is a symbolic name for the desired service, for instance telnet, or a numeric port number. * It can be any name supported by UNIX's getservbyname. *
* {@link Dial#netmkaddr} builds a dial string from its three components, adding defaults as required. *

* Dial tries each possible network address for a host (a host name might correspond to several network addresses), * until one succeeds, or all have failed. On success, dial returns a SocketChannel value giving a full-duplex * connection to the remote host. On an error, it returns null, setting the error string to the diagnostic. * * @param dest network address in the form net!host!service. * @param local leave null or "" to use any local port (other values reserved for future use) * @return a SocketChannel value representing a full-duplex connection to the remote host, or null on any error */ static public final SocketChannel dial(String dest, String local){ StringTokenizer flds = new StringTokenizer(dest, "!"); int n = flds.countTokens(); if(n < 3){ werrstr("invalid network address for dial: "+dest); return null; } String net = flds.nextToken(); String host = flds.nextToken(); String svc = flds.nextToken(); if(net.equals("net")) net = "tcp"; else if(!net.equals("tcp")){ // TO DO: "udp" werrstr("no network to host: "+dest); return null; } InetAddress[] addrs; try{ addrs = InetAddress.getAllByName(host); }catch(UnknownHostException e){ werrstr("unknown host: "+host); return null; } int port = portno(svc); if(port < 0){ Integer p = portmap.get(net+"!"+svc); if(p == null){ werrstr("can't translate service: "+svc); return null; } port = p.intValue(); } for(InetAddress ip : addrs){ try{ SocketChannel sc = SocketChannel.open(new InetSocketAddress(ip, port)); if(sc != null){ sc.socket().setTcpNoDelay(true); return sc; } }catch(java.io.IOException e){ werrstr(e.getMessage()); } } return null; } /** * Add default network and service components as required to an address in {@link Dial#dial}'s form. * The address has {@link Dial#dial}'s net!host!service form, * but if net and svc are missing (ie, the address is just a host name), netmkaddr * will add them, using the values of its {@code net} and {@code svc} parameters. If only the service * is missing, netmkaddr will add it, using {@code svc}. * @param addr an address in one of the forms net!host!svc, net!host, or simply host * @param net default network name to use if addr hasn't got one * @param svc default service name to use if addr hasn't got one * @return a complete dial string */ static public final String netmkaddr(String addr, String net, String svc){ StringTokenizer flds = new StringTokenizer(addr, "!"); int n = flds.countTokens(); if(n <= 1){ if(net == null) net = "net"; if(svc == null) return net+"!"+addr; return net+"!"+addr+"!"+svc; } if(svc != null && n < 2) return addr+"!"+svc; return addr; } static private int portno(String s){ int n = 0; for(int i = 0; i < s.length(); i++){ char c = s.charAt(i); if(!(c >= '0' && c <= '9')) return -1; n = n*10 + ((int)c-'0'); } return n; } }