/* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2013. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ package com.ericsson.otp.erlang; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetAddress; /** * Provides methods for registering, unregistering and looking up nodes with the Erlang * portmapper daemon (Epmd). For each registered node, Epmd maintains information about * the port on which incoming connections are accepted, as well as which versions of the * Erlang communication protocol the node supports. * * <p> * Nodes wishing to contact other nodes must first request information from Epmd before a * connection can be set up, however this is done automatically by * {@link OtpSelf#connect(OtpPeer) OtpSelf.connect()} when necessary. * * <p> * The methods {@link #publishPort(OtpLocalNode) publishPort()} and * {@link #unPublishPort(OtpLocalNode) unPublishPort()} will fail if an Epmd process is * not running on the localhost. Additionally {@link #lookupPort(AbstractNode) * lookupPort()} will fail if there is no Epmd process running on the host where the * specified node is running. See the Erlang documentation for information about starting * Epmd. * * <p> * This class contains only static methods, there are no constructors. */ public class OtpEpmd { private static class EpmdPort { private static int epmdPort = 0; public static int get() { if (epmdPort == 0) { String env; try { env = System.getenv("ERL_EPMD_PORT"); } catch (final java.lang.SecurityException e) { env = null; } epmdPort = env != null ? Integer.parseInt(env) : 4369; } return epmdPort; } public static void set(final int port) { epmdPort = port; } } // common values private static final byte stopReq = (byte) 115; private static final byte port4req = (byte) 122; private static final byte port4resp = (byte) 119; private static final byte publish4req = (byte) 120; private static final byte publish4resp = (byte) 121; private static final byte names4req = (byte) 110; private static int traceLevel = 0; private static final int traceThreshold = 4; static { // debug this connection? final String trace = System.getProperties().getProperty("OtpConnection.trace"); try { if (trace != null) { traceLevel = Integer.valueOf(trace).intValue(); } } catch (final NumberFormatException e) { traceLevel = 0; } } // only static methods: no public constructors // hmm, idea: singleton constructor could spawn epmd process private OtpEpmd() { } /** * Set the port number to be used to contact the epmd process. Only needed when the * default port is not desired and system environment variable ERL_EPMD_PORT can not * be read (applet). */ public static void useEpmdPort(final int port) { EpmdPort.set(port); } /** * Determine what port a node listens for incoming connections on. * * @return the listen port for the specified node, or 0 if the node was not registered * with Epmd. * * @exception java.io.IOException * if there was no response from the name server. */ public static int lookupPort(final AbstractNode node) throws IOException { return r4_lookupPort(node); } /** * Register with Epmd, so that other nodes are able to find and connect to it. * * @param node * the server node that should be registered with Epmd. * * @return true if the operation was successful. False if the node was already * registered. * * @exception java.io.IOException * if there was no response from the name server. */ public static boolean publishPort(final OtpLocalNode node) throws IOException { OtpTransport s = null; s = r4_publish(node); node.setEpmd(s); return s != null; } // Ask epmd to close his end of the connection. // Caller should close his epmd socket as well. // This method is pretty forgiving... /** * Unregister from Epmd. Other nodes wishing to connect will no longer be able to. * * <p> * This method does not report any failures. */ public static void unPublishPort(final OtpLocalNode node) { OtpTransport s = null; try { s = node.createTransport((String) null, EpmdPort.get()); @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); obuf.write2BE(node.alive().length() + 1); obuf.write1(stopReq); obuf.writeN(node.alive().getBytes()); obuf.writeToAndFlush(s.getOutputStream()); // don't even wait for a response (is there one?) if (traceLevel >= traceThreshold) { System.out.println("-> UNPUBLISH " + node + " port=" + node.port()); System.out.println("<- OK (assumed)"); } } catch (final Exception e) {/* ignore all failures */ } finally { try { if (s != null) { s.close(); } } catch (final IOException e) { /* ignore close failure */ } s = null; } } private static int r4_lookupPort(final AbstractNode node) throws IOException { int port = 0; OtpTransport s = null; try { @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); s = node.createTransport(node.host(), EpmdPort.get()); // build and send epmd request // length[2], tag[1], alivename[n] (length = n+1) obuf.write2BE(node.alive().length() + 1); obuf.write1(port4req); obuf.writeN(node.alive().getBytes()); // send request obuf.writeToAndFlush(s.getOutputStream()); if (traceLevel >= traceThreshold) { System.out.println("-> LOOKUP (r4) " + node); } // receive and decode reply // resptag[1], result[1], port[2], ntype[1], proto[1], // disthigh[2], distlow[2], nlen[2], alivename[n], // elen[2], edata[m] final byte[] tmpbuf = new byte[100]; final int n = s.getInputStream().read(tmpbuf); if (n < 0) { s.close(); throw new IOException("Nameserver not responding on " + node.host() + " when looking up " + node.alive()); } @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); final int response = ibuf.read1(); if (response == port4resp) { final int result = ibuf.read1(); if (result == 0) { port = ibuf.read2BE(); node.ntype = ibuf.read1(); node.proto = ibuf.read1(); node.distHigh = ibuf.read2BE(); node.distLow = ibuf.read2BE(); // ignore rest of fields } } } catch (final IOException e) { if (traceLevel >= traceThreshold) { System.out.println("<- (no response)"); } throw new IOException("Nameserver not responding on " + node.host() + " when looking up " + node.alive(), e); } catch (final OtpErlangDecodeException e) { if (traceLevel >= traceThreshold) { System.out.println("<- (invalid response)"); } throw new IOException("Nameserver not responding on " + node.host() + " when looking up " + node.alive()); } finally { try { if (s != null) { s.close(); } } catch (final IOException e) { /* ignore close errors */ } s = null; } if (traceLevel >= traceThreshold) { if (port == 0) { System.out.println("<- NOT FOUND"); } else { System.out.println("<- PORT " + port); } } return port; } /* * this function will get an exception if it tries to talk to a very old epmd, or if * something else happens that it cannot forsee. In both cases we return an exception. * We no longer support r3, so the exception is fatal. If we manage to successfully * communicate with an r4 epmd, we return either the socket, or null, depending on the * result. */ private static OtpTransport r4_publish(final OtpLocalNode node) throws IOException { OtpTransport s = null; try { @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); s = node.createTransport((String) null, EpmdPort.get()); obuf.write2BE(node.alive().length() + 13); obuf.write1(publish4req); obuf.write2BE(node.port()); obuf.write1(node.type()); obuf.write1(node.proto()); obuf.write2BE(node.distHigh()); obuf.write2BE(node.distLow()); obuf.write2BE(node.alive().length()); obuf.writeN(node.alive().getBytes()); obuf.write2BE(0); // No extra // send request obuf.writeToAndFlush(s.getOutputStream()); if (traceLevel >= traceThreshold) { System.out.println("-> PUBLISH (r4) " + node + " port=" + node.port()); } // get reply final byte[] tmpbuf = new byte[100]; final int n = s.getInputStream().read(tmpbuf); if (n < 0) { s.close(); throw new IOException("Nameserver not responding on " + node.host() + " when publishing " + node.alive()); } @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); final int response = ibuf.read1(); if (response == publish4resp) { final int result = ibuf.read1(); if (result == 0) { node.creation = ibuf.read2BE(); if (traceLevel >= traceThreshold) { System.out.println("<- OK"); } return s; // success } } } catch (final IOException e) { // epmd closed the connection = fail if (s != null) { s.close(); } if (traceLevel >= traceThreshold) { System.out.println("<- (no response)"); } throw new IOException("Nameserver not responding on " + node.host() + " when publishing " + node.alive()); } catch (final OtpErlangDecodeException e) { s.close(); if (traceLevel >= traceThreshold) { System.out.println("<- (invalid response)"); } throw new IOException("Nameserver not responding on " + node.host() + " when publishing " + node.alive()); } s.close(); return null; } public static String[] lookupNames() throws IOException { return lookupNames(InetAddress.getByName(null), new OtpSocketTransportFactory()); } public static String[] lookupNames(final OtpTransportFactory transportFactory) throws IOException { return lookupNames(InetAddress.getByName(null), transportFactory); } public static String[] lookupNames(final InetAddress address) throws IOException { return lookupNames(address, new OtpSocketTransportFactory()); } public static String[] lookupNames(final InetAddress address, final OtpTransportFactory transportFactory) throws IOException { OtpTransport s = null; try { @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); try { s = transportFactory.createTransport(address, EpmdPort.get()); obuf.write2BE(1); obuf.write1(names4req); // send request obuf.writeToAndFlush(s.getOutputStream()); if (traceLevel >= traceThreshold) { System.out.println("-> NAMES (r4) "); } // get reply final byte[] buffer = new byte[256]; final ByteArrayOutputStream out = new ByteArrayOutputStream(256); while (true) { final int bytesRead = s.getInputStream().read(buffer); if (bytesRead == -1) { break; } out.write(buffer, 0, bytesRead); } final byte[] tmpbuf = out.toByteArray(); @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); ibuf.read4BE(); // read port int // final int port = ibuf.read4BE(); // check if port = epmdPort final int n = tmpbuf.length; final byte[] buf = new byte[n - 4]; System.arraycopy(tmpbuf, 4, buf, 0, n - 4); final String all = OtpErlangString.newString(buf); return all.split("\n"); } finally { if (s != null) { s.close(); } } } catch (final IOException e) { if (traceLevel >= traceThreshold) { System.out.println("<- (no response)"); } throw new IOException("Nameserver not responding when requesting names"); } catch (final OtpErlangDecodeException e) { if (traceLevel >= traceThreshold) { System.out.println("<- (invalid response)"); } throw new IOException("Nameserver not responding when requesting names"); } } }