Portmapper.java
/**
* Copyright 2016-2018 Dell Inc. or its subsidiaries. All rights reserved.
*
* <p>Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
* except in compliance with the License. A copy of the License is located at
*
* <p>http://www.apache.org/licenses/LICENSE-2.0.txt
*
* <p>or in the "license" file accompanying this file. This file 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.
*/
package com.emc.ecs.nfsclient.portmap;
import com.emc.ecs.nfsclient.network.NetMgr;
import com.emc.ecs.nfsclient.rpc.RpcException;
import com.emc.ecs.nfsclient.rpc.RpcStatus;
import com.emc.ecs.nfsclient.rpc.Xdr;
import java.io.IOException;
import org.apache.commons.lang3.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used to query the port used for a service (mount or NFS), as specified by
* https://tools.ietf.org/html/rfc1833.
*
* @author seibed
*/
public final class Portmapper {
private static final Logger LOG = LoggerFactory.getLogger(Portmapper.class);
/** TCP port for Portmap calls, as specified by https://tools.ietf.org/html/rfc1833. */
private static final int PMAP_PORT = 111;
/** Maximum request size in bytes. */
private static final int PORTMAP_MAX_REQUEST_SIZE = 128;
/** RPC timeout in seconds. */
private static int PORTMAP_RPC_TIMEOUT = 10;
/** Maximum number of retries. Calls are wrapped in case of temporary issues. */
private static int _maxRetry = 2;
/**
* <ul>
* <li>If <code>true</code>, use a privileged local port (below 1024) for Portmap RPC
* communication.
* <li>If <code>false</code>, use any non-privileged local port for Portmap RPC communication.
* </ul>
*/
private static boolean _usePrivilegedPort = false;
/**
* Given program and version of a service, query its tcp port number
*
* @param program The program number, used to identify it for RPC calls.
* @param version The program version number, used to identify it for RPC calls.
* @param serverIP The server IP address.
* @return The port number for the program.
*/
public static int queryPortFromPortMap(int program, int version, String serverIP)
throws IOException {
GetPortResponse response = null;
GetPortRequest request = new GetPortRequest(program, version);
for (int i = 0; i < _maxRetry; ++i) {
try {
LOG.debug(
"Querying portmapper: server={}, port={}, program={}, version={}, attempt={}/{}",
serverIP,
PMAP_PORT,
program,
version,
i + 1,
_maxRetry);
Xdr portmapXdr = new Xdr(PORTMAP_MAX_REQUEST_SIZE);
request.marshalling(portmapXdr);
Xdr reply =
NetMgr.getInstance()
.sendAndWait(
serverIP, PMAP_PORT, _usePrivilegedPort, portmapXdr, PORTMAP_RPC_TIMEOUT);
response = new GetPortResponse();
response.unmarshalling(reply);
break;
} catch (RpcException e) {
handleRpcException(e, i, serverIP, program, version);
}
}
int port = response.getPort();
if (port == 0) { // A port value of zeros means the program has not been
// registered.
String msg =
String.format(
"Portmapper has no TCP service registration: server=%s, port=%d, program=%d,"
+ " version=%d. Check that the requested service/version is enabled and registered with rpcbind."
+ " NFSv3 requires both NFS (100003) and MOUNT (100005) services.",
serverIP, PMAP_PORT, program, version);
LOG.warn("{}", msg);
throw new IOException(msg);
}
LOG.debug(
"Resolved RPC service port: server={}, program={}, version={}, servicePort={}",
serverIP,
program,
version,
port);
return port;
}
/**
* Decide whether to retry or throw exception.
*
* @param e The exception.
* @param attemptNumber The number of attempts so far.
*/
private static void handleRpcException(
RpcException e, int attemptNumber, String server, int program, int version)
throws IOException {
boolean networkError = e.getStatus().equals(RpcStatus.NETWORK_ERROR);
if (networkError && attemptNumber + 1 < _maxRetry) {
LOG.warn(
"Portmapper query failed; retrying: server={}, port={}, program={}, version={}, attempt={}/{},"
+ " status={}: {}",
server,
PMAP_PORT,
program,
version,
attemptNumber + 1,
_maxRetry,
e.getStatus(),
e.getMessage(),
e);
return;
}
String message =
String.format(
"Portmapper %s failure: server=%s, port=%d, program=%d, version=%d, attempt=%d/%d,"
+ " status=%s: %s. Check rpcbind availability and TCP port 111 reachability.",
networkError ? "network" : "RPC",
server,
PMAP_PORT,
program,
version,
attemptNumber + 1,
_maxRetry,
e.getStatus(),
e.getMessage());
LOG.warn("{}", message, e);
throw new IOException(message, e);
}
/** Never called. */
private Portmapper() {
throw new NotImplementedException("No class instances should be needed.");
}
}