/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* @(#)PortMapper.java 1.35 06/29/07
*/
package com.sun.messaging.jmq.jmsserver.service;
import java.io.*;
import java.net.*;
import java.nio.channels.SocketChannel;
import java.util.Properties;
import java.util.Hashtable;
import java.util.Map;
import java.util.HashMap;
import java.util.Enumeration;
import javax.net.ServerSocketFactory;
import com.sun.messaging.jmq.io.MQAddress;
import com.sun.messaging.jmq.io.PortMapperTable;
import com.sun.messaging.jmq.io.PortMapperEntry;
import com.sun.messaging.jmq.util.log.Logger;
import com.sun.messaging.jmq.util.service.PortMapperClientHandler;
import com.sun.messaging.jmq.jmsserver.Broker;
import com.sun.messaging.jmq.jmsservice.BrokerEvent;
import com.sun.messaging.jmq.jmsserver.BrokerStateHandler;
import com.sun.messaging.jmq.jmsserver.config.*;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.resources.*;
import com.sun.messaging.jmq.jmsserver.util.LockFile;
import com.sun.messaging.jmq.jmsserver.core.BrokerMQAddress;
import com.sun.messaging.jmq.net.MQServerSocketFactory;
/**
* The PortMapper is a simple service that hands out service/port pairs.
* Basically a thread listens on a ServerSocket. When a client connects
* the PortMapper dumps the port map to the socket, closes the connection
* and goes back to listening on the socket.
*/
public class PortMapper implements Runnable, ConfigListener, PortMapperClientHandler {
public static final int PORTMAPPER_DEFAULT_PORT = 7676;
// Hostname can not be dynamically updated
public static final String HOSTNAME_PROPERTY = Globals.IMQ +
".portmapper.hostname";
private static final String IMQHOSTNAME_PROPERTY = Globals.IMQ +
".hostname";
private static final String PORT_PROPERTY = Globals.IMQ +
".portmapper.port";
public static final String BIND_PROPERTY = Globals.IMQ +
".portmapper.bind";
private static final String BACKLOG_PROPERTY = Globals.IMQ +
".portmapper.backlog";
private static final String SOTIMEOUT_PROPERTY = Globals.IMQ +
".portmapper.sotimeout";
private static final String SOLINGER_PROPERTY = Globals.IMQ +
".portmapper.solinger";
static final String SERVICE_NAME = "portmapper";
private static boolean DEBUG = false;
private Logger logger = null;
private BrokerResources rb = null;
private BrokerConfig bc = null;
private PortMapperTable portMapTable = null;
private ServerSocket serverSocket = null;
private int port = 0;
// specifies whether the portmapper should bind to the portmapper port,
// or whether some other component will do that on our behalf
private boolean doBind = true;
private int backlog = 100;
private int sotimeout = 100;
private int solinger = -1;
private InetAddress bindAddr = null;
private String hostname = null;
private HashMap portmapperMap = null;
private MQAddress mqaddress = null;
private boolean running = false;
private static MQServerSocketFactory ssf = (MQServerSocketFactory)MQServerSocketFactory.getDefault();
/**
* updates the portmapper service
*/
public void updateProperties() {
// if we have a brokerid set, publish in in the portmapper
// entry
portmapperMap = new HashMap();
if (Globals.getBrokerID() != null) {
portmapperMap.put("brokerid", Globals.getBrokerID());
}
if (Globals.getBrokerSessionID() != null) {
portmapperMap.put("sessionid", Globals.getBrokerSessionID().toString());
String tmp;
tmp = Globals.getConfig().getProperty(Globals.JMQ_HOME_PROPERTY);
if ((tmp != null) && (!tmp.equals(""))) {
portmapperMap.put("imqhome", tmp);
}
tmp = Globals.getConfig().getProperty(Globals.JMQ_VAR_HOME_PROPERTY);
if ((tmp != null) && (!tmp.equals(""))) {
portmapperMap.put("imqvarhome", tmp);
}
}
updateServiceProperties(SERVICE_NAME, portmapperMap );
}
/**
* Create a portmapper for this instance of a broker.
*
* @param instance Instance name of this broker
*/
public PortMapper(String instance) {
running = true;
bc = Globals.getConfig();
if (!bc.getBooleanProperty("imq.portmapper.reuseAddress", true)) {
ssf.setReuseAddress(false);
}
portMapTable = new PortMapperTable();
portMapTable.setBrokerInstanceName(instance);
portMapTable.setBrokerVersion(Globals.getVersion().getProductVersion());
logger = Globals.getLogger();
rb = Globals.getBrokerResources();
addService(SERVICE_NAME, "tcp", "PORTMAPPER", port, portmapperMap);
addService("cluster_discovery", "tcp", "CLUSTER_DISCOVERY", 0, null);
bc.addListener(PORT_PROPERTY, this);
bc.addListener(BACKLOG_PROPERTY, this);
}
public void destroy() {
running = false;
try {
if (serverSocket != null)
serverSocket.close();
} catch (IOException ex) {
logger.logStack(Logger.INFO,"Error closing portmapper", ex);
}
serverSocket = null;
}
/**
* Configure the portmapper with its properties
*/
public void setParameters(BrokerConfig params)
throws PropertyUpdateException {
// doBind must be checked first
doBind = params.getBooleanProperty(PortMapper.BIND_PROPERTY,true);
// Hostname must be configured next
String value = (String)params.getProperty(HOSTNAME_PROPERTY);
if (value == null || value.trim().length() == 0) {
// If portmapper specific hostname is not set, check imq.hostname
value = (String)params.getProperty(IMQHOSTNAME_PROPERTY);
}
validate(HOSTNAME_PROPERTY, value);
update(HOSTNAME_PROPERTY, value);
value = (String)params.getProperty(PORT_PROPERTY);
validate(PORT_PROPERTY, value);
update(PORT_PROPERTY, value);
value = (String)params.getProperty(BACKLOG_PROPERTY);
validate(BACKLOG_PROPERTY, value);
update(BACKLOG_PROPERTY, value);
value = (String)params.getProperty(SOTIMEOUT_PROPERTY);
if (value != null) {
validate(SOTIMEOUT_PROPERTY, value);
update(SOTIMEOUT_PROPERTY, value);
}
value = (String)params.getProperty(SOLINGER_PROPERTY);
if (value != null) {
validate(SOLINGER_PROPERTY, value);
update(SOLINGER_PROPERTY, value);
}
}
/**
* Change the portmapper service's port
*/
private synchronized void setPort(int port) {
if (port == this.port) {
return;
}
this.port = port;
addService(SERVICE_NAME, "tcp", "PORTMAPPER", port, portmapperMap);
LockFile lf = LockFile.getCurrentLockFile();
try {
if (lf != null) {
lf.updatePort(port);
}
} catch (IOException e) {
logger.log(Logger.WARNING, rb.E_LOCKFILE_BADUPDATE, e);
}
if (serverSocket != null) {
// If there is a server socket close it so we create a new
// one with the new port
try {
serverSocket.close();
} catch (IOException e) {}
}
}
public int getPort() {
return port;
}
/**
* Change the portmapper service's host interface
*/
public synchronized void setHostname(String hostname)
throws PropertyUpdateException {
MQAddress mqaddr = null;
try {
String h = hostname;
if (hostname != null &&
hostname.equals(Globals.HOSTNAME_ALL)) {
h = null;
}
mqaddr = MQAddress.getMQAddress(h, getPort());
} catch (Exception e) {
throw new PropertyUpdateException(
PropertyUpdateException.InvalidSetting,
hostname+": "+e.toString(), e);
}
if (hostname == null || hostname.equals(Globals.HOSTNAME_ALL) ||
hostname.trim().length() == 0) {
// Bind to all
this.hostname = null;
this.bindAddr = null;
mqaddress = mqaddr;
return;
}
if (hostname.equals(this.hostname)) {
return;
}
try {
if (Globals.isConfigForCluster()) {
String hn = hostname;
if (hn != null && hn.equals("localhost")) hn = null;
this.bindAddr = BrokerMQAddress.resolveBindAddress(hn, true);
mqaddr = MQAddress.getMQAddress(
this.bindAddr.getHostAddress(), getPort());
} else {
this.bindAddr = InetAddress.getByName(hostname);
}
} catch (Exception e) {
throw new PropertyUpdateException(
PropertyUpdateException.InvalidSetting,
rb.getString(rb.E_BAD_HOSTNAME, hostname),
e);
}
this.hostname = hostname;
this.mqaddress = mqaddr;
LockFile lf = LockFile.getCurrentLockFile();
try {
if (lf != null) {
lf.updateHostname(mqaddress.getHostName());
}
} catch (IOException e) {
logger.log(Logger.WARNING, rb.E_LOCKFILE_BADUPDATE, e);
}
if (serverSocket != null) {
// If there is a server socket close it so we create a new
// one with the new port
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
/**
* This should never return HOSTNAME_ALL when
* called by Globals.getPortMapper().getHostname
*/
public String getHostname() {
return hostname;
}
/**
* This should never return null when called
* by Globals.getPortMapper().getMQAddress
*/
public MQAddress getMQAddress() {
return mqaddress;
}
public InetAddress getBindAddress() {
return bindAddr;
}
/**
* Set the backlog parameter on the socket the portmapper is running on
*/
public synchronized void setBacklog(int backlog) {
this.backlog = backlog;
if (serverSocket != null) {
// If there is a server socket close it so we create a new
// one with the new backlog
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
/**
* Add a service to the port mapper
*
* @param name Name of service
* @param protocl Transport protocol of service ("tcp", "ssl", etc)
* @param type Service type (NORMAL, ADMIN, etc)
* @param port Port service is runningon
*/
public synchronized void addService(String name,
String protocol, String type, int port, HashMap props) {
PortMapperEntry pme = new PortMapperEntry();
pme.setName(name);
pme.setProtocol(protocol);
pme.setType(type);
pme.setPort(port);
if (props != null) {
pme.addProperties(props);
}
portMapTable.add(pme);
}
/**
* update the port for a service
* @param name Name of service
* @param port Port service is runningon
*/
public synchronized void updateServicePort(String name, int port)
{
PortMapperEntry pme = portMapTable.get(name);
if (pme != null) {
pme.setPort(port);
}
}
/**
* update the service properties
* @param name Name of service
* @param props Properties for a service
*/
public synchronized void updateServiceProperties(String name, HashMap props)
{
PortMapperEntry pme = portMapTable.get(name);
if (pme != null) {
pme.addProperties(props);
}
}
/**
* Add a service to the port mapper
*
* @param pme A PortMapperEntry containing all information for
* the service.
*/
public synchronized void addService(String name, PortMapperEntry pme) {
portMapTable.add(pme);
}
/**
* Remove a service from the port mapper
*/
public synchronized void removeService(String name) {
portMapTable.remove(name);
}
/**
* Get a hashtable containing information for each service.
* This hashtable is indexed by the service name, and the values
* are PortMapperEntry's
*/
public synchronized Map getServices() {
return portMapTable.getServices();
}
public synchronized String toString() {
return portMapTable.toString();
}
/**
* Bind the portmapper to the port
* unless we have configured the portmapper to expect some other component to do this on our behalf
*/
public synchronized void bind() {
if (doBind){
serverSocket = createPortMapperServerSocket(this.port, this.bindAddr);
}
}
/**
* Return the ServerSocket the portmapper is using.
* Returns null if portmapper is not currently bound to
* a server socket.
*/
public synchronized ServerSocket getServerSocket() {
return serverSocket;
}
/**
* Create a ServerSocket for the portmapper
*
* @param port Port number to create socket on
* @param bindAddr Interface to bind to. Null to bind to all.
*/
private ServerSocket createPortMapperServerSocket(
int port, InetAddress bindAddr) {
ServerSocket serverSocket = null;
try {
serverSocket = ssf.createServerSocket(port, backlog, bindAddr);
} catch (BindException e) {
logger.log(Logger.ERROR, rb.E_BROKER_PORT_BIND,
SERVICE_NAME, new Integer(port));
return null;
} catch (IOException e) {
logger.log(Logger.ERROR, rb.E_BAD_SERVICE_START,
SERVICE_NAME, new Integer(port), e);
return null;
}
Object[] args = {SERVICE_NAME,
"tcp [ " + port + ", " + backlog + ", " +
(bindAddr != null ? bindAddr.getHostAddress() :
Globals.HOSTNAME_ALL) +
" ]",
new Integer(1), new Integer(1)};
logger.log(Logger.INFO, rb.I_SERVICE_START, args);
return serverSocket;
}
public void run() {
int restartCode = BrokerStateHandler.getRestartCode();
String restartOOMsg = rb.getKString(rb.M_LOW_MEMORY_PORTMAPPER_RESTART);
String acceptOOMsg = rb.getKString(rb.M_LOW_MEMORY_PORTMAPPER_ACCEPT);
Socket connection = null;
boolean oom = false;
try {
if (serverSocket == null) {
serverSocket = createPortMapperServerSocket(this.port, this.bindAddr);
}
if (DEBUG && serverSocket != null) {
logger.log(Logger.DEBUG, "PortMapper: " + serverSocket + " "
+ MQServerSocketFactory.serverSocketToString(serverSocket) + ", backlog=" + backlog + "");
}
boolean firstpass = true;
while (running) {
if (serverSocket == null) {
logger.log(Logger.ERROR, rb.E_PORTMAPPER_EXITING);
return;
}
try {
connection = serverSocket.accept();
firstpass = true;
} catch (SocketException e) {
if (e instanceof BindException || e instanceof ConnectException
|| e instanceof NoRouteToHostException) {
logger.log(Logger.ERROR, rb.E_PORTMAPPER_ACCEPT, e);
// We sleep in case the exception is not repairable.
// This
// prevents us from a tight loop.
sleep(1);
} else {
if (!running)
break;
// Serversocket was closed. Should be because something
// like the port number has changed. Try to recreate
// the server socket.
try {
// Make sure it is closed
serverSocket.close();
} catch (IOException ioe) {
} catch (NullPointerException ioe) {
if (!running)
break;
}
serverSocket = createPortMapperServerSocket(this.port, this.bindAddr);
}
continue;
} catch (IOException e) {
logger.logStack(Logger.ERROR, rb.E_PORTMAPPER_ACCEPT, e);
sleep(1);
continue;
} catch (OutOfMemoryError e) {
if (!running)
break;
if (firstpass) {
firstpass = false;
Globals.handleGlobalError(e, acceptOOMsg);
sleep(1);
continue;
}
Broker.getBroker().exit(restartCode, restartOOMsg, BrokerEvent.Type.RESTART, null, false, false,
true);
return;
}
// process the new portmapper client and close the socket
handleSocket(connection);
}
} catch (OutOfMemoryError e) {
oom = true;
throw e;
} finally {
try {
try {
if (connection != null)
connection.close();
if (serverSocket != null)
serverSocket.close();
} catch (IOException e) {
}
if (oom && running) {
logger.log(Logger.ERROR, restartOOMsg);
Broker.getBroker().exit(restartCode, restartOOMsg, BrokerEvent.Type.RESTART, null, false, false,
true);
}
if (running)
logger.log(Logger.INFO, rb.M_PORTMAPPER_EXITING);
} catch (OutOfMemoryError e) {
if (running) {
Broker.getBroker().exit(restartCode, restartOOMsg, BrokerEvent.Type.RESTART, null, false, false,
true);
}
}
}
}
/**
* Process a newly-connected PortMapper client and then close the socket
*
* This method takes a Socket and is intended to be called by this class's run() loop after
* the incoming connection has been accepted and the new socket created
*
* @param socket the newly-connected PortMapper client
*/
public void handleSocket(Socket socket) {
String connOOMsg = rb.getKString(rb.M_LOW_MEMORY_PORTMAPPER_CONNECTION);
// Make sure socket is still connected. Client may
// have disconnected if we were slow to accept connection
if (!socket.isConnected()) {
logger.log(Logger.DEBUG, "PortMapper: accepted client connection (" + socket.toString() + ") that is"
+ " no longer connected. Ignoring.");
try {
socket.close();
} catch (IOException e) {
}
return;
}
// Got connection. Write port map and close connection
try {
synchronized (this) {
/*
* Get version from client. 2.0 client does not send a version
* so we must set SoTimeout to timeout if there is nothing to
* read. 3.0 client does send version (101).
*/
socket.setSoTimeout(sotimeout);
if (solinger > 0) {
socket.setSoLinger(true, solinger);
}
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String version = "";// "101";
try {
version = br.readLine();
} catch (SocketTimeoutException e) {
// 2.0 client did not send version
}
// System.out.println("Version = " + version);
portMapTable.write(socket.getOutputStream());
// Force reads until EOF. This avoids leaving
// sockets in TIME_WAIT. See 4750307. Typically
// this will block until the client closes the
// connection or SoTimeout expires.
try {
int n = 0;
while (br.readLine() != null) {
if (++n >= 5)
break; // Don't read forever
}
} catch (SocketTimeoutException e) {
// Client did not close socket before sotimeout
// expired. That's OK. Just means we leave connection
// in TIME_WAIT state.
}
}
} catch (IOException e) {
InetAddress ia = socket.getInetAddress();
logger.logStack(Logger.WARNING, rb.E_PORTMAPPER_EXCEPTION, ia.getHostAddress(), e);
} catch (OutOfMemoryError e) {
if (!running)
return;
logger.log(Logger.WARNING, connOOMsg);
try {
socket.close();
} catch (Throwable t) {
}
;
Globals.handleGlobalError(e, connOOMsg);
sleep(1);
} finally {
try {
socket.close();
} catch (IOException e) {
}
}
}
/**
* Process a newly-connected PortMapper client and then close the socket
*
* This method takes a SocketChannel and is intended to be called by an external proxy
* which has accepted the connection for us and created the new socket
*
* @param clientSocketChannel the newly-connected PortMapper client
*/
public void handleRequest(SocketChannel clientSocketChannel) {
if (doBind) throw new IllegalStateException("Should not call PortMapper.handleRequest() unless Broker has been started with the -noBind argument");
handleSocket(clientSocketChannel.socket());
}
public void validate(String name, String value)
throws PropertyUpdateException {
if (!name.equals(PORT_PROPERTY) &&
!name.equals(BACKLOG_PROPERTY) &&
!name.equals(SOLINGER_PROPERTY) &&
!name.equals(HOSTNAME_PROPERTY) &&
!name.equals(SOTIMEOUT_PROPERTY)) {
throw new PropertyUpdateException(
rb.getString(rb.X_BAD_PROPERTY, name));
}
if (name.equals(HOSTNAME_PROPERTY)) {
if (value == null || value.trim().length() == 0 ||
value.equals(Globals.HOSTNAME_ALL)) {
// null is OK. Means bind to all interfaces
return;
}
try {
if (Globals.isConfigForCluster()) {
BrokerMQAddress.resolveBindAddress(value, true);
} else {
InetAddress.getByName(value);
}
} catch (Exception e) {
throw new PropertyUpdateException(
PropertyUpdateException.InvalidSetting,
rb.getKString(rb.E_BAD_HOSTNAME_PROP, value, name)+": "+e.toString(),
e);
}
return;
}
// Will throw an exception if integer value is bad
int n = getIntProperty(name, value);
if (name.equals(PORT_PROPERTY)) {
if (n == this.port) {
return;
}
if (isDoBind()){
// Check if we will be able to bind to this port
try {
canBind(n, this.bindAddr);
} catch (BindException e) {
throw new PropertyUpdateException(
rb.getKString(rb.E_BROKER_PORT_BIND, SERVICE_NAME, value)+
"\n"+e.toString());
} catch (IOException e) {
throw new PropertyUpdateException(
rb.getKString(rb.E_BAD_SERVICE_START, SERVICE_NAME, value) +
"\n"+e.toString());
}
}
}
}
public boolean update(String name, String value) {
try {
if (name.equals(PORT_PROPERTY)) {
setPort(getIntProperty(name, value));
if (mqaddress != null) {
mqaddress = MQAddress.getMQAddress(
mqaddress.getHostName()+":"+getPort());
}
} else if (name.equals(BACKLOG_PROPERTY)) {
setBacklog(getIntProperty(name, value));
} else if (name.equals(SOTIMEOUT_PROPERTY)) {
sotimeout = getIntProperty(name, value);
} else if (name.equals(SOLINGER_PROPERTY)) {
solinger = getIntProperty(name, value);
} else {
setHostname(value);
}
} catch (Exception e) {
logger.log(
Logger.ERROR,
rb.getString(rb.X_BAD_PROPERTY_VALUE, name + "=" + value),
e);
return false;
}
return true;
}
public int getIntProperty(String name, String value)
throws PropertyUpdateException {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new PropertyUpdateException(
rb.getString(rb.X_BAD_PROPERTY_VALUE, name + "=" + value));
}
}
public static void sleep(int nseconds) {
try {
Thread.sleep(nseconds * 1000L);
} catch (Exception e) {}
}
/**
* Check if you can open a server socket on the specified port
* and address.
*
* @param port Port number to bind to
* @param bindAddr Address to bind to. Null to bind to all connections
*
* Throws an IOException if you can't
*/
public static void canBind(int port, InetAddress bindAddr)
throws IOException {
ServerSocket ss = null;
ss = ssf.createServerSocket(port, 0, bindAddr);
ss.close();
return;
}
/**
* Return whether the portmapper should bind to the portmapper port,
* or whether some other component will do that on our behalf
*
* @return
*/
public boolean isDoBind() {
return doBind;
}
/**
* Get the instance name of the broker running on the specified port.
* Throws an IOException if it has any trouble connecting to the
* port and reading a portmapper table.
*/
public static String getBrokerAtPort(String host, int port)
throws IOException {
InetAddress ia = null;
if (host == null) {
ia = InetAddress.getLocalHost();
} else {
ia = InetAddress.getByName(host);
}
String version =
String.valueOf(PortMapperTable.PORTMAPPER_VERSION) + "\n";
Socket s = new Socket(ia, port);
InputStream is = s.getInputStream();
OutputStream os = s.getOutputStream();
try {
// Write version of portmapper we support
os.write(version.getBytes());
os.flush();
} catch (IOException e) {
}
PortMapperTable table = new PortMapperTable();
table.read(is);
return table.getBrokerInstanceName();
}
}